一、<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中: