一、<input type="file">

带有 type="file" 的 <input> 元素允许用户可以从他们的设备中选择一个或多个文件。选择后,这些文件可以使用提交表单的方式上传到服务器上,或者通过 Javascript 代码和文件 API 对文件进行操作。

<input type="file"> - HTML(超文本标记语言) | MDN

例子:

<!DOCTYPE html>
<html>
<head> 
<meta charset="utf-8"> 
<title>菜鸟教程(runoob.com)</title> 
</head>
<body>
<p>

<label for="avatar">Choose a profile picture:</label>
<input type="file" id="avatar" name="avatar" accept="image/png, image/jpeg" />

</body>
</html>

二、file_chooser.mojom定义:

        third_party\blink\public\mojom\choosers\file_chooser.mojom

module blink.mojom;

import "mojo/public/mojom/base/file_path.mojom";
import "mojo/public/mojom/base/string16.mojom";
import "mojo/public/mojom/base/time.mojom";
import "url/mojom/url.mojom";

// FileChooserParams stores arguments for an operation to open a file
// chooser dialog.
struct FileChooserParams {
  enum Mode {
    // Requires that the file exists before allowing the user to pick it.
    kOpen,

    // Like Open, but allows picking multiple files to open.
    kOpenMultiple,

    // Like Open, but selects a folder for upload.  This mode returns multiple
    // FileChooserFileInfo objects for descendant files, rather than a single
    // FileChooserFileInfo for the selected directory.
    kUploadFolder,

    // Allows picking a nonexistent file, and prompts to overwrite if the file
    // already exists. This is not for Blink but for PPAPI.
    kSave,
  };
  Mode mode = kOpen;

  // Title to be used for the dialog. This may be empty for the default title,
  // which will be either "Open" or "Save" depending on the mode.
  mojo_base.mojom.String16 title;

  // Default file name to select in the dialog with kSave mode.
  mojo_base.mojom.FilePath default_file_name;

  // |selected_files| has filenames which a file upload control already
  // selected. A FileChooser implementation may ask a user to select
  //  - removing a file from the selected files,
  //  - appending other files, or
  //  - replacing with other files
  // before opening a file chooser dialog.
  array<mojo_base.mojom.FilePath> selected_files;

  // A list of valid lower-cased MIME types or file extensions specified
  // in an |accept| attribute of an input element, such as "audio/*",
  // "text/plain", ".mp3" ".txt". It is used to restrict selectable
  // files to such types.
  array<mojo_base.mojom.String16> accept_types;

  // Whether the caller needs native file path or not.
  // This may be false if the callsite can handle |file_system_url| field of
  // resultant FileChooserFileInfo objects.
  bool need_local_path = true;

  // See http://www.w3.org/TR/html-media-capture for more information.
  // If true, the data should be obtained using the device's camera/mic/etc.
  bool use_media_capture = false;

  // If non-empty, represents the URL of the requestor if the request was
  // initiated by a document. Note that this value should be considered
  // untrustworthy since it is specified by the sandbox and not validated.
  url.mojom.Url requestor;
};

struct NativeFileInfo {
  mojo_base.mojom.FilePath file_path;

  // The display name of the file that is to be exposed as File.name in the
  // DOM layer.  If it is empty, the base part of the |file_path| is used.
  mojo_base.mojom.String16 display_name;
};

struct FileSystemFileInfo {
  url.mojom.Url url;
  mojo_base.mojom.Time modification_time;
  int64 length = 0;
};

// Represents a file selected by a user.
union FileChooserFileInfo {
  NativeFileInfo native_file;
  FileSystemFileInfo file_system;
};

// Represents the result of FileChooser::OpenFileChooser() or
// EnumerateChosenDirectory().
struct FileChooserResult {
  // Selected files.
  array<FileChooserFileInfo> files;

  // Requested base directory.
  // This is a user-chosen path in a case of OpenFileChooser() with
  // mode=kUploadFolder.
  // This is |directory_path| in a case of EnumerateChosenDirectory().
  // Otherwise, it's an empty FilePath.
  mojo_base.mojom.FilePath base_directory;
};

// An interface to receive file chooser requests.
interface FileChooser {
  // Ask a user to choose files interactively.  Null |result| is returned if the
  // user canceled a dialog, or the request is not accepted.
  //
  // TODO(crbug.com/1142011): Consider chunking the reply into multiple messages
  // on a separate interface to avoid one very large IPC in some corner cases.
  [UnlimitedSize]
  OpenFileChooser(FileChooserParams params) => (FileChooserResult? result);

  // Ask to enumerate files which are descendants of the specified directory.
  // Null |result| is returned if the enumeration request is not accepted.
  EnumerateChosenDirectory(mojo_base.mojom.FilePath directory_path)
       => (FileChooserResult? result);
};

三、file_chooser.mojom定义实现:

out\Debug\gen\third_party\blink\public\mojom\choosers\file_chooser.mojom.h

out\Debug\gen\third_party\blink\public\mojom\choosers\file_chooser.mojom.cc

1、OpenFileChooser发送:

void FileChooserProxy::OpenFileChooser(
    FileChooserParamsPtr in_params, OpenFileChooserCallback callback) {
#if BUILDFLAG(MOJO_TRACE_ENABLED)
  TRACE_EVENT1(
    "mojom", "Send blink::mojom::FileChooser::OpenFileChooser", "input_parameters",
    [&](perfetto::TracedValue context){
      auto dict = std::move(context).WriteDictionary();
      perfetto::WriteIntoTracedValueWithFallback(
           dict.AddItem("params"), in_params,
                        "<value of type FileChooserParamsPtr>");
   });
#endif

  const bool kExpectsResponse = true;
  const bool kIsSync = false;
  const bool kAllowInterrupt = true;
  const bool is_urgent = false;
  
  const uint32_t kFlags =
      ((kExpectsResponse) ? mojo::Message::kFlagExpectsResponse : 0) |
      ((kIsSync) ? mojo::Message::kFlagIsSync : 0) |
      ((kAllowInterrupt) ? 0 : mojo::Message::kFlagNoInterrupt) |
      ((is_urgent) ? mojo::Message::kFlagIsUrgent : 0);
  
  mojo::Message message(
      internal::kFileChooser_OpenFileChooser_Name, kFlags, 0, 0,
      MOJO_CREATE_MESSAGE_FLAG_UNLIMITED_SIZE, nullptr);
  mojo::internal::MessageFragment<
      ::blink::mojom::internal::FileChooser_OpenFileChooser_Params_Data> params(
          message);
  params.Allocate();
  mojo::internal::MessageFragment<
      typename decltype(params->params)::BaseType> params_fragment(
          params.message());
  mojo::internal::Serialize<::blink::mojom::FileChooserParamsDataView>(
      in_params, params_fragment);
  params->params.Set(
      params_fragment.is_null() ? nullptr : params_fragment.data());
  MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
      params->params.is_null(),
      mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
      "null params in FileChooser.OpenFileChooser request");

#if defined(ENABLE_IPC_FUZZER)
  message.set_interface_name(FileChooser::Name_);
  message.set_method_name("OpenFileChooser");
#endif
  std::unique_ptr<mojo::MessageReceiver> responder(
      new FileChooser_OpenFileChooser_ForwardToCallback(
          std::move(callback)));
  ::mojo::internal::SendMojoMessage(*receiver_, message, std::move(responder));
}

2、OpenFileChooser实现

    case internal::kFileChooser_OpenFileChooser_Name: {

      internal::FileChooser_OpenFileChooser_Params_Data* params =
          reinterpret_cast<
              internal::FileChooser_OpenFileChooser_Params_Data*>(
                  message->mutable_payload());
      
      bool success = true;
      FileChooserParamsPtr p_params{};
      FileChooser_OpenFileChooser_ParamsDataView input_data_view(params, message);
      
      if (success && !input_data_view.ReadParams(&p_params))
        success = false;
      if (!success) {
        ReportValidationErrorForMessage(
            message,
            mojo::internal::VALIDATION_ERROR_DESERIALIZATION_FAILED,
            FileChooser::Name_, 0, false);
        return false;
      }
      FileChooser::OpenFileChooserCallback callback =
          FileChooser_OpenFileChooser_ProxyToResponder::CreateCallback(
              *message, std::move(responder));
      // A null |impl| means no implementation was bound.
      DCHECK(impl);
      impl->OpenFileChooser(
std::move(p_params), std::move(callback));
      return true;
    }

3、FileChooser_OpenFileChooser_ForwardToCallback回调:

bool FileChooser_OpenFileChooser_ForwardToCallback::Accept(
    mojo::Message* message) {

  DCHECK(message->is_serialized());
  internal::FileChooser_OpenFileChooser_ResponseParams_Data* params =
      reinterpret_cast<
          internal::FileChooser_OpenFileChooser_ResponseParams_Data*>(
              message->mutable_payload());
  
  bool success = true;
  FileChooserResultPtr p_result{};
  FileChooser_OpenFileChooser_ResponseParamsDataView input_data_view(params, message);
  
  if (success && !input_data_view.ReadResult(&p_result))
    success = false;
  if (!success) {
    ReportValidationErrorForMessage(
        message,
        mojo::internal::VALIDATION_ERROR_DESERIALIZATION_FAILED,
        FileChooser::Name_, 0, true);
    return false;
  }
  if (!callback_.is_null())
    std::move(callback_).Run(
std::move(p_result));
  return true;
}

四、file_chooser.mojom定义blink实现:

与主进程通信类:

third_party\blink\renderer\core\html\forms\file_chooser.cc

third_party\blink\renderer\core\html\forms\file_chooser.h

FileChooser::OpenFileChooser选择文件函数。

bool FileChooser::OpenFileChooser(ChromeClientImpl& chrome_client_impl) {
  LocalFrame* frame = FrameOrNull();
  if (!frame)
    return false;
  chrome_client_impl_ = chrome_client_impl;
  frame->GetBrowserInterfaceBroker().GetInterface(
      file_chooser_.BindNewPipeAndPassReceiver());
  file_chooser_.set_disconnect_handler(
      WTF::BindOnce(&FileChooser::DidCloseChooser, WTF::Unretained(this)));
  file_chooser_->OpenFileChooser(
      params_.Clone(),
      WTF::BindOnce(&FileChooser::DidChooseFiles, WTF::Unretained(this)));

  // Should be released on file choosing or connection error.
  AddRef();
  chrome_client_impl.RegisterPopupOpeningObserver(client_);
  return true;
}

<input type="file">对应c++代码实现类如下:

third_party\blink\renderer\core\html\forms\file_input_type.cc

third_party\blink\renderer\core\html\forms\file_input_type.h

FileInputType::HandleDOMActivateEvent(Event& event) //鼠标点击事件

FileInputType::OpenPopupView() 

void FileInputType::HandleDOMActivateEvent(Event& event) {
  if (GetElement().IsDisabledFormControl())
    return;

  HTMLInputElement& input = GetElement();
  Document& document = input.GetDocument();

  if (!LocalFrame::HasTransientUserActivation(document.GetFrame())) {
    String message =
        "File chooser dialog can only be shown with a user activation.";
    document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
        mojom::ConsoleMessageSource::kJavaScript,
        mojom::ConsoleMessageLevel::kWarning, message));
    return;
  }

  OpenPopupView();
  event.SetDefaultHandled();
}

void FileInputType::OpenPopupView() {
  HTMLInputElement& input = GetElement();
  Document& document = input.GetDocument();

  bool intercepted = false;
  probe::FileChooserOpened(document.GetFrame(), &input, input.Multiple(),
                           &intercepted);
  if (intercepted) {
    return;
  }

  if (ChromeClient* chrome_client = GetChromeClient()) {
    FileChooserParams params;
    bool is_directory =
        input.FastHasAttribute(html_names::kWebkitdirectoryAttr);
    if (is_directory)
      params.mode = FileChooserParams::Mode::kUploadFolder;
    else if (input.FastHasAttribute(html_names::kMultipleAttr))
      params.mode = FileChooserParams::Mode::kOpenMultiple;
    else
      params.mode = FileChooserParams::Mode::kOpen;
    params.title = g_empty_string;
    params.need_local_path = is_directory;
    params.accept_types = CollectAcceptTypes(input);
    params.selected_files = file_list_->PathsForUserVisibleFiles();
    params.use_media_capture = RuntimeEnabledFeatures::MediaCaptureEnabled() &&
                               input.FastHasAttribute(html_names::kCaptureAttr);
    params.requestor = document.Url();

    UseCounter::Count(
        document, GetElement().GetExecutionContext()->IsSecureContext()
                      ? WebFeature::kInputTypeFileSecureOriginOpenChooser
                      : WebFeature::kInputTypeFileInsecureOriginOpenChooser);
    chrome_client->OpenFileChooser(document.GetFrame(), NewFileChooser(params));
  }
}

五、file_chooser.mojom定义browser实现:

content\browser\web_contents\file_chooser_impl.cc

content\browser\web_contents\file_chooser_impl.h

void FileChooserImpl::OpenFileChooser(blink::mojom::FileChooserParamsPtr params,
                                      OpenFileChooserCallback callback) {
  if (listener_impl_ || !render_frame_host()) {
    std::move(callback).Run(nullptr);
    return;
  }
  callback_ = std::move(callback);
  auto listener = base::MakeRefCounted<FileSelectListenerImpl>(this);
  listener_impl_ = listener.get();
  // Do not allow messages with absolute paths in them as this can permit a
  // renderer to coerce the browser to perform I/O on a renderer controlled
  // path.
  if (params->default_file_name != params->default_file_name.BaseName()) {
    mojo::ReportBadMessage(
        "FileChooser: The default file name must not be an absolute path.");
    listener->FileSelectionCanceled();
    return;
  }

  // Don't allow page with open FileChooser to enter BackForwardCache to avoid
  // any unexpected behaviour from BackForwardCache.
  BackForwardCache::DisableForRenderFrameHost(
      render_frame_host(),
      BackForwardCacheDisable::DisabledReason(
          BackForwardCacheDisable::DisabledReasonId::kFileChooser));

  WebContentsImpl::FromRenderFrameHostImpl(render_frame_host())
      ->RunFileChooser(GetWeakPtr(), render_frame_host(), std::move(listener),
                       *params);
}

六、调用堆栈:

1、在页面点击<input type="file">控件:

2、FileInputType::HandleDOMActivateEvent(Event& event) //鼠标点击事件

3、FileInputType::OpenPopupView()  //发送选择文件框命令

4、ChromeClientImpl::OpenFileChooser

src\third_party\blink\renderer\core\page\chrome_client_impl.cc

src\third_party\blink\renderer\core\page\chrome_client_impl.h

5、FileChooser::OpenFileChooser:

src\third_party\blink\renderer\core\html\forms\file_chooser.cc

src\third_party\blink\renderer\core\html\forms\file_chooser.h

6、FileChooserProxy::OpenFileChooser

//mojom消息主进程通信file_chooser_impl.cc

7、主进程收到OpenFileChooser命令:

      impl->OpenFileChooser(

std::move(p_params), std::move(callback));

8、主进程响应mojom FileChooserImpl::OpenFileChooser:

9、主进程将选择结果返回给render进程:

    FileChooserImpl::FileSelected执行OpenFileChooserCallback回调

std::move(callback_).Run(FileChooserResult::New(std::move(files), base_dir));

10、render进程收到OpenFileChooserCallback回调:

FileChooser_OpenFileChooser_ForwardToCallback::Accept

11、render进程FileChooser::DidChooseFiles收到结果:

  此段代码是解析主进程发送过来的数据的

  FileChooserResultPtr p_result{};
  FileChooser_OpenFileChooser_ResponseParamsDataView input_data_view(params, message);
  if (success && !input_data_view.ReadResult(&p_result))

选择的2.png存储在p_result中:

12、最终前端展示选择结果:

总结:至此input type="file"选择文件流程分析完毕。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐