一、本文以一个简单的 HTML 表单,包含两个文本输入框和一个提交按钮:
<form action="demo_form.php">
First name: <input type="text" name="fname"><br>
Last name: <input type="text" name="lname"><br>
<input type="submit" value="提交">
</form>
测试时候可以打开菜鸟教程在线编辑器 (runoob.com)运行以上代码
二、看下c++form表单接口定义:
2.1)、form接口定义文件:
third_party\blink\renderer\core\html\forms\html_form_element.idl
method="get|post|dialog"
// https://html.spec.whatwg.org/C/#the-form-element
[
Exposed=Window,
HTMLConstructor,
LegacyOverrideBuiltIns
] interface HTMLFormElement : HTMLElement {
[CEReactions, Reflect=accept_charset] attribute DOMString acceptCharset;
[CEReactions, URL] attribute USVString action;
[CEReactions, Reflect, ReflectOnly=("on","off"), ReflectMissing="on", ReflectInvalid="on"] attribute DOMString autocomplete;
[CEReactions] attribute DOMString enctype;
[CEReactions] attribute DOMString encoding;
[CEReactions] attribute DOMString method;
[CEReactions, Reflect] attribute DOMString name;
[CEReactions, Reflect] attribute boolean noValidate;
[CEReactions, Reflect] attribute DOMString target;
[CEReactions, Reflect] attribute DOMString rel;
[SameObject, PutForwards=value] readonly attribute DOMTokenList relList;
readonly attribute HTMLFormControlsCollection elements;
readonly attribute long length;
[ImplementedAs=item] getter Element (unsigned long index);
// FIXME: This getter should not have [NotEnumerable].
[NotEnumerable] getter (RadioNodeList or Element) (DOMString name);
[ImplementedAs=submitFromJavaScript] void submit();
[RaisesException] void requestSubmit(optional HTMLElement? submitter = null);
[CEReactions] void reset();
boolean checkValidity();
boolean reportValidity();
};
2.2)、blink下form接口实现:
third_party\blink\renderer\core\html\forms\html_form_element.h
third_party\blink\renderer\core\html\forms\html_form_element.cc
用于查找和遍历form元素
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_ELEMENT_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_ELEMENT_H_
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h"
#include "third_party/blink/renderer/core/html/forms/radio_button_group_scope.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/loader/form_submission.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
namespace blink {
class DOMTokenList;
class Event;
class HTMLFormControlElement;
class HTMLFormControlsCollection;
class HTMLImageElement;
class ListedElement;
class RelList;
class V8UnionElementOrRadioNodeList;
class CORE_EXPORT HTMLFormElement final : public HTMLElement {
DEFINE_WRAPPERTYPEINFO();
public:
enum RelAttribute {
kNone = 0,
kNoReferrer = 1 << 0,
kNoOpener = 1 << 1,
kOpener = 1 << 2,
};
explicit HTMLFormElement(Document&);
~HTMLFormElement() override;
void Trace(Visitor*) const override;
HTMLFormControlsCollection* elements();
void GetNamedElements(const AtomicString&, HeapVector<Member<Element>>&);
unsigned length() const;
HTMLElement* item(unsigned index);
String action() const;
void setAction(const AtomicString&);
String enctype() const { return attributes_.EncodingType(); }
void setEnctype(const AtomicString&);
String encoding() const { return attributes_.EncodingType(); }
void setEncoding(const AtomicString& value) { setEnctype(value); }
DOMTokenList& relList() const;
bool HasRel(RelAttribute relation) const;
bool ShouldAutocomplete() const;
void Associate(ListedElement&);
void Disassociate(ListedElement&);
void Associate(HTMLImageElement&);
void Disassociate(HTMLImageElement&);
void DidAssociateByParser();
void PrepareForSubmission(const Event*,
HTMLFormControlElement* submit_button);
void submitFromJavaScript();
void requestSubmit(ExceptionState& exception_state);
void requestSubmit(HTMLElement* submitter, ExceptionState& exception_state);
void reset();
void AttachLayoutTree(AttachContext& context) override;
void DetachLayoutTree(bool performing_reattach) override;
void SubmitImplicitly(const Event&, bool from_implicit_submission_trigger);
String GetName() const;
bool NoValidate() const;
const AtomicString& Action() const;
String method() const;
void setMethod(const AtomicString&);
FormSubmission::SubmitMethod Method() const { return attributes_.Method(); }
// Find the 'default button.'
// https://html.spec.whatwg.org/C/#default-button
HTMLFormControlElement* FindDefaultButton() const;
bool checkValidity();
bool reportValidity();
bool MatchesValidityPseudoClasses() const final;
bool IsValidElement() final;
RadioButtonGroupScope& GetRadioButtonGroupScope() {
return radio_button_group_scope_;
}
const ListedElement::List& ListedElements(
bool include_shadow_trees = false) const;
const HeapVector<Member<HTMLImageElement>>& ImageElements();
V8UnionElementOrRadioNodeList* AnonymousNamedGetter(const AtomicString& name);
void InvalidateDefaultButtonStyle() const;
// 'construct the entry list'
// https://html.spec.whatwg.org/C/#constructing-the-form-data-set
// Returns nullptr if this form is already running this function.
FormData* ConstructEntryList(HTMLFormControlElement* submit_button,
const WTF::TextEncoding& encoding);
uint64_t UniqueRendererFormId() const { return unique_renderer_form_id_; }
void InvalidateListedElementsIncludingShadowTrees();
private:
InsertionNotificationRequest InsertedInto(ContainerNode&) override;
void RemovedFrom(ContainerNode&) override;
void FinishParsingChildren() override;
void HandleLocalEvents(Event&) override;
void ParseAttribute(const AttributeModificationParams&) override;
bool IsURLAttribute(const Attribute&) const override;
bool HasLegalLinkAttribute(const QualifiedName&) const override;
NamedItemType GetNamedItemType() const override {
return NamedItemType::kName;
}
void SubmitDialog(FormSubmission*);
void ScheduleFormSubmission(const Event*,
HTMLFormControlElement* submit_button);
void CollectListedElements(
const Node& root,
ListedElement::List& elements,
ListedElement::List* elements_including_shadow_trees = nullptr,
bool in_shadow_tree = false) const;
void CollectImageElements(Node& root, HeapVector<Member<HTMLImageElement>>&);
// Returns true if the submission should proceed.
bool ValidateInteractively();
// Validates each of the controls, and stores controls of which 'invalid'
// event was not canceled to the specified vector. Returns true if there
// are any invalid controls in this form.
bool CheckInvalidControlsAndCollectUnhandled(ListedElement::List*);
Element* ElementFromPastNamesMap(const AtomicString&);
void AddToPastNamesMap(Element*, const AtomicString& past_name);
void RemoveFromPastNamesMap(HTMLElement&);
typedef HeapHashMap<AtomicString, Member<Element>> PastNamesMap;
FormSubmission::Attributes attributes_;
Member<PastNamesMap> past_names_map_;
RadioButtonGroupScope radio_button_group_scope_;
// Do not access listed_elements_ directly. Use ListedElements() instead.
ListedElement::List listed_elements_;
// Do not access listed_elements_including_shadow_trees_ directly. Use
// ListedElements(true) instead.
ListedElement::List listed_elements_including_shadow_trees_;
// Do not access image_elements_ directly. Use ImageElements() instead.
HeapVector<Member<HTMLImageElement>> image_elements_;
uint64_t unique_renderer_form_id_;
base::OnceClosure cancel_last_submission_;
bool is_submitting_ = false;
bool in_user_js_submit_event_ = false;
bool is_constructing_entry_list_ = false;
bool listed_elements_are_dirty_ : 1;
bool listed_elements_including_shadow_trees_are_dirty_ : 1;
bool image_elements_are_dirty_ : 1;
bool has_elements_associated_by_parser_ : 1;
bool has_elements_associated_by_form_attribute_ : 1;
bool did_finish_parsing_children_ : 1;
bool is_in_reset_function_ : 1;
Member<RelList> rel_list_;
unsigned rel_attribute_ = 0;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_ELEMENT_H_
2.3)、v8下form接口实现:
out\Debug\gen\third_party\blink\renderer\bindings\core\v8\v8_html_form_element.h
out\Debug\gen\third_party\blink\renderer\bindings\core\v8\v8_html_form_element.cc
截取部分定义:
void MethodAttributeGetCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
RUNTIME_CALL_TIMER_SCOPE_DISABLED_BY_DEFAULT(info.GetIsolate(), "Blink_HTMLFormElement_method_Getter");
BLINK_BINDINGS_TRACE_EVENT("HTMLFormElement.method.get");
v8::Isolate* isolate = info.GetIsolate();
v8::Local<v8::Object> v8_receiver = info.This();
HTMLFormElement* blink_receiver = V8HTMLFormElement::ToWrappableUnsafe(isolate, v8_receiver);
auto&& return_value = blink_receiver->method();
bindings::V8SetReturnValue(info, return_value, isolate, bindings::V8ReturnValue::kNonNullable);
}
void MethodAttributeSetCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
RUNTIME_CALL_TIMER_SCOPE_DISABLED_BY_DEFAULT(info.GetIsolate(), "Blink_HTMLFormElement_method_Setter");
BLINK_BINDINGS_TRACE_EVENT("HTMLFormElement.method.set");
v8::Isolate* isolate = info.GetIsolate();
const ExceptionContextType exception_context_type = ExceptionContextType::kAttributeSet;
const char* const class_like_name = "HTMLFormElement";
const char* const property_name = "method";
ExceptionState exception_state(isolate, exception_context_type, class_like_name, property_name);
// [CEReactions]
CEReactionsScope ce_reactions_scope;
v8::Local<v8::Object> v8_receiver = info.This();
HTMLFormElement* blink_receiver = V8HTMLFormElement::ToWrappableUnsafe(isolate, v8_receiver);
v8::Local<v8::Value> v8_property_value = info[0];
auto&& arg1_value = NativeValueTraits<IDLString>::NativeValue(isolate, v8_property_value, exception_state);
if (UNLIKELY(exception_state.HadException())) {
return;
}
blink_receiver->setMethod(arg1_value);
}
2.4)、form_datat数据填充接口:
FormData用来构建FormSubmission对象主要存储form表单内的元素参数:
例如:
First name: <input type="text" name="fname"><br>
Last name: <input type="text" name="lname"><br>
会转换成FirstName=Mickey&LastName=Mouse,最后放到url请求参数里面。
结果:
https://www.runoob.com/try/demo_source/demo-form.php?FirstName=Mickey&LastName=Mouse
hird_party\blink\renderer\core\html\forms\form_data.idl
// https://xhr.spec.whatwg.org/#interface-formdata
typedef (File or USVString) FormDataEntryValue;
[
Exposed=(Window,Worker)
] interface FormData {
[RaisesException] constructor(optional HTMLFormElement form, optional HTMLElement? submitter = null);
void append(USVString name, USVString value);
[CallWith=ScriptState] void append(USVString name, Blob value, optional USVString filename);
[ImplementedAs=deleteEntry] void delete(USVString name);
FormDataEntryValue? get(USVString name);
sequence<FormDataEntryValue> getAll(USVString name);
boolean has(USVString name);
void set(USVString name, USVString value);
void set(USVString name, Blob value, optional USVString filename);
iterable<USVString, FormDataEntryValue>;
};
blink和v8定义:
third_party\blink\renderer\core\html\forms\form_data.h
third_party\blink\renderer\core\html\forms\form_data.cc
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_DATA_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_DATA_H_
#include "third_party/blink/renderer/bindings/core/v8/iterable.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_sync_iterator_form_data.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_typedefs.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/network/encoded_form_data.h"
#include "third_party/blink/renderer/platform/wtf/forward.h"
#include "third_party/blink/renderer/platform/wtf/text/text_encoding.h"
namespace blink {
class Blob;
class File;
class FormControlState;
class HTMLFormElement;
class ScriptState;
class ExecutionContext;
class CORE_EXPORT FormData final : public ScriptWrappable,
public PairSyncIterable<FormData> {
DEFINE_WRAPPERTYPEINFO();
public:
static FormData* Create(ExceptionState& exception_state) {
return MakeGarbageCollected<FormData>();
}
static FormData* Create(HTMLFormElement* form,
ExceptionState& exception_state);
static FormData* Create(HTMLFormElement* form,
HTMLElement* submitter,
ExceptionState& exception_state);
explicit FormData(const WTF::TextEncoding&);
// Clones form_data. This clones |form_data.entries_| Vector, but
// doesn't clone entries in it because they are immutable.
FormData(const FormData& form_data);
FormData();
void Trace(Visitor*) const override;
// FormData IDL interface.
void append(const String& name, const String& value);
void append(ScriptState*,
const String& name,
Blob*,
const String& filename = String());
void deleteEntry(const String& name);
V8FormDataEntryValue* get(const String& name);
HeapVector<Member<V8FormDataEntryValue>> getAll(const String& name);
bool has(const String& name);
void set(const String& name, const String& value);
void set(const String& name, Blob*, const String& filename = String());
// Internal functions.
const WTF::TextEncoding& Encoding() const { return encoding_; }
std::string Encode(const String& key) const;
class Entry;
const HeapVector<Member<const Entry>>& Entries() const { return entries_; }
size_t size() const { return entries_.size(); }
void append(const String& name, Blob*, const String& filename = String());
void AppendFromElement(const String& name, int value);
void AppendFromElement(const String& name, File* file);
void AppendFromElement(const String& name, const String& value);
// This flag is true if this FormData is created with a <form>, and its
// associated elements contain a non-empty password field.
bool ContainsPasswordData() const { return contains_password_data_; }
void SetContainsPasswordData(bool flag) { contains_password_data_ = flag; }
scoped_refptr<EncodedFormData> EncodeFormData(
EncodedFormData::EncodingType = EncodedFormData::kFormURLEncoded);
scoped_refptr<EncodedFormData> EncodeMultiPartFormData();
void AppendToControlState(FormControlState& state) const;
static FormData* CreateFromControlState(ExecutionContext& execution_context,
const FormControlState& state,
wtf_size_t& index);
private:
void SetEntry(const Entry*);
IterationSource* CreateIterationSource(ScriptState*,
ExceptionState&) override;
WTF::TextEncoding encoding_;
// Entry pointers in entries_ never be nullptr.
HeapVector<Member<const Entry>> entries_;
bool contains_password_data_ = false;
};
// Represents entry, which is a pair of a name and a value.
// https://xhr.spec.whatwg.org/#concept-formdata-entry
// Entry objects are immutable.
class FormData::Entry final : public GarbageCollected<FormData::Entry> {
public:
Entry(const String& name, const String& value);
Entry(const String& name, Blob* blob, const String& filename);
void Trace(Visitor*) const;
bool IsString() const { return !blob_; }
bool isFile() const { return blob_ != nullptr; }
const String& name() const { return name_; }
const String& Value() const { return value_; }
Blob* GetBlob() const { return blob_.Get(); }
CORE_EXPORT File* GetFile() const;
const String& Filename() const { return filename_; }
private:
const String name_;
const String value_;
const Member<Blob> blob_;
const String filename_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_DATA_H_
out\Debug\gen\third_party\blink\renderer\bindings\core\v8\v8_form_data.h
out\Debug\gen\third_party\blink\renderer\bindings\core\v8\v8_form_data.cc
截图部分实现:
void KeysOperationCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
RUNTIME_CALL_TIMER_SCOPE_DISABLED_BY_DEFAULT(info.GetIsolate(), "Blink_FormData_keys");
BLINK_BINDINGS_TRACE_EVENT("FormData.keys");
v8::Isolate* isolate = info.GetIsolate();
v8::Local<v8::Object> v8_receiver = info.This();
FormData* blink_receiver = V8FormData::ToWrappableUnsafe(isolate, v8_receiver);
v8::Local<v8::Context> receiver_context = v8_receiver->GetCreationContextChecked();
ScriptState* receiver_script_state = ScriptState::From(receiver_context);
ScriptState* script_state = receiver_script_state;
const ExceptionContextType exception_context_type = ExceptionContextType::kOperationInvoke;
const char* const class_like_name = "FormData";
const char* const property_name = "keys";
ExceptionState exception_state(isolate, exception_context_type, class_like_name, property_name);
auto&& return_value = blink_receiver->keysForBinding(script_state, exception_state);
if (UNLIKELY(exception_state.HadException())) {
return;
}
bindings::V8SetReturnValue(info, return_value, blink_receiver);
}
void ValuesOperationCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
RUNTIME_CALL_TIMER_SCOPE_DISABLED_BY_DEFAULT(info.GetIsolate(), "Blink_FormData_values");
BLINK_BINDINGS_TRACE_EVENT("FormData.values");
v8::Isolate* isolate = info.GetIsolate();
v8::Local<v8::Object> v8_receiver = info.This();
FormData* blink_receiver = V8FormData::ToWrappableUnsafe(isolate, v8_receiver);
v8::Local<v8::Context> receiver_context = v8_receiver->GetCreationContextChecked();
ScriptState* receiver_script_state = ScriptState::From(receiver_context);
ScriptState* script_state = receiver_script_state;
const ExceptionContextType exception_context_type = ExceptionContextType::kOperationInvoke;
const char* const class_like_name = "FormData";
const char* const property_name = "values";
ExceptionState exception_state(isolate, exception_context_type, class_like_name, property_name);
auto&& return_value = blink_receiver->valuesForBinding(script_state, exception_state);
if (UNLIKELY(exception_state.HadException())) {
return;
}
bindings::V8SetReturnValue(info, return_value, blink_receiver);
}
2.5)、form submit 管理类FormSubmission
网页点击submit会调用到FormSubmission。
third_party\blink\renderer\core\loader\form_submission.h
third_party\blink\renderer\core\loader\form_submission.cc
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_FORM_SUBMISSION_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_FORM_SUBMISSION_H_
#include "third_party/blink/public/common/tokens/tokens.h"
#include "third_party/blink/public/mojom/frame/policy_container.mojom-blink.h"
#include "third_party/blink/public/mojom/frame/triggering_event_info.mojom-blink-forward.h"
#include "third_party/blink/public/web/web_frame_load_type.h"
#include "third_party/blink/renderer/core/loader/frame_loader_types.h"
#include "third_party/blink/renderer/core/loader/navigation_policy.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/heap/member.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
namespace blink {
class Element;
class EncodedFormData;
class Event;
class Frame;
class HTMLFormControlElement;
class HTMLFormElement;
class LocalDOMWindow;
class ResourceRequest;
class SourceLocation;
class FormSubmission final : public GarbageCollected<FormSubmission> {
public:
enum SubmitMethod { kGetMethod, kPostMethod, kDialogMethod };
class Attributes {
DISALLOW_NEW();
public:
Attributes()
: method_(kGetMethod),
is_multi_part_form_(false),
encoding_type_("application/x-www-form-urlencoded") {}
Attributes(const Attributes&) = delete;
Attributes& operator=(const Attributes&) = delete;
SubmitMethod Method() const { return method_; }
static SubmitMethod ParseMethodType(const String&);
void UpdateMethodType(const String&);
static String MethodString(SubmitMethod);
const String& Action() const { return action_; }
void ParseAction(const String&);
const AtomicString& Target() const { return target_; }
void SetTarget(const AtomicString& target) { target_ = target; }
const AtomicString& EncodingType() const { return encoding_type_; }
static AtomicString ParseEncodingType(const String&);
void UpdateEncodingType(const String&);
bool IsMultiPartForm() const { return is_multi_part_form_; }
const String& AcceptCharset() const { return accept_charset_; }
void SetAcceptCharset(const String& value) { accept_charset_ = value; }
void CopyFrom(const Attributes&);
private:
SubmitMethod method_;
bool is_multi_part_form_;
String action_;
AtomicString target_;
AtomicString encoding_type_;
String accept_charset_;
};
// Create FormSubmission
//
// This returns nullptr if form submission is not allowed for the given
// arguments. For example, if navigation policy for the event is
// `kNavigationPolicyLinkPreview`.
static FormSubmission* Create(HTMLFormElement*,
const Attributes&,
const Event*,
HTMLFormControlElement* submit_button);
FormSubmission(
SubmitMethod,
const KURL& action,
const AtomicString& target,
const AtomicString& content_type,
Element* submitter,
scoped_refptr<EncodedFormData>,
const Event*,
NavigationPolicy navigation_policy,
mojom::blink::TriggeringEventInfo triggering_event_info,
ClientNavigationReason reason,
std::unique_ptr<ResourceRequest> resource_request,
Frame* target_frame,
WebFrameLoadType load_type,
LocalDOMWindow* origin_window,
const LocalFrameToken& initiator_frame_token,
std::unique_ptr<SourceLocation> source_location,
mojo::PendingRemote<mojom::blink::PolicyContainerHostKeepAliveHandle>
initiator_policy_container_keep_alive_handle);
// FormSubmission for DialogMethod
explicit FormSubmission(const String& result);
void Trace(Visitor*) const;
void Navigate();
KURL RequestURL() const;
SubmitMethod Method() const { return method_; }
const KURL& Action() const { return action_; }
EncodedFormData* Data() const { return form_data_.get(); }
const String& Result() const { return result_; }
Frame* TargetFrame() const { return target_frame_.Get(); }
private:
// FIXME: Hold an instance of Attributes instead of individual members.
SubmitMethod method_;
KURL action_;
AtomicString target_;
AtomicString content_type_;
Member<Element> submitter_;
scoped_refptr<EncodedFormData> form_data_;
NavigationPolicy navigation_policy_;
mojom::blink::TriggeringEventInfo triggering_event_info_;
String result_;
ClientNavigationReason reason_;
std::unique_ptr<ResourceRequest> resource_request_;
Member<Frame> target_frame_;
WebFrameLoadType load_type_;
Member<LocalDOMWindow> origin_window_;
LocalFrameToken initiator_frame_token_;
// Since form submissions are scheduled asynchronously, we need to store the
// source location when we create the form submission and then pass it over to
// the `FrameLoadRequest`. Capturing the source location later when creating
// the `FrameLoadRequest` will not return the correct location.
std::unique_ptr<SourceLocation> source_location_;
// Since form submissions are scheduled asynchronously, we need to keep a
// handle to the initiator PolicyContainerHost. This ensures that it remains
// available in the browser until we create the NavigationRequest.
mojo::PendingRemote<mojom::blink::PolicyContainerHostKeepAliveHandle>
initiator_policy_container_keep_alive_handle_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_FORM_SUBMISSION_H_
2.6)、submit_input类对应前端<input type="submit":
third_party\blink\renderer\core\html\forms\submit_input_type.h
third_party\blink\renderer\core\html\forms\submit_input_type.cc
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SUBMIT_INPUT_TYPE_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SUBMIT_INPUT_TYPE_H_
#include "third_party/blink/renderer/core/html/forms/base_button_input_type.h"
namespace blink {
class SubmitInputType final : public BaseButtonInputType {
public:
explicit SubmitInputType(HTMLInputElement& element);
private:
void AppendToFormData(FormData&) const override;
bool SupportsRequired() const override;
void HandleDOMActivateEvent(Event&) override;
bool CanBeSuccessfulSubmitButton() override;
String DefaultLabel() const override;
bool IsTextButton() const override;
void ValueAttributeChanged() override;
};
template <>
struct DowncastTraits<SubmitInputType> {
static bool AllowFrom(const InputType& type) {
return type.IsSubmitInputType();
}
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SUBMIT_INPUT_TYPE_H_
三、看下form表单提交过程:
form表单提交过程:
1、render进程点击提交<input type="submit" value="提交">
2、render进程在HTMLFormElement类中构建 FormSubmission并解析<input>内容、从而构建出请求参数:FirstName=Mickey&LastName=Mouse 以及
获取method="get"
/*
First name: <input type="text" name="FirstName" value="Mickey"><br>
Last name: <input type="text" name="LastName" value="Mouse"><br>
*/
最后拼接出请求URL
https://www.runoob.com/try/demo_source/demo-form.php?FirstName=Mickey&LastName=Mouse
3、给主进程发送message.set_method_name("CreateNewWindow");
在新标签中打开https://www.runoob.com/try/demo_source/demo-form.php?FirstName=Mickey&LastName=Mouse。
============================优雅的分割线===================================
1、render进程点击提交<input type="submit" value="提交">
SubmitInputType::HandleDOMActivateEvent(Event& event)
void SubmitInputType::HandleDOMActivateEvent(Event& event) {
if (GetElement().IsDisabledFormControl() || !GetElement().Form())
return;
// Event handlers can run.
GetElement().Form()->PrepareForSubmission(&event, &GetElement());
event.SetDefaultHandled();
}
2、HTMLFormElement::ScheduleFormSubmission 函数
调用 FormSubmission* form_submission =
FormSubmission::Create(this, attributes_, event, submit_button);
void HTMLFormElement::ScheduleFormSubmission(
const Event* event,
HTMLFormControlElement* submit_button) {
LocalFrameView* view = GetDocument().View();
LocalFrame* frame = GetDocument().GetFrame();
if (!view || !frame || !frame->GetPage())
return;
// https://html.spec.whatwg.org/C/#form-submission-algorithm
// 2. If form document is not connected, has no associated browsing context,
// or its active sandboxing flag set has its sandboxed forms browsing
// context flag set, then abort these steps without doing anything.
if (!isConnected()) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kJavaScript,
mojom::ConsoleMessageLevel::kWarning,
"Form submission canceled because the form is not connected"));
return;
}
if (is_constructing_entry_list_) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kJavaScript,
mojom::ConsoleMessageLevel::kWarning,
"Form submission canceled because the form is "
"constructing entry list"));
return;
}
if (is_submitting_)
return;
// Delay dispatching 'close' to dialog until done submitting.
EventQueueScope scope_for_dialog_close;
base::AutoReset<bool> submit_scope(&is_submitting_, true);
if (event && !submit_button) {
// In a case of implicit submission without a submit button, 'submit'
// event handler might add a submit button. We search for a submit
// button again.
// TODO(tkent): Do we really need to activate such submit button?
for (ListedElement* listed_element : ListedElements()) {
auto* control = DynamicTo<HTMLFormControlElement>(listed_element);
if (!control)
continue;
DCHECK(!control->IsActivatedSubmit());
if (control->IsSuccessfulSubmitButton()) {
submit_button = control;
break;
}
}
}
FormSubmission* form_submission =
FormSubmission::Create(this, attributes_, event, submit_button);
if (!form_submission) {
// Form submission is not allowed for some NavigationPolicies, e.g. Link
// Preview. If an user triggered such user event for form submission, just
// ignores it.
return;
}
Frame* target_frame = form_submission->TargetFrame();
// 'formdata' event handlers might disconnect the form.
if (!isConnected()) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kJavaScript,
mojom::ConsoleMessageLevel::kWarning,
"Form submission canceled because the form is not connected"));
return;
}
if (form_submission->Method() == FormSubmission::kDialogMethod) {
SubmitDialog(form_submission);
return;
}
DCHECK(form_submission->Method() == FormSubmission::kPostMethod ||
form_submission->Method() == FormSubmission::kGetMethod);
DCHECK(form_submission->Data());
if (form_submission->Action().IsEmpty())
return;
if (GetExecutionContext()->IsSandboxed(
network::mojom::blink::WebSandboxFlags::kForms)) {
// FIXME: This message should be moved off the console once a solution to
// https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
GetExecutionContext()->AddConsoleMessage(
MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kSecurity,
mojom::blink::ConsoleMessageLevel::kError,
"Blocked form submission to '" +
form_submission->Action().ElidedString() +
"' because the form's frame is sandboxed and the 'allow-forms' "
"permission is not set."));
return;
}
if (form_submission->Action().ProtocolIsJavaScript()) {
// For javascript URLs we need to do the CSP check for 'form-action' here.
// All other schemes are checked in the browser.
//
// TODO(antoniosartori): Should we keep the 'form-action' check for
// javascript: URLs? For 'frame-src' and 'navigate-to', we do not check
// javascript: URLs. Reading the specification, it looks like 'form-action'
// should not apply to javascript: URLs.
if (!GetExecutionContext()->GetContentSecurityPolicy()->AllowFormAction(
form_submission->Action())) {
return;
}
}
UseCounter::Count(GetDocument(), WebFeature::kFormsSubmitted);
if (MixedContentChecker::IsMixedFormAction(GetDocument().GetFrame(),
form_submission->Action())) {
UseCounter::Count(GetDocument(), WebFeature::kMixedContentFormsSubmitted);
}
if (FastHasAttribute(html_names::kDisabledAttr)) {
UseCounter::Count(GetDocument(),
WebFeature::kFormDisabledAttributePresentAndSubmit);
}
if (!target_frame)
return;
if (form_submission->Action().ProtocolIsJavaScript()) {
// For javascript urls, don't post a task to execute the form submission
// because we already get another task posted for it in
// Document::ProcessJavascriptUrl. If we post two tasks, the javascript will
// be run too late according to some tests.
form_submission->Navigate();
return;
}
FrameScheduler* scheduler = GetDocument().GetFrame()->GetFrameScheduler();
if (auto* target_local_frame = DynamicTo<LocalFrame>(target_frame)) {
if (!target_local_frame->IsNavigationAllowed())
return;
// Cancel parsing if the form submission is targeted at this frame.
if (target_local_frame == GetDocument().GetFrame() &&
!form_submission->Action().ProtocolIsJavaScript()) {
target_local_frame->GetDocument()->CancelParsing();
}
// Use the target frame's frame scheduler. If we can't due to targeting a
// RemoteFrame, then use the frame scheduler from the frame this form is in.
scheduler = target_local_frame->GetFrameScheduler();
// Cancel pending javascript url navigations for the target frame. This new
// form submission should take precedence over them.
target_local_frame->GetDocument()->CancelPendingJavaScriptUrls();
// Cancel any pre-existing attempt to navigate the target frame which was
// already sent to the browser process so this form submission will take
// precedence over it.
target_local_frame->Loader().CancelClientNavigation();
}
cancel_last_submission_ =
target_frame->ScheduleFormSubmission(scheduler, form_submission);
}
3、FormSubmission::Create()函数构建FirstName=Mickey&LastName=Mouse,以及打开提交页面方式:
FormSubmission* FormSubmission::Create(HTMLFormElement* form,
const Attributes& attributes,
const Event* event,
HTMLFormControlElement* submit_button) {
DCHECK(form);
FormSubmission::Attributes copied_attributes;
copied_attributes.CopyFrom(attributes);
if (submit_button) {
AtomicString attribute_value;
if (!(attribute_value =
submit_button->FastGetAttribute(html_names::kFormactionAttr))
.IsNull())
copied_attributes.ParseAction(attribute_value);
if (!(attribute_value =
submit_button->FastGetAttribute(html_names::kFormenctypeAttr))
.IsNull())
copied_attributes.UpdateEncodingType(attribute_value);
if (!(attribute_value =
submit_button->FastGetAttribute(html_names::kFormmethodAttr))
.IsNull())
copied_attributes.UpdateMethodType(attribute_value);
if (!(attribute_value =
submit_button->FastGetAttribute(html_names::kFormtargetAttr))
.IsNull())
copied_attributes.SetTarget(attribute_value);
}
if (copied_attributes.Method() == kDialogMethod) {
if (submit_button) {
return MakeGarbageCollected<FormSubmission>(
submit_button->ResultForDialogSubmit());
}
return MakeGarbageCollected<FormSubmission>("");
}
Document& document = form->GetDocument();
KURL action_url = document.CompleteURL(copied_attributes.Action().empty()
? document.Url().GetString()
: copied_attributes.Action());
if ((document.domWindow()->GetSecurityContext().GetInsecureRequestPolicy() &
mojom::blink::InsecureRequestPolicy::kUpgradeInsecureRequests) !=
mojom::blink::InsecureRequestPolicy::kLeaveInsecureRequestsAlone &&
action_url.ProtocolIs("http") &&
!network::IsUrlPotentiallyTrustworthy(GURL(action_url))) {
UseCounter::Count(document,
WebFeature::kUpgradeInsecureRequestsUpgradedRequestForm);
action_url.SetProtocol("https");
if (action_url.Port() == 80)
action_url.SetPort(443);
}
bool is_mailto_form = action_url.ProtocolIs("mailto");
bool is_multi_part_form = false;
AtomicString encoding_type = copied_attributes.EncodingType();
if (copied_attributes.Method() == kPostMethod) {
is_multi_part_form = copied_attributes.IsMultiPartForm();
if (is_multi_part_form && is_mailto_form) {
encoding_type = AtomicString("application/x-www-form-urlencoded");
is_multi_part_form = false;
}
}
WTF::TextEncoding data_encoding =
is_mailto_form
? UTF8Encoding()
: FormDataEncoder::EncodingFromAcceptCharset(
copied_attributes.AcceptCharset(), document.Encoding());
//解析<input>构建请求参数FirstName=Mickey&LastName=Mouse
FormData* dom_form_data = form->ConstructEntryList(
submit_button, data_encoding.EncodingForFormSubmission());
DCHECK(dom_form_data);
scoped_refptr<EncodedFormData> form_data;
String boundary;
if (is_multi_part_form) {
form_data = dom_form_data->EncodeMultiPartFormData();
boundary = form_data->Boundary().data();
} else {
form_data = dom_form_data->EncodeFormData(
attributes.Method() == kGetMethod
? EncodedFormData::kFormURLEncoded
: EncodedFormData::ParseEncodingType(encoding_type));
if (copied_attributes.Method() == kPostMethod && is_mailto_form) {
// Convert the form data into a string that we put into the URL.
AppendMailtoPostFormDataToURL(action_url, *form_data, encoding_type);
form_data = EncodedFormData::Create();
}
}
form_data->SetIdentifier(GenerateFormDataIdentifier());
form_data->SetContainsPasswordData(dom_form_data->ContainsPasswordData());
if (copied_attributes.Method() != FormSubmission::kPostMethod &&
!action_url.ProtocolIsJavaScript()) {
action_url.SetQuery(form_data->FlattenToString());
}
std::unique_ptr<ResourceRequest> resource_request =
std::make_unique<ResourceRequest>(action_url);
ClientNavigationReason reason = ClientNavigationReason::kFormSubmissionGet;
if (copied_attributes.Method() == FormSubmission::kPostMethod) {
reason = ClientNavigationReason::kFormSubmissionPost;
resource_request->SetHttpMethod(http_names::kPOST);
resource_request->SetHttpBody(form_data);
// construct some user headers if necessary
if (boundary.empty()) {
resource_request->SetHTTPContentType(encoding_type);
} else {
resource_request->SetHTTPContentType(encoding_type +
"; boundary=" + boundary);
}
}
resource_request->SetHasUserGesture(
LocalFrame::HasTransientUserActivation(form->GetDocument().GetFrame()));
resource_request->SetFormSubmission(true);
mojom::blink::TriggeringEventInfo triggering_event_info;
if (event) {
triggering_event_info =
event->isTrusted()
? mojom::blink::TriggeringEventInfo::kFromTrustedEvent
: mojom::blink::TriggeringEventInfo::kFromUntrustedEvent;
if (event->UnderlyingEvent())
event = event->UnderlyingEvent();
} else {
triggering_event_info = mojom::blink::TriggeringEventInfo::kNotFromEvent;
}
FrameLoadRequest frame_request(form->GetDocument().domWindow(),
*resource_request);
//设置submit打开策略
NavigationPolicy navigation_policy = NavigationPolicyFromEvent(event);
if (navigation_policy == kNavigationPolicyLinkPreview) {
return nullptr;
}
frame_request.SetNavigationPolicy(navigation_policy);
frame_request.SetClientRedirectReason(reason);
if (submit_button) {
frame_request.SetSourceElement(submit_button);
} else {
frame_request.SetSourceElement(form);
}
frame_request.SetTriggeringEventInfo(triggering_event_info);
AtomicString target_or_base_target = frame_request.CleanNavigationTarget(
copied_attributes.Target().empty() ? document.BaseTarget()
: copied_attributes.Target());
if (form->HasRel(HTMLFormElement::kNoReferrer)) {
frame_request.SetNoReferrer();
frame_request.SetNoOpener();
}
if (form->HasRel(HTMLFormElement::kNoOpener) ||
(EqualIgnoringASCIICase(target_or_base_target, "_blank") &&
!form->HasRel(HTMLFormElement::kOpener) &&
form->GetDocument()
.domWindow()
->GetFrame()
->GetSettings()
->GetTargetBlankImpliesNoOpenerEnabledWillBeRemoved())) {
frame_request.SetNoOpener();
}
//给主进程发送message.set_method_name("CreateNewWindow");
Frame* target_frame =
form->GetDocument()
.GetFrame()
->Tree()
.FindOrCreateFrameForNavigation(frame_request, target_or_base_target)
.frame;
// Apply replacement now, before any async steps, as the result may change.
WebFrameLoadType load_type = WebFrameLoadType::kStandard;
LocalFrame* target_local_frame = DynamicTo<LocalFrame>(target_frame);
if (target_local_frame &&
target_local_frame->NavigationShouldReplaceCurrentHistoryEntry(
frame_request, load_type)) {
load_type = WebFrameLoadType::kReplaceCurrentItem;
}
return MakeGarbageCollected<FormSubmission>(
copied_attributes.Method(), action_url, target_or_base_target,
encoding_type, frame_request.GetSourceElement(), std::move(form_data),
event, frame_request.GetNavigationPolicy(), triggering_event_info, reason,
std::move(resource_request), target_frame, load_type,
form->GetDocument().domWindow(),
form->GetDocument().GetFrame()->GetLocalFrameToken(),
CaptureSourceLocation(form->GetDocument().domWindow()),
form->GetDocument()
.domWindow()
->GetPolicyContainer()
->IssueKeepAliveHandle());
}
3.1)、构建FirstName=Mickey&LastName=Mouse 参数
/ /ConstructEntryList函数遍历form表单元素
FormData* dom_form_data = form->ConstructEntryList(
submit_button, data_encoding.EncodingForFormSubmission());
DCHECK(dom_form_data);
3.2)、FormSubmission::Create调用FindOrCreateFrameForNavigation
用来打开提交新页面。
Frame* target_frame =
form->GetDocument()
.GetFrame()
->Tree()
.FindOrCreateFrameForNavigation(frame_request, target_or_base_target)
.frame;
3.3)、FindOrCreateFrameForNavigation调用RenderFrameImpl::CreateNewWindow 函数
RenderFrameImpl::CreateNewWindow函数定义(content\renderer\render_frame_impl.cc):
WebView* RenderFrameImpl::CreateNewWindow(
const WebURLRequest& request,
const blink::WebWindowFeatures& features,
const WebString& frame_name,
WebNavigationPolicy policy,
network::mojom::WebSandboxFlags sandbox_flags,
const blink::SessionStorageNamespaceId& session_storage_namespace_id,
bool& consumed_user_gesture,
const absl::optional<blink::Impression>& impression,
const absl::optional<blink::WebPictureInPictureWindowOptions>& pip_options,
const blink::WebURL& base_url) {
consumed_user_gesture = false;
mojom::CreateNewWindowParamsPtr params = mojom::CreateNewWindowParams::New();
// The user activation check is done at the browser process through
// |frame_host->CreateNewWindow()| call below. But the extensions case
// handled through the following |if| is an exception.
params->allow_popup = false;
if (GetContentClient()->renderer()->AllowPopup())
params->allow_popup = true;
params->window_container_type = WindowFeaturesToContainerType(features);
params->session_storage_namespace_id = session_storage_namespace_id;
if (!features.noopener) {
params->clone_from_session_storage_namespace_id =
GetWebView()->GetSessionStorageNamespaceId();
}
const std::string& frame_name_utf8 = frame_name.Utf8(
WebString::UTF8ConversionMode::kStrictReplacingErrorsWithFFFD);
params->frame_name = frame_name_utf8;
params->opener_suppressed = features.noopener;
params->disposition = NavigationPolicyToDisposition(policy);
if (!request.IsNull()) {
params->target_url = request.Url();
// The browser process does not consider empty URLs as valid (partly due to
// a risk of treating them as a navigation to the privileged NTP in some
// cases), so treat an attempt to create a window with an empty URL as
// opening about:blank.
//
// Similarly, javascript: URLs should not be sent to the browser process,
// since they are either handled within the renderer process (if a window is
// created within the same browsing context group) or ignored (in the
// noopener case). Use about:blank for the URL in that case as well, to
// reduce the risk of running them incorrectly.
if (params->target_url.is_empty() ||
params->target_url.SchemeIs(url::kJavaScriptScheme)) {
params->target_url = GURL(url::kAboutBlankURL);
}
params->referrer = blink::mojom::Referrer::New(
blink::WebStringToGURL(request.ReferrerString()),
request.GetReferrerPolicy());
}
params->features = ConvertWebWindowFeaturesToMojoWindowFeatures(features);
params->is_form_submission = request.IsFormSubmission();
params->form_submission_post_data =
blink::GetRequestBodyForWebURLRequest(request);
params->form_submission_post_content_type = request.HttpContentType().Utf8();
params->impression = impression;
if (pip_options) {
CHECK_EQ(policy, blink::kWebNavigationPolicyPictureInPicture);
auto pip_mojom_opts = blink::mojom::PictureInPictureWindowOptions::New();
pip_mojom_opts->width = pip_options->width;
pip_mojom_opts->height = pip_options->height;
// TODO(crbug.com/1444658): Remove this from mojom and the browser side.
pip_mojom_opts->initial_aspect_ratio = 0.0;
// TODO(crbug.com/1410379): Remove this from mojom and the browser side.
pip_mojom_opts->lock_aspect_ratio = false;
params->pip_options = std::move(pip_mojom_opts);
}
params->download_policy.ApplyDownloadFramePolicy(
/*is_opener_navigation=*/false, request.HasUserGesture(),
// `openee_can_access_opener_origin` only matters for opener navigations,
// so its value here is irrelevant.
/*openee_can_access_opener_origin=*/true,
!GetWebFrame()->IsAllowedToDownload(), GetWebFrame()->IsAdFrame());
params->initiator_activation_and_ad_status =
blink::GetNavigationInitiatorActivationAndAdStatus(
request.HasUserGesture(), GetWebFrame()->IsAdFrame(),
GetWebFrame()->IsAdScriptInStack());
// We preserve this information before sending the message since |params| is
// moved on send.
bool is_background_tab =
params->disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB;
mojom::CreateNewWindowStatus status;
mojom::CreateNewWindowReplyPtr reply;
auto* frame_host = GetFrameHost();
if (!frame_host->CreateNewWindow(std::move(params), &status, &reply)) {
// The sync IPC failed, e.g. maybe the render process is in the middle of
// shutting down. Can't create a new window without the browser process,
// so just bail out.
return nullptr;
}
// If creation of the window was blocked (e.g. because this frame doesn't
// have user activation), return before consuming user activation. A frame
// that isn't allowed to open a window shouldn't be able to consume the
// activation for the rest of the frame tree.
if (status == mojom::CreateNewWindowStatus::kBlocked)
return nullptr;
// For Android WebView, we support a pop-up like behavior for window.open()
// even if the embedding app doesn't support multiple windows. In this case,
// window.open() will return "window" and navigate it to whatever URL was
// passed. We also don't need to consume user gestures to protect against
// multiple windows being opened, because, well, the app doesn't support
// multiple windows.
// TODO(dcheng): It's awkward that this is plumbed into Blink but not really
// used much in Blink, except to enable web testing... perhaps this should
// be checked directly in the browser side.
if (status == mojom::CreateNewWindowStatus::kReuse) {
// In this case, treat javascript: URLs as blocked rather than running them
// in a reused main frame in Android WebView. See https://crbug.com/1083819.
if (!request.IsNull() && request.Url().ProtocolIs(url::kJavaScriptScheme)) {
return nullptr;
}
return GetWebView();
}
// Consume the transient user activation in the current renderer.
consumed_user_gesture = GetWebFrame()->ConsumeTransientUserActivation(
blink::UserActivationUpdateSource::kBrowser);
// If we should ignore the new window (e.g. because of `noopener`), return
// now that user activation was consumed.
if (status == mojom::CreateNewWindowStatus::kIgnore)
return nullptr;
DCHECK(reply);
DCHECK_NE(MSG_ROUTING_NONE, reply->main_frame_route_id);
DCHECK_NE(MSG_ROUTING_NONE, reply->widget_params->routing_id);
// While this view may be a background extension page, it can spawn a visible
// render view. So we just assume that the new one is not another background
// page instead of passing on our own value.
// TODO(vangelis): Can we tell if the new view will be a background page?
bool never_composited = false;
// The initial hidden state for the RenderViewImpl here has to match what the
// browser will eventually decide for the given disposition. Since we have to
// return from this call synchronously, we just have to make our best guess
// and rely on the browser sending a WasHidden / WasShown message if it
// disagrees.
mojom::CreateViewParamsPtr view_params = mojom::CreateViewParams::New();
view_params->opener_frame_token = GetWebFrame()->GetFrameToken();
view_params->window_was_opened_by_another_window = true;
view_params->renderer_preferences = GetWebView()->GetRendererPreferences();
view_params->web_preferences = GetWebView()->GetWebPreferences();
view_params->replication_state = blink::mojom::FrameReplicationState::New();
view_params->replication_state->frame_policy.sandbox_flags = sandbox_flags;
view_params->replication_state->name = frame_name_utf8;
view_params->devtools_main_frame_token = reply->devtools_main_frame_token;
view_params->browsing_context_group_info = reply->browsing_context_group_info;
view_params->color_provider_colors = reply->color_provider_colors;
auto main_frame_params = mojom::CreateLocalMainFrameParams::New();
main_frame_params->frame_token = reply->main_frame_token;
main_frame_params->routing_id = reply->main_frame_route_id;
main_frame_params->frame = std::move(reply->frame);
main_frame_params->interface_broker =
std::move(reply->main_frame_interface_broker);
main_frame_params->document_token = reply->document_token;
main_frame_params->policy_container = std::move(reply->policy_container);
main_frame_params->associated_interface_provider_remote =
std::move(reply->associated_interface_provider);
main_frame_params->widget_params = std::move(reply->widget_params);
main_frame_params->subresource_loader_factories =
base::WrapUnique(static_cast<blink::PendingURLLoaderFactoryBundle*>(
CloneLoaderFactories()->Clone().release()));
view_params->main_frame =
mojom::CreateMainFrameUnion::NewLocalParams(std::move(main_frame_params));
view_params->blink_page_broadcast = std::move(reply->page_broadcast);
view_params->session_storage_namespace_id =
reply->cloned_session_storage_namespace_id;
DCHECK(!view_params->session_storage_namespace_id.empty())
<< "Session storage namespace must be populated.";
view_params->hidden = is_background_tab;
view_params->never_composited = never_composited;
WebView* web_view = agent_scheduling_group_->CreateWebView(
std::move(view_params),
/*was_created_by_renderer=*/true, base_url);
if (reply->wait_for_debugger) {
blink::WebFrameWidget* frame_widget =
web_view->MainFrame()->ToWebLocalFrame()->LocalRoot()->FrameWidget();
frame_widget->WaitForDebuggerWhenShown();
}
return web_view;
}
堆栈图:
@3 content.dll!content::RenderFrameImpl::CreateNewWindow(const blink::WebURLRequest & request, const blink::WebWindowFeatures & features, const blink::WebString & frame_name, blink::WebNavigationPolicy policy, network::mojom::WebSandboxFlags sandbox_flags, const std::__Cr::basic_string<char,std::__Cr::char_traits<char>,std::__Cr::allocator<char>> & session_storage_namespace_id, bool & consumed_user_gesture, const std::__Cr::optional<blink::Impression> & impression, const std::__Cr::optional<blink::WebPictureInPictureWindowOptions> & pip_options, const blink::WebURL & base_url) 行 6562 C++ 已加载符号。
@2 blink_core.dll!blink::ChromeClientImpl::CreateWindowDelegate(blink::LocalFrame * frame, const blink::FrameLoadRequest & r, const WTF::AtomicString & name, const blink::WebWindowFeatures & features, network::mojom::WebSandboxFlags sandbox_flags, const std::__Cr::basic_string<char,std::__Cr::char_traits<char>,std::__Cr::allocator<char>> & session_storage_namespace_id, bool & consumed_user_gesture) 行 342 C++ 已加载符号。
@1 blink_core.dll!blink::ChromeClient::CreateWindow(blink::LocalFrame * frame, const blink::FrameLoadRequest & r, const WTF::AtomicString & frame_name, const blink::WebWindowFeatures & features, network::mojom::WebSandboxFlags sandbox_flags, const std::__Cr::basic_string<char,std::__Cr::char_traits<char>,std::__Cr::allocator<char>> & session_storage_namespace_id, bool & consumed_user_gesture) 行 88 C++ 已加载符号。
@0 blink_core.dll!blink::CreateNewWindow(blink::LocalFrame & opener_frame, blink::FrameLoadRequest & request, const WTF::AtomicString & frame_name) 行 355 C++ 已加载符号。
> blink_core.dll!blink::FrameTree::FindOrCreateFrameForNavigation(blink::FrameLoadRequest & request, const WTF::AtomicString & name) 行 217 C++ 已加载符号。
3.4)、RenderFrameImpl::CreateNewWindow 调用frame_host->CreateNewWindow 给主进程发送CreateNewWindow的mojom消息。
mojom::CreateNewWindowStatus status;
mojom::CreateNewWindowReplyPtr reply;
auto* frame_host = GetFrameHost();
//给主进程发送CreateNewWindow mojom消息
if (!frame_host->CreateNewWindow(std::move(params), &status, &reply)) {
// The sync IPC failed, e.g. maybe the render process is in the middle of
// shutting down. Can't create a new window without the browser process,
// so just bail out.
return nullptr;
}
4、主进程收到子进程发送的mojom CreateNewWindow消息:
在RenderFrameHostImpl::CreateNewWindow函数里面处理打开 submit提交URL="https://www.runoob.com/try/demo_source/demo-form.php?FirstName=Mickey&LastName=Mouse"
src\content\browser\renderer_host\render_frame_host_impl.cc
浏览器打开了一个submit提交的新标签,至此form提交完成。
void RenderFrameHostImpl::CreateNewWindow(
mojom::CreateNewWindowParamsPtr params,
CreateNewWindowCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT2("navigation", "RenderFrameHostImpl::CreateNewWindow",
"render_frame_host", this, "url", params->target_url);
// Only top-most frames can open picture-in-picture windows.
if (params->disposition == WindowOpenDisposition::NEW_PICTURE_IN_PICTURE &&
GetParentOrOuterDocumentOrEmbedder()) {
frame_host_associated_receiver_.ReportBadMessage(
"Only top-most frames can open picture-in-picture windows.");
return;
}
bool no_javascript_access = false;
// Filter out URLs to which navigation is disallowed from this context.
GetProcess()->FilterURL(false, ¶ms->target_url);
bool effective_transient_activation_state =
params->allow_popup || HasTransientUserActivation() ||
(transient_allow_popup_.IsActive() &&
params->disposition == WindowOpenDisposition::NEW_POPUP);
// Ignore window creation when sent from a frame that's not active or
// created.
bool can_create_window =
IsActive() && is_render_frame_created() &&
GetContentClient()->browser()->CanCreateWindow(
this, GetLastCommittedURL(),
GetOutermostMainFrame()->GetLastCommittedURL(),
last_committed_origin_, params->window_container_type,
params->target_url, params->referrer.To<Referrer>(),
params->frame_name, params->disposition, *params->features,
effective_transient_activation_state, params->opener_suppressed,
&no_javascript_access);
// If this frame isn't allowed to create a window, return early (before we
// consume transient user activation).
if (!can_create_window) {
std::move(callback).Run(mojom::CreateNewWindowStatus::kBlocked, nullptr);
return;
}
// Otherwise, consume user activation before we proceed. In particular, it is
// important to do this before we return from the |opener_suppressed| case
// below.
// NB: This call will consume activations in the browser and the remote frame
// proxies for this frame. The initiating renderer will consume its view of
// the activations after we return.
// See `owner_` invariants about `IsActive()`, which is implied by
// `can_create_window`.
CHECK(owner_);
bool was_consumed = owner_->UpdateUserActivationState(
blink::mojom::UserActivationUpdateType::kConsumeTransientActivation,
blink::mojom::UserActivationNotificationType::kNone);
// For Android WebView, we support a pop-up like behavior for window.open()
// even if the embedding app doesn't support multiple windows. In this case,
// window.open() will return "window" and navigate it to whatever URL was
// passed.
if (!GetOrCreateWebPreferences().supports_multiple_windows) {
std::move(callback).Run(mojom::CreateNewWindowStatus::kReuse, nullptr);
return;
}
// This will clone the sessionStorage for namespace_id_to_clone.
StoragePartition* storage_partition = GetStoragePartition();
DOMStorageContextWrapper* dom_storage_context =
static_cast<DOMStorageContextWrapper*>(
storage_partition->GetDOMStorageContext());
scoped_refptr<SessionStorageNamespaceImpl> cloned_namespace;
if (!params->clone_from_session_storage_namespace_id.empty()) {
cloned_namespace = SessionStorageNamespaceImpl::CloneFrom(
dom_storage_context, params->session_storage_namespace_id,
params->clone_from_session_storage_namespace_id);
} else {
cloned_namespace = SessionStorageNamespaceImpl::Create(
dom_storage_context, params->session_storage_namespace_id);
}
if (IsCredentialless() || IsNestedWithinFencedFrame() ||
CoopSuppressOpener(/*opener=*/this)) {
params->opener_suppressed = true;
// TODO(https://crbug.com/1060691) This should be applied to all
// popups opened with noopener.
params->frame_name.clear();
}
RenderFrameHostImpl* top_level_opener = GetMainFrame();
int popup_virtual_browsing_context_group =
params->opener_suppressed
? CrossOriginOpenerPolicyAccessReportManager::
GetNewVirtualBrowsingContextGroup()
: top_level_opener->virtual_browsing_context_group();
int popup_soap_by_default_virtual_browsing_context_group =
params->opener_suppressed
? CrossOriginOpenerPolicyAccessReportManager::
GetNewVirtualBrowsingContextGroup()
: top_level_opener->soap_by_default_virtual_browsing_context_group();
// If the opener is suppressed or script access is disallowed, we should
// open the window in a new BrowsingInstance, and thus a new process. That
// means the current renderer process will not be able to route messages to
// it. Because of this, we will immediately show and navigate the window
// in OnCreateNewWindowOnUI, using the params provided here.
bool is_new_browsing_instance =
params->opener_suppressed || no_javascript_access;
DCHECK(IsRenderFrameLive());
// The non-owning pointer |new_frame_tree| is valid in this stack frame since
// nothing can delete it until this thread is freed up again.
FrameTree* new_frame_tree =
delegate_->CreateNewWindow(this, *params, is_new_browsing_instance,
was_consumed, cloned_namespace.get());
transient_allow_popup_.Deactivate();
MaybeRecordAdClickMainFrameNavigationUseCounter(
/*initiator_frame=*/this, params->initiator_activation_and_ad_status);
if (is_new_browsing_instance || !new_frame_tree) {
// Opener suppressed, Javascript access disabled, or delegate did not
// provide a handle to any windows it created. In these cases, never tell
// the renderer about the new window.
std::move(callback).Run(mojom::CreateNewWindowStatus::kIgnore, nullptr);
return;
}
DCHECK(!params->opener_suppressed);
RenderFrameHostImpl* new_main_rfh =
new_frame_tree->root()->current_frame_host();
new_main_rfh->virtual_browsing_context_group_ =
popup_virtual_browsing_context_group;
new_main_rfh->soap_by_default_virtual_browsing_context_group_ =
popup_soap_by_default_virtual_browsing_context_group;
// COOP and COOP reporter are inherited from the opener to the popup's initial
// empty document.
if (IsOpenerSameOriginFrame(/*opener=*/this) &&
GetMainFrame()->coop_access_report_manager()->coop_reporter()) {
new_main_rfh->SetCrossOriginOpenerPolicyReporter(
std::make_unique<CrossOriginOpenerPolicyReporter>(
GetProcess()->GetStoragePartition(), GetLastCommittedURL(),
params->referrer->url,
// TODO(https://crbug.com/1385827): See if we need to send the
// origin to reporters as well.
new_main_rfh->cross_origin_opener_policy(), GetReportingSource(),
isolation_info_.network_anonymization_key()));
}
mojo::PendingAssociatedRemote<mojom::Frame> pending_frame_remote;
mojo::PendingAssociatedReceiver<mojom::Frame> pending_frame_receiver =
pending_frame_remote.InitWithNewEndpointAndPassReceiver();
new_main_rfh->SetMojomFrameRemote(std::move(pending_frame_remote));
mojo::PendingRemote<blink::mojom::BrowserInterfaceBroker>
browser_interface_broker;
new_main_rfh->BindBrowserInterfaceBrokerReceiver(
browser_interface_broker.InitWithNewPipeAndPassReceiver());
mojo::PendingAssociatedRemote<blink::mojom::AssociatedInterfaceProvider>
pending_associated_interface_provider;
new_main_rfh->BindAssociatedInterfaceProviderReceiver(
pending_associated_interface_provider
.InitWithNewEndpointAndPassReceiver());
// With this path, RenderViewHostImpl::CreateRenderView is never called
// because `blink::WebView` is already created on the renderer side. Thus we
// need to establish the connection here.
mojo::PendingAssociatedRemote<blink::mojom::PageBroadcast> page_broadcast;
mojo::PendingAssociatedReceiver<blink::mojom::PageBroadcast>
page_broadcast_receiver =
page_broadcast.InitWithNewEndpointAndPassReceiver();
auto widget_params =
new_main_rfh->GetLocalRenderWidgetHost()
->BindAndGenerateCreateFrameWidgetParamsForNewWindow();
new_main_rfh->render_view_host()->BindPageBroadcast(
std::move(page_broadcast));
bool wait_for_debugger =
devtools_instrumentation::ShouldWaitForDebuggerInWindowOpen();
mojom::CreateNewWindowReplyPtr reply = mojom::CreateNewWindowReply::New(
new_main_rfh->GetFrameToken(), new_main_rfh->GetRoutingID(),
std::move(pending_frame_receiver), std::move(widget_params),
std::move(page_broadcast_receiver), std::move(browser_interface_broker),
std::move(pending_associated_interface_provider), cloned_namespace->id(),
new_main_rfh->GetDevToolsFrameToken(), wait_for_debugger,
new_main_rfh->GetDocumentToken(),
new_main_rfh->policy_container_host()->CreatePolicyContainerForBlink(),
blink::BrowsingContextGroupInfo(
new_main_rfh->GetSiteInstance()->browsing_instance_token(),
new_main_rfh->GetSiteInstance()->coop_related_group_token()),
delegate_->GetColorProviderColorMaps());
std::move(callback).Run(mojom::CreateNewWindowStatus::kSuccess,
std::move(reply));
// The mojom reply callback with kSuccess causes the renderer to create the
// renderer-side objects.
new_main_rfh->render_view_host()->RenderViewCreated(new_main_rfh);
}
子进程发送消息定义在
F:\code\google\src\out\Debug\gen\content\common\frame.mojom.cc
message.set_method_name("CreateNewWindow");
FrameHostProxy::FrameHostProxy(mojo::MessageReceiverWithResponder* receiver)
: receiver_(receiver) {
}
bool FrameHostProxy::CreateNewWindow(
CreateNewWindowParamsPtr param_params, CreateNewWindowStatus* out_param_status, CreateNewWindowReplyPtr* out_param_reply) {
#if BUILDFLAG(MOJO_TRACE_ENABLED)
TRACE_EVENT_BEGIN1(
"mojom", "Call content::mojom::FrameHost::CreateNewWindow (sync)", "input_parameters",
[&](perfetto::TracedValue context){
auto dict = std::move(context).WriteDictionary();
perfetto::WriteIntoTracedValueWithFallback(
dict.AddItem("params"), param_params,
"<value of type CreateNewWindowParamsPtr>");
});
#else
TRACE_EVENT0("mojom", "FrameHost::CreateNewWindow");
#endif
const bool kExpectsResponse = true;
const bool kIsSync = true;
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::kFrameHost_CreateNewWindow_Name, kFlags, 0, 0, nullptr);
mojo::internal::MessageFragment<
::content::mojom::internal::FrameHost_CreateNewWindow_Params_Data> params(
message);
params.Allocate();
mojo::internal::MessageFragment<
typename decltype(params->params)::BaseType> params_fragment(
params.message());
mojo::internal::Serialize<::content::mojom::CreateNewWindowParamsDataView>(
param_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 FrameHost.CreateNewWindow request");
#if defined(ENABLE_IPC_FUZZER)
message.set_interface_name(FrameHost::Name_);
message.set_method_name("CreateNewWindow");
#endif
bool result = false;
std::unique_ptr<mojo::MessageReceiver> responder(
new FrameHost_CreateNewWindow_HandleSyncResponse(
&result, out_param_status, out_param_reply));
::mojo::internal::SendMojoMessage(*receiver_, message, std::move(responder));
#if BUILDFLAG(MOJO_TRACE_ENABLED)
TRACE_EVENT_END1(
"mojom", "FrameHost::CreateNewWindow", "sync_response_parameters",
[&](perfetto::TracedValue context){
auto dict = std::move(context).WriteDictionary();
perfetto::WriteIntoTracedValueWithFallback(
dict.AddItem("status"), out_param_status,
"<value of type CreateNewWindowStatus>");
perfetto::WriteIntoTracedValueWithFallback(
dict.AddItem("reply"), out_param_reply,
"<value of type CreateNewWindowReplyPtr>");
});
#endif
return result;
}