Bug 1962710 - Part 3: Fire NavigateEvent for regular navigations. r=smaug,necko-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D249242
This commit is contained in:
Andreas Farre
2025-05-19 09:01:13 +00:00
committed by afarre@mozilla.com
parent d2e41c93c1
commit e10114d313
12 changed files with 162 additions and 42 deletions

View File

@@ -7,6 +7,7 @@
#include "nsDocShell.h"
#include <algorithm>
#include "mozilla/dom/HTMLFormElement.h"
#ifdef XP_WIN
# include <process.h>
@@ -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<uint32_t> 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> document = GetDocument();
document &&
aLoadState->UserNavigationInvolvement() !=
UserNavigationInvolvement::BrowserUI &&
!document->IsInitialDocument() &&
!NS_IsAboutBlankAllowQueryAndFragment(document->GetDocumentURI()) &&
NS_IsFetchScheme(aLoadState->URI()) &&
document->NodePrincipal()->Subsumes(aLoadState->TriggeringPrincipal())) {
if (nsCOMPtr<nsPIDOMWindowInner> window = document->GetInnerWindow()) {
// Step 20.1
if (RefPtr<Navigation> navigation = window->Navigation()) {
AutoJSAPI jsapi;
if (jsapi.Init(window)) {
RefPtr<Element> sourceElement = aLoadState->GetSourceElement();
// Step 20.2
RefPtr<FormData> formData = aLoadState->GetFormDataEntryList();
// Step 20.3
RefPtr<nsIStructuredCloneContainer> navigationAPIStateForFiring =
aLoadState->GetNavigationAPIState();
if (!navigationAPIStateForFiring) {
navigationAPIStateForFiring = nullptr;
}
nsCOMPtr<nsIURI> 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<JS::Value> 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);

View File

@@ -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<Element> nsDocShellLoadState::GetSourceElement() const {
nsCOMPtr<Element> 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;
}

View File

@@ -34,6 +34,7 @@ class OriginAttributes;
template <typename, class>
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<mozilla::dom::Element> 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<mozilla::dom::LoadingSessionHistoryInfo>
mLoadingSessionHistoryInfo;
nsCOMPtr<nsIStructuredCloneContainer> mNavigationAPIState;
// Target for load, like _content, _blank etc.
nsString mTarget;
@@ -674,6 +679,10 @@ class nsDocShellLoadState final {
nsILoadInfo::NOT_INITIALIZED;
nsWeakPtr mSourceElement;
nsCOMPtr<nsIStructuredCloneContainer> mNavigationAPIState;
RefPtr<mozilla::dom::FormData> mFormDataEntryList;
};
#endif /* nsDocShellLoadState_h__ */

View File

@@ -307,9 +307,18 @@ already_AddRefed<FormData> FormData::Constructor(
const GlobalObject& aGlobal,
const Optional<NonNull<HTMLFormElement> >& aFormElement,
nsGenericHTMLElement* aSubmitter, ErrorResult& aRv) {
return Constructor(aGlobal.GetAsSupports(),
aFormElement.WasPassed() ? &aFormElement.Value() : nullptr,
aSubmitter, aRv);
}
/* static */
already_AddRefed<FormData> FormData::Constructor(
nsISupports* aGlobal, HTMLFormElement* aFormElement,
nsGenericHTMLElement* aSubmitter, ErrorResult& aRv) {
RefPtr<FormData> 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> 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> 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> 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();

View File

@@ -73,6 +73,10 @@ class FormData final : public nsISupports,
const Optional<NonNull<HTMLFormElement> >& aFormElement,
nsGenericHTMLElement* aSubmitter, ErrorResult& aRv);
static already_AddRefed<FormData> Constructor(
nsISupports* aGlobal, HTMLFormElement* aFormElement,
nsGenericHTMLElement* aSubmitter, ErrorResult& aRv);
void Append(const nsAString& aName, const nsAString& aValue,
ErrorResult& aRv);

View File

@@ -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<nsIPrincipal> nodePrincipal = NodePrincipal();
rv = container->OnLinkClickSync(this, loadState, false, nodePrincipal);

View File

@@ -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<const Encoding*>& 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;
}

View File

@@ -45,6 +45,7 @@ class HTMLFormSubmission {
static nsresult GetFromForm(HTMLFormElement* aForm,
nsGenericHTMLElement* aSubmitter,
NotNull<const Encoding*>& 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<const mozilla::Encoding*> mEncoding;
RefPtr<FormData> mFormData;
// Keep track of whether this form submission was user-initiated or not
bool mInitiatedFromUserInput;
};

View File

@@ -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<UserNavigationInvolvement> aUserInvolvement,
Element* aSourceElement, Maybe<const FormData&> aFormDataEntryList,
Element* aSourceElement, already_AddRefed<FormData> 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<const FormData&> aFormDataEntryList,
already_AddRefed<FormData> 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);

View File

@@ -115,7 +115,7 @@ class Navigation final : public DOMEventTargetHelper {
MOZ_CAN_RUN_SCRIPT bool FirePushReplaceReloadNavigateEvent(
JSContext* aCx, NavigationType aNavigationType, nsIURI* aDestinationURL,
bool aIsSameDocument, Maybe<UserNavigationInvolvement> aUserInvolvement,
Element* aSourceElement, Maybe<const FormData&> aFormDataEntryList,
Element* aSourceElement, already_AddRefed<FormData> 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<const FormData&> aFormDataEntryList,
already_AddRefed<FormData> aFormDataEntryList,
nsIStructuredCloneContainer* aClassicHistoryAPIState,
const nsAString& aDownloadRequestFilename);

View File

@@ -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(), ':')) {

View File

@@ -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);