diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 75e78849fbfb..ea583f1cd0d4 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -7,6 +7,7 @@ #include "nsDocShell.h" #include +#include "mozilla/dom/HTMLFormElement.h" #ifdef XP_WIN # include @@ -3930,7 +3931,7 @@ nsresult nsDocShell::ReloadNavigable( if (!navigation->FirePushReplaceReloadNavigateEvent( aCx, NavigationType::Reload, destinationURL, /* aIsSameDocument */ false, Some(aUserInvolvement), - /* aSourceElement*/ nullptr, /* aFormDataEntryList */ Nothing{}, + /* aSourceElement*/ nullptr, /* aFormDataEntryList */ nullptr, destinationNavigationAPIState, /* aClassiCHistoryAPIState */ nullptr)) { return NS_OK; @@ -8772,7 +8773,7 @@ nsresult nsDocShell::HandleSameDocumentNavigation( jsapi.cx(), aLoadState->GetNavigationType(), newURI, /* aIsSameDocument */ true, Some(aLoadState->UserNavigationInvolvement()), sourceElement, - /* aFormDataEntryList */ Nothing(), + /* aFormDataEntryList */ nullptr, /* aNavigationAPIState */ destinationNavigationAPIState, /* aClassicHistoryAPIState */ nullptr); @@ -9276,6 +9277,8 @@ uint32_t nsDocShell::GetLoadTypeForFormSubmission( : LOAD_LINK; } +// InternalLoad performs several of the steps from +// https://html.spec.whatwg.org/#navigate. nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState, Maybe aCacheKey) { MOZ_ASSERT(aLoadState, "need a load state!"); @@ -9485,6 +9488,52 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState, if (mTiming && !isDownload) { mTiming->NotifyBeforeUnload(); } + + // The following steps are from https://html.spec.whatwg.org/#navigate + // Step 20 + if (RefPtr document = GetDocument(); + document && + aLoadState->UserNavigationInvolvement() != + UserNavigationInvolvement::BrowserUI && + !document->IsInitialDocument() && + !NS_IsAboutBlankAllowQueryAndFragment(document->GetDocumentURI()) && + NS_IsFetchScheme(aLoadState->URI()) && + document->NodePrincipal()->Subsumes(aLoadState->TriggeringPrincipal())) { + if (nsCOMPtr window = document->GetInnerWindow()) { + // Step 20.1 + if (RefPtr navigation = window->Navigation()) { + AutoJSAPI jsapi; + if (jsapi.Init(window)) { + RefPtr sourceElement = aLoadState->GetSourceElement(); + + // Step 20.2 + RefPtr formData = aLoadState->GetFormDataEntryList(); + + // Step 20.3 + RefPtr navigationAPIStateForFiring = + aLoadState->GetNavigationAPIState(); + if (!navigationAPIStateForFiring) { + navigationAPIStateForFiring = nullptr; + } + + nsCOMPtr destinationURL = aLoadState->URI(); + // Step 20.4 + bool shouldContinue = navigation->FirePushReplaceReloadNavigateEvent( + jsapi.cx(), aLoadState->GetNavigationType(), destinationURL, + /* aIsSameDocument */ false, + Some(aLoadState->UserNavigationInvolvement()), sourceElement, + formData.forget(), navigationAPIStateForFiring, + /* aClassicHistoryAPIState */ nullptr); + + // Step 20.5 + if (!shouldContinue) { + return NS_OK; + } + } + } + } + } + // Check if the page doesn't want to be unloaded. The javascript: // protocol handler deals with this for javascript: URLs. // NOTE(emilio): As of this writing, other browsers fire beforeunload for @@ -11450,7 +11499,7 @@ nsDocShell::AddState(JS::Handle aData, const nsAString& aTitle, aCx, aReplace ? NavigationType::Replace : NavigationType::Push, newURI, /* aIsSameDocument */ true, /* aUserInvolvement */ Nothing(), - /* aSourceElement */ nullptr, /* aFormDataEntryList */ Nothing(), + /* aSourceElement */ nullptr, /* aFormDataEntryList */ nullptr, /* aNavigationAPIState */ nullptr, scContainer); // Step 9 @@ -13265,6 +13314,7 @@ nsresult nsDocShell::OnLinkClickSync(nsIContent* aContent, aLoadState->SetTypeHint(NS_ConvertUTF16toUTF8(typeHint)); aLoadState->SetLoadType(loadType); aLoadState->SetSourceBrowsingContext(mBrowsingContext); + aLoadState->SetSourceElement(aContent->AsElement()); nsresult rv = InternalLoad(aLoadState); diff --git a/docshell/base/nsDocShellLoadState.cpp b/docshell/base/nsDocShellLoadState.cpp index d7de4d5a1ea9..c179ef88716f 100644 --- a/docshell/base/nsDocShellLoadState.cpp +++ b/docshell/base/nsDocShellLoadState.cpp @@ -24,6 +24,7 @@ #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/FormData.h" #include "mozilla/dom/LoadURIOptionsBinding.h" #include "mozilla/dom/nsHTTPSOnlyUtils.h" #include "mozilla/StaticPrefs_browser.h" @@ -755,20 +756,6 @@ bool nsDocShellLoadState::LoadIsFromSessionHistory() const { : !!mSHEntry; } -nsIStructuredCloneContainer* nsDocShellLoadState::GetNavigationAPIState() - const { - return mNavigationAPIState; -} - -void nsDocShellLoadState::SetNavigationAPIState( - nsIStructuredCloneContainer* aNavigationAPIState) { - mNavigationAPIState = aNavigationAPIState; -} - -NavigationType nsDocShellLoadState::GetNavigationType() const { - return LoadReplace() ? NavigationType::Replace : NavigationType::Push; -} - void nsDocShellLoadState::MaybeStripTrackerQueryStrings( BrowsingContext* aContext) { MOZ_ASSERT(aContext); @@ -1424,3 +1411,26 @@ already_AddRefed nsDocShellLoadState::GetSourceElement() const { nsCOMPtr element = do_QueryReferent(mSourceElement); return element.forget(); } + +nsIStructuredCloneContainer* nsDocShellLoadState::GetNavigationAPIState() + const { + return mNavigationAPIState; +} + +void nsDocShellLoadState::SetNavigationAPIState( + nsIStructuredCloneContainer* aNavigationAPIState) { + mNavigationAPIState = aNavigationAPIState; +} + +NavigationType nsDocShellLoadState::GetNavigationType() const { + return LoadReplace() ? NavigationType::Replace : NavigationType::Push; +} + +mozilla::dom::FormData* nsDocShellLoadState::GetFormDataEntryList() { + return nullptr; +} + +void nsDocShellLoadState::SetFormDataEntryList( + mozilla::dom::FormData* aFormDataEntryList) { + mFormDataEntryList = aFormDataEntryList; +} diff --git a/docshell/base/nsDocShellLoadState.h b/docshell/base/nsDocShellLoadState.h index f22c1dd85971..2f78bc7f85d3 100644 --- a/docshell/base/nsDocShellLoadState.h +++ b/docshell/base/nsDocShellLoadState.h @@ -34,6 +34,7 @@ class OriginAttributes; template class UniquePtr; namespace dom { +class FormData; class DocShellLoadStateInit; } // namespace dom } // namespace mozilla @@ -196,14 +197,6 @@ class nsDocShellLoadState final { bool LoadIsFromSessionHistory() const; - nsIStructuredCloneContainer* GetNavigationAPIState() const; - - // This is used as the parameter for https://html.spec.whatwg.org/#navigate, - // but it's currently missing. See bug 1966674 - void SetNavigationAPIState(nsIStructuredCloneContainer* aNavigationAPIState); - - mozilla::dom::NavigationType GetNavigationType() const; - const nsString& Target() const; void SetTarget(const nsAString& aTarget); @@ -417,9 +410,23 @@ class nsDocShellLoadState final { void MaybeStripTrackerQueryStrings(mozilla::dom::BrowsingContext* aContext); + // This is used as the parameter for https://html.spec.whatwg.org/#navigate void SetSourceElement(mozilla::dom::Element* aElement); already_AddRefed GetSourceElement() const; + // This is used as the parameter for https://html.spec.whatwg.org/#navigate, + // but it's currently missing. See bug 1966674 + nsIStructuredCloneContainer* GetNavigationAPIState() const; + void SetNavigationAPIState(nsIStructuredCloneContainer* aNavigationAPIState); + + // This is used as the parameter for https://html.spec.whatwg.org/#navigate + mozilla::dom::NavigationType GetNavigationType() const; + + // This is used as the parameter for https://html.spec.whatwg.org/#navigate + // It should only ever be set if the method is POST. + mozilla::dom::FormData* GetFormDataEntryList(); + void SetFormDataEntryList(mozilla::dom::FormData* aFormDataEntryList); + protected: // Destructor can't be defaulted or inlined, as header doesn't have all type // includes it needs to do so. @@ -561,8 +568,6 @@ class nsDocShellLoadState final { mozilla::UniquePtr mLoadingSessionHistoryInfo; - nsCOMPtr mNavigationAPIState; - // Target for load, like _content, _blank etc. nsString mTarget; @@ -674,6 +679,10 @@ class nsDocShellLoadState final { nsILoadInfo::NOT_INITIALIZED; nsWeakPtr mSourceElement; + + nsCOMPtr mNavigationAPIState; + + RefPtr mFormDataEntryList; }; #endif /* nsDocShellLoadState_h__ */ diff --git a/dom/base/FormData.cpp b/dom/base/FormData.cpp index d9967ab947c2..5f5de016b56b 100644 --- a/dom/base/FormData.cpp +++ b/dom/base/FormData.cpp @@ -307,9 +307,18 @@ already_AddRefed FormData::Constructor( const GlobalObject& aGlobal, const Optional >& aFormElement, nsGenericHTMLElement* aSubmitter, ErrorResult& aRv) { + return Constructor(aGlobal.GetAsSupports(), + aFormElement.WasPassed() ? &aFormElement.Value() : nullptr, + aSubmitter, aRv); +} + +/* static */ +already_AddRefed FormData::Constructor( + nsISupports* aGlobal, HTMLFormElement* aFormElement, + nsGenericHTMLElement* aSubmitter, ErrorResult& aRv) { RefPtr formData; // 1. If form is given, then: - if (aFormElement.WasPassed()) { + if (aFormElement) { // 1.1. If submitter is non-null, then: if (aSubmitter) { const nsIFormControl* fc = nsIFormControl::FromNode(aSubmitter); @@ -322,7 +331,7 @@ already_AddRefed FormData::Constructor( // 1.1.2. If submitter's form owner is not this form element, then throw a // "NotFoundError" DOMException. - if (fc->GetForm() != &aFormElement.Value()) { + if (fc->GetForm() != aFormElement) { aRv.ThrowNotFoundError("The submitter is not owned by this form."); return nullptr; } @@ -330,9 +339,8 @@ already_AddRefed FormData::Constructor( // 1.2. Let list be the result of constructing the entry list for form and // submitter. - formData = - new FormData(aGlobal.GetAsSupports(), UTF_8_ENCODING, aSubmitter); - aRv = aFormElement.Value().ConstructEntryList(formData); + formData = new FormData(aGlobal, UTF_8_ENCODING, aSubmitter); + aRv = aFormElement->ConstructEntryList(formData); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } @@ -341,7 +349,7 @@ already_AddRefed FormData::Constructor( // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-form-data-set formData = formData->Clone(); } else { - formData = new FormData(aGlobal.GetAsSupports()); + formData = new FormData(aGlobal); } return formData.forget(); diff --git a/dom/base/FormData.h b/dom/base/FormData.h index 831c64fa519f..b8c7f6eee5b9 100644 --- a/dom/base/FormData.h +++ b/dom/base/FormData.h @@ -73,6 +73,10 @@ class FormData final : public nsISupports, const Optional >& aFormElement, nsGenericHTMLElement* aSubmitter, ErrorResult& aRv); + static already_AddRefed Constructor( + nsISupports* aGlobal, HTMLFormElement* aFormElement, + nsGenericHTMLElement* aSubmitter, ErrorResult& aRv); + void Append(const nsAString& aName, const nsAString& aValue, ErrorResult& aRv); diff --git a/dom/html/HTMLFormElement.cpp b/dom/html/HTMLFormElement.cpp index 4fd39c663984..83b84fe43fbe 100644 --- a/dom/html/HTMLFormElement.cpp +++ b/dom/html/HTMLFormElement.cpp @@ -784,7 +784,7 @@ nsresult HTMLFormElement::BuildSubmission(HTMLFormSubmission** aFormSubmission, // // Get the submission object // - rv = HTMLFormSubmission::GetFromForm(this, submitter, encoding, + rv = HTMLFormSubmission::GetFromForm(this, submitter, encoding, formData, aFormSubmission); NS_ENSURE_SUBMIT_SUCCESS(rv); @@ -889,6 +889,7 @@ nsresult HTMLFormElement::SubmitSubmission( loadState->SetTextDirectiveUserActivation( doc->ConsumeTextDirectiveUserActivation() || hasValidUserGestureActivation); + loadState->SetFormDataEntryList(aFormSubmission->GetFormData()); nsCOMPtr nodePrincipal = NodePrincipal(); rv = container->OnLinkClickSync(this, loadState, false, nodePrincipal); diff --git a/dom/html/HTMLFormSubmission.cpp b/dom/html/HTMLFormSubmission.cpp index 6c8a83952023..a3b7e108525b 100644 --- a/dom/html/HTMLFormSubmission.cpp +++ b/dom/html/HTMLFormSubmission.cpp @@ -33,6 +33,7 @@ #include "mozilla/dom/AncestorIterator.h" #include "mozilla/dom/Directory.h" #include "mozilla/dom/File.h" +#include "mozilla/dom/FormData.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/RandomNum.h" @@ -770,6 +771,7 @@ void GetEnumAttr(nsGenericHTMLElement* aContent, nsAtom* atom, nsresult HTMLFormSubmission::GetFromForm(HTMLFormElement* aForm, nsGenericHTMLElement* aSubmitter, NotNull& aEncoding, + FormData* aFormData, HTMLFormSubmission** aFormSubmission) { // Get all the information necessary to encode the form data NS_ASSERTION(aForm->GetComposedDoc(), @@ -864,6 +866,13 @@ nsresult HTMLFormSubmission::GetFromForm(HTMLFormElement* aForm, new FSURLEncoded(actionURL, target, aEncoding, method, doc, aSubmitter); } + // We store the FormData here to be able to set it on the load state when we + // submit the submission. It's used for the #navigate algorithm in the HTML + // spec and is only ever needed when the method is POST. + if (method == NS_FORM_METHOD_POST) { + (*aFormSubmission)->mFormData = aFormData; + } + return NS_OK; } diff --git a/dom/html/HTMLFormSubmission.h b/dom/html/HTMLFormSubmission.h index 7e4d3a0e4e3e..524daf8f139e 100644 --- a/dom/html/HTMLFormSubmission.h +++ b/dom/html/HTMLFormSubmission.h @@ -45,6 +45,7 @@ class HTMLFormSubmission { static nsresult GetFromForm(HTMLFormElement* aForm, nsGenericHTMLElement* aSubmitter, NotNull& aEncoding, + FormData* aFormData, HTMLFormSubmission** aFormSubmission); MOZ_COUNTED_DTOR_VIRTUAL(HTMLFormSubmission) @@ -111,6 +112,8 @@ class HTMLFormSubmission { virtual DialogFormSubmission* GetAsDialogSubmission() { return nullptr; } + FormData* GetFormData() const { return mFormData; } + protected: /** * Can only be constructed by subclasses. @@ -129,6 +132,8 @@ class HTMLFormSubmission { // The character encoding of this form submission mozilla::NotNull mEncoding; + RefPtr mFormData; + // Keep track of whether this form submission was user-initiated or not bool mInitiatedFromUserInput; }; diff --git a/dom/navigation/Navigation.cpp b/dom/navigation/Navigation.cpp index 5de163487cf5..fa114afff984 100644 --- a/dom/navigation/Navigation.cpp +++ b/dom/navigation/Navigation.cpp @@ -524,7 +524,7 @@ bool Navigation::FireTraverseNavigateEvent( aCx, NavigationType::Traverse, destination, aUserInvolvement.valueOr(UserNavigationInvolvement::None), /* aSourceElement */ nullptr, - /* aFormDataEntryList*/ Nothing(), + /* aFormDataEntryList*/ nullptr, /* aClassicHistoryAPIState */ nullptr, /* aDownloadRequestFilename */ VoidString()); } @@ -533,7 +533,7 @@ bool Navigation::FireTraverseNavigateEvent( bool Navigation::FirePushReplaceReloadNavigateEvent( JSContext* aCx, NavigationType aNavigationType, nsIURI* aDestinationURL, bool aIsSameDocument, Maybe aUserInvolvement, - Element* aSourceElement, Maybe aFormDataEntryList, + Element* aSourceElement, already_AddRefed aFormDataEntryList, nsIStructuredCloneContainer* aNavigationAPIState, nsIStructuredCloneContainer* aClassicHistoryAPIState) { // To not unnecessarily create an event that's never used, step 1 and step 2 @@ -551,7 +551,7 @@ bool Navigation::FirePushReplaceReloadNavigateEvent( return InnerFireNavigateEvent( aCx, aNavigationType, destination, aUserInvolvement.valueOr(UserNavigationInvolvement::None), aSourceElement, - aFormDataEntryList, aClassicHistoryAPIState, + std::move(aFormDataEntryList), aClassicHistoryAPIState, /* aDownloadRequestFilename */ VoidString()); } @@ -574,7 +574,7 @@ bool Navigation::FireDownloadRequestNavigateEvent( // Step 8 return InnerFireNavigateEvent( aCx, NavigationType::Push, destination, aUserInvolvement, aSourceElement, - /* aFormDataEntryList */ Nothing(), + /* aFormDataEntryList */ nullptr, /* aClassicHistoryAPIState */ nullptr, aFilename); } @@ -663,7 +663,7 @@ bool Navigation::InnerFireNavigateEvent( JSContext* aCx, NavigationType aNavigationType, NavigationDestination* aDestination, UserNavigationInvolvement aUserInvolvement, Element* aSourceElement, - Maybe aFormDataEntryList, + already_AddRefed aFormDataEntryList, nsIStructuredCloneContainer* aClassicHistoryAPIState, const nsAString& aDownloadRequestFilename) { // Step 1 @@ -758,7 +758,7 @@ bool Navigation::InnerFireNavigateEvent( init.mUserInitiated = aUserInvolvement != UserNavigationInvolvement::None; // Step 24 - init.mFormData = aFormDataEntryList ? aFormDataEntryList->Clone() : nullptr; + init.mFormData = aFormDataEntryList; // Step 25 MOZ_DIAGNOSTIC_ASSERT(!mOngoingNavigateEvent); diff --git a/dom/navigation/Navigation.h b/dom/navigation/Navigation.h index e8fb79192e6d..99f3e13a8520 100644 --- a/dom/navigation/Navigation.h +++ b/dom/navigation/Navigation.h @@ -115,7 +115,7 @@ class Navigation final : public DOMEventTargetHelper { MOZ_CAN_RUN_SCRIPT bool FirePushReplaceReloadNavigateEvent( JSContext* aCx, NavigationType aNavigationType, nsIURI* aDestinationURL, bool aIsSameDocument, Maybe aUserInvolvement, - Element* aSourceElement, Maybe aFormDataEntryList, + Element* aSourceElement, already_AddRefed aFormDataEntryList, nsIStructuredCloneContainer* aNavigationAPIState, nsIStructuredCloneContainer* aClassicHistoryAPIState); @@ -157,7 +157,7 @@ class Navigation final : public DOMEventTargetHelper { JSContext* aCx, NavigationType aNavigationType, NavigationDestination* aDestination, UserNavigationInvolvement aUserInvolvement, Element* aSourceElement, - Maybe aFormDataEntryList, + already_AddRefed aFormDataEntryList, nsIStructuredCloneContainer* aClassicHistoryAPIState, const nsAString& aDownloadRequestFilename); diff --git a/netwerk/base/nsNetUtil.cpp b/netwerk/base/nsNetUtil.cpp index d3fd93a4b687..134a7f3cee39 100644 --- a/netwerk/base/nsNetUtil.cpp +++ b/netwerk/base/nsNetUtil.cpp @@ -2840,6 +2840,24 @@ bool NS_IsAboutSrcdoc(nsIURI* uri) { return spec.EqualsLiteral("about:srcdoc"); } +// https://fetch.spec.whatwg.org/#fetch-scheme +bool NS_IsFetchScheme(nsIURI* uri) { + for (const auto& scheme : { + "http", + "https", + "about", + "blob", + "data", + "file", + }) { + if (uri->SchemeIs(scheme)) { + return true; + } + } + + return false; +} + nsresult NS_GenerateHostPort(const nsCString& host, int32_t port, nsACString& hostLine) { if (strchr(host.get(), ':')) { diff --git a/netwerk/base/nsNetUtil.h b/netwerk/base/nsNetUtil.h index caa39ea104dd..8b689ee6e90f 100644 --- a/netwerk/base/nsNetUtil.h +++ b/netwerk/base/nsNetUtil.h @@ -946,6 +946,12 @@ bool NS_IsAboutBlankAllowQueryAndFragment(nsIURI* uri); */ bool NS_IsAboutSrcdoc(nsIURI* uri); +/** + * Test whether a URI has an "about", "blob", "data", "file", or an HTTP(S) + * scheme. + */ +bool NS_IsFetchScheme(nsIURI* uri); + nsresult NS_GenerateHostPort(const nsCString& host, int32_t port, nsACString& hostLine);