From 6abcdc2cc2fad55eeb028fe0f09ad1497ca3ca5d Mon Sep 17 00:00:00 2001 From: Cameron Kaiser Date: Thu, 20 Sep 2018 21:09:22 -0700 Subject: [PATCH] closes #522: rel=noopener M1222516 M1358469 M1419960 --- accessible/generic/ImageAccessible.cpp | 2 + docshell/base/nsDocShell.cpp | 80 ++++++++++++++++--- dom/base/nsGlobalWindow.cpp | 39 +++++++-- dom/base/nsGlobalWindow.h | 21 +++-- dom/base/nsPIDOMWindow.h | 9 ++- dom/html/HTMLAnchorElement.cpp | 1 + dom/workers/ServiceWorkerClients.cpp | 1 + .../windowwatcher/nsPIWindowWatcher.idl | 12 ++- .../windowwatcher/nsWindowWatcher.cpp | 8 +- .../windowwatcher/nsWindowWatcher.h | 1 + 10 files changed, 147 insertions(+), 27 deletions(-) diff --git a/accessible/generic/ImageAccessible.cpp b/accessible/generic/ImageAccessible.cpp index cd9a7f443..1349d7751 100644 --- a/accessible/generic/ImageAccessible.cpp +++ b/accessible/generic/ImageAccessible.cpp @@ -138,6 +138,8 @@ ImageAccessible::DoAction(uint8_t aIndex) nsCOMPtr tmp; return NS_SUCCEEDED(piWindow->Open(spec, EmptyString(), EmptyString(), + /* aLoadInfo = */ nullptr, + /* aForceNoOpener = */ false, getter_AddRefs(tmp))); } diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 6baa001a7..9cc2a9fb2 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -9592,9 +9592,20 @@ nsDocShell::InternalLoad(nsIURI* aURI, if (aWindowTarget && *aWindowTarget) { // Locate the target DocShell. nsCOMPtr targetItem; - rv = FindItemWithName(aWindowTarget, nullptr, this, - getter_AddRefs(targetItem)); - NS_ENSURE_SUCCESS(rv, rv); + nsDependentString name(aWindowTarget); + // Only _self, _parent, and _top are supported in noopener case. But we + // have to be careful to not apply that to the noreferrer case. See bug + // 1358469. + bool allowNamedTarget = !(aFlags & INTERNAL_LOAD_FLAGS_NO_OPENER) || + (aFlags & INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER); + if (allowNamedTarget || + name.LowerCaseEqualsLiteral("_self") || + name.LowerCaseEqualsLiteral("_parent") || + name.LowerCaseEqualsLiteral("_top")) { + rv = FindItemWithName(aWindowTarget, nullptr, this, + getter_AddRefs(targetItem)); + NS_ENSURE_SUCCESS(rv, rv); + } targetDocShell = do_QueryInterface(targetItem); // If the targetDocShell doesn't exist, then this is a new docShell @@ -9737,11 +9748,60 @@ nsDocShell::InternalLoad(nsIURI* aURI, NS_ENSURE_TRUE(win, NS_ERROR_NOT_AVAILABLE); nsDependentString name(aWindowTarget); - nsCOMPtr newWin; nsAutoCString spec; if (aURI) { aURI->GetSpec(spec); } + // If we are a noopener load, we just hand the whole thing over to our + // window. + if (aFlags & INTERNAL_LOAD_FLAGS_NO_OPENER) { + // Various asserts that we know to hold because NO_OPENER loads can only + // happen for links. + MOZ_ASSERT(!aLoadReplace); + /* MOZ_ASSERT(aPrincipalToInherit == aTriggeringPrincipal); */ + MOZ_ASSERT(aFlags == INTERNAL_LOAD_FLAGS_NO_OPENER || + aFlags == (INTERNAL_LOAD_FLAGS_NO_OPENER | + INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER)); + MOZ_ASSERT(!aPostData); + MOZ_ASSERT(!aHeadersData); + MOZ_ASSERT(aLoadType == LOAD_LINK); + MOZ_ASSERT(!aSHEntry); + MOZ_ASSERT(aFirstParty); // Windowwatcher will assume this. + + nsCOMPtr loadInfo; + rv = CreateLoadInfo(getter_AddRefs(loadInfo)); + if (NS_FAILED(rv)) { + return rv; + } + + // Set up our loadinfo so it will do the load as much like we would have + // as possible. + loadInfo->SetReferrer(aReferrer); + loadInfo->SetReferrerPolicy(aReferrerPolicy); + loadInfo->SetSendReferrer(!(aFlags & + INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER)); + loadInfo->SetOriginalURI(aOriginalURI); + loadInfo->SetLoadReplace(aLoadReplace); + loadInfo->SetOwner(loadingPrincipal); // SetTriggeringPrincipal + loadInfo->SetInheritOwner( /* SetInheritPrincipal _INHERIT_PRINCIPAL */ + !!(aFlags & INTERNAL_LOAD_FLAGS_INHERIT_OWNER)); + // Explicit principal because we do not want any guesses as to what the + // principal to inherit is: it should be aTriggeringPrincipal. + loadInfo->SetOwnerIsExplicit(true); // SetPrincipalIsExplicit(true); + loadInfo->SetLoadType(ConvertLoadTypeToDocShellLoadInfo(LOAD_LINK)); + + nsCOMPtr newWin; + rv = win->Open(NS_ConvertUTF8toUTF16(spec), + name, // window name + EmptyString(), // Features + loadInfo, + true, // aForceNoOpener + getter_AddRefs(newWin)); + MOZ_ASSERT(!newWin); + return rv; + } + + nsCOMPtr newWin; rv = win->OpenNoNavigate(NS_ConvertUTF8toUTF16(spec), name, // window name EmptyString(), // Features @@ -9756,11 +9816,6 @@ nsDocShell::InternalLoad(nsIURI* aURI, if (!newDoc || newDoc->IsInitialDocument()) { isNewWindow = true; aFlags |= INTERNAL_LOAD_FLAGS_FIRST_LOAD; - - // set opener object to null for noreferrer - if (aFlags & INTERNAL_LOAD_FLAGS_NO_OPENER) { - piNewWin->SetOpenerWindow(nullptr, false); - } } } @@ -13497,11 +13552,16 @@ nsDocShell::OnLinkClickSync(nsIContent* aContent, aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, referrer); nsWhitespaceTokenizerTemplate tok(referrer); while (tok.hasMoreTokens()) { - if (tok.nextToken().LowerCaseEqualsLiteral("noreferrer")) { + const nsAString& token = tok.nextToken(); + if (token.LowerCaseEqualsLiteral("noreferrer")) { flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER | INTERNAL_LOAD_FLAGS_NO_OPENER; + // We now have all the flags we could possibly have, so just stop. break; } + if (token.LowerCaseEqualsLiteral("noopener")) { + flags |= INTERNAL_LOAD_FLAGS_NO_OPENER; + } } } diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 747d6d941..1de0a581a 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -7677,9 +7677,11 @@ nsGlobalWindow::Open(const nsAString& aUrl, const nsAString& aName, nsresult nsGlobalWindow::Open(const nsAString& aUrl, const nsAString& aName, - const nsAString& aOptions, nsPIDOMWindow **_retval) + const nsAString& aOptions, nsIDocShellLoadInfo* aLoadInfo, + bool aForceNoOpener, nsPIDOMWindow **_retval) { - FORWARD_TO_OUTER(Open, (aUrl, aName, aOptions, _retval), + FORWARD_TO_OUTER(Open, (aUrl, aName, aOptions, aLoadInfo, aForceNoOpener, + _retval), NS_ERROR_NOT_INITIALIZED); nsCOMPtr window; nsresult rv = OpenInternal(aUrl, aName, aOptions, @@ -7689,6 +7691,8 @@ nsGlobalWindow::Open(const nsAString& aUrl, const nsAString& aName, false, // aDoJSFixups true, // aNavigate nullptr, nullptr, // No args + aLoadInfo, + aForceNoOpener, GetPrincipal(), // aCalleePrincipal nullptr, // aJSCallerContext getter_AddRefs(window)); @@ -7710,6 +7714,8 @@ nsGlobalWindow::OpenJS(const nsAString& aUrl, const nsAString& aName, true, // aDoJSFixups true, // aNavigate nullptr, nullptr, // No args + nullptr, // aLoadInfo + false, // aForceNoOpener GetPrincipal(), // aCalleePrincipal nsContentUtils::GetCurrentJSContext(), // aJSCallerContext _retval); @@ -7730,6 +7736,8 @@ nsGlobalWindow::OpenDialog(const nsAString& aUrl, const nsAString& aName, false, // aDoJSFixups true, // aNavigate nullptr, aExtraArgument, // Arguments + nullptr, // aLoadInfo + false, // aForceNoOpener GetPrincipal(), // aCalleePrincipal nullptr, // aJSCallerContext _retval); @@ -7750,6 +7758,8 @@ nsGlobalWindow::OpenNoNavigate(const nsAString& aUrl, false, // aDoJSFixups false, // aNavigate nullptr, nullptr, // No args + nullptr, // aLoadInfo + false, // aForceNoOpener GetPrincipal(), // aCalleePrincipal nullptr, // aJSCallerContext _retval); @@ -7780,6 +7790,8 @@ nsGlobalWindow::OpenDialogOuter(JSContext* aCx, const nsAString& aUrl, false, // aDoJSFixups true, // aNavigate argvArray, nullptr, // Arguments + nullptr, // aLoadInfo + false, // aForceNoOpener GetPrincipal(), // aCalleePrincipal aCx, // aJSCallerContext getter_AddRefs(dialog)); @@ -8860,6 +8872,8 @@ nsGlobalWindow::ShowModalDialogOuter(const nsAString& aUrl, nsIVariant* aArgumen true, // aDoJSFixups true, // aNavigate nullptr, argHolder, // args + nullptr, // aLoadInfo + false, // aForceNoOpener GetPrincipal(), // aCalleePrincipal nullptr, // aJSCallerContext getter_AddRefs(dlgWin)); @@ -11294,6 +11308,8 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName, bool aDoJSFixups, bool aNavigate, nsIArray *argv, nsISupports *aExtraArgument, + nsIDocShellLoadInfo* aLoadInfo, + bool aForceNoOpener, nsIPrincipal *aCalleePrincipal, JSContext *aJSCallerContext, nsIDOMWindow **aReturn) @@ -11335,16 +11351,24 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName, nsIPrincipal::APP_STATUS_INSTALLED; } - bool forceNoOpener = false; + nsAutoCString options; + bool forceNoOpener = aForceNoOpener; // Unlike other window flags, "noopener" comes from splitting on commas with // HTML whitespace trimming... nsCharSeparatedTokenizerTemplate tok( aOptions, ','); while (tok.hasMoreTokens()) { - if (tok.nextToken().EqualsLiteral("noopener")) { + auto nextTok = tok.nextToken(); + if (nextTok.EqualsLiteral("noopener")) { forceNoOpener = true; - break; + continue; } + // Want to create a copy of the options without 'noopener' because having + // 'noopener' in the options affects other window features. + if (!options.IsEmpty()) { + options.Append(','); + } + AppendUTF16toUTF8(nextTok, options); } // XXXbz When this gets fixed to not use LegacyIsCallerNativeCode() @@ -11406,10 +11430,9 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName, do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); NS_ENSURE_TRUE(wwatch, rv); - NS_ConvertUTF16toUTF8 options(aOptions); NS_ConvertUTF16toUTF8 name(aName); - const char *options_ptr = aOptions.IsEmpty() ? nullptr : options.get(); + const char *options_ptr = options.IsEmpty() ? nullptr : options.get(); const char *name_ptr = aName.IsEmpty() ? nullptr : name.get(); nsCOMPtr pwwatch(do_QueryInterface(wwatch)); @@ -11436,6 +11459,7 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName, aDialog, aNavigate, nullptr, argv, isPopupSpamWindow, forceNoOpener, + aLoadInfo, getter_AddRefs(domReturn)); } else { // Force a system caller here so that the window watcher won't screw us @@ -11457,6 +11481,7 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName, aDialog, aNavigate, nullptr, aExtraArgument, isPopupSpamWindow, forceNoOpener, + aLoadInfo, getter_AddRefs(domReturn)); } diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index 640d081c8..e6361d7e9 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -901,7 +901,10 @@ public: const nsAString& aOptions, mozilla::ErrorResult& aError); nsresult Open(const nsAString& aUrl, const nsAString& aName, - const nsAString& aOptions, nsPIDOMWindow **_retval) override; + const nsAString& aOptions, + nsIDocShellLoadInfo* aLoadInfo, + bool aForceNoOpener, + nsPIDOMWindow **_retval) override; mozilla::dom::Navigator* GetNavigator(mozilla::ErrorResult& aError); nsIDOMNavigator* GetNavigator() override; nsIDOMOfflineResourceList* GetApplicationCache(mozilla::ErrorResult& aError); @@ -1380,15 +1383,21 @@ private: * three args, if present, will be aUrl, aName, and aOptions. So this * param only matters if there are more than 3 arguments. * - * @param argc The number of arguments in argv. - * * @param aExtraArgument Another way to pass arguments in. This is mutually - * exclusive with the argv/argc approach. + * exclusive with the argv approach. + * + * @param aLoadInfo to be passed on along to the windowwatcher. + * + * @param aForceNoOpener if true, will act as if "noopener" were passed in + * aOptions, but without affecting any other window + * features. * * @param aJSCallerContext The calling script's context. This must be null * when aCalledNoScript is true. * - * @param aReturn [out] The window that was opened, if any. + * @param aReturn [out] The window that was opened, if any. Will be null if + * aForceNoOpener is true of if aOptions contains + * "noopener". * * Outer windows only. */ @@ -1402,6 +1411,8 @@ private: bool aNavigate, nsIArray *argv, nsISupports *aExtraArgument, + nsIDocShellLoadInfo* aLoadInfo, + bool aForceNoOpener, nsIPrincipal *aCalleePrincipal, JSContext *aJSCallerContext, nsIDOMWindow **aReturn); diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h index 2dab484fd..5cad2bce5 100644 --- a/dom/base/nsPIDOMWindow.h +++ b/dom/base/nsPIDOMWindow.h @@ -25,6 +25,7 @@ class nsIArray; class nsIContent; class nsICSSDeclaration; class nsIDocShell; +class nsIDocShellLoadInfo; class nsIDocument; class nsIIdleObserver; class nsIScriptTimeoutHandler; @@ -795,8 +796,14 @@ public: virtual already_AddRefed GetSelection() = 0; virtual already_AddRefed GetOpener() = 0; virtual already_AddRefed GetFrames() = 0; + // aLoadInfo will be passed on through to the windowwatcher. + // aForceNoOpener will act just like a "noopener" feature in aOptions except + // will not affect any other window features. virtual nsresult Open(const nsAString& aUrl, const nsAString& aName, - const nsAString& aOptions, nsPIDOMWindow **_retval) = 0; + const nsAString& aOptions, + nsIDocShellLoadInfo* aLoadInfo, + bool aForceNoOpener, + nsPIDOMWindow **_retval) = 0; virtual nsresult OpenDialog(const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions, nsISupports* aExtraArgument, nsIDOMWindow** _retval) = 0; diff --git a/dom/html/HTMLAnchorElement.cpp b/dom/html/HTMLAnchorElement.cpp index b71d5a43b..19ea1a39d 100644 --- a/dom/html/HTMLAnchorElement.cpp +++ b/dom/html/HTMLAnchorElement.cpp @@ -42,6 +42,7 @@ ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 2); // static const DOMTokenListSupportedToken HTMLAnchorElement::sSupportedRelValues[] = { "noreferrer", + "noopener", nullptr }; diff --git a/dom/workers/ServiceWorkerClients.cpp b/dom/workers/ServiceWorkerClients.cpp index d20db79b8..5c9f59f79 100644 --- a/dom/workers/ServiceWorkerClients.cpp +++ b/dom/workers/ServiceWorkerClients.cpp @@ -614,6 +614,7 @@ private: // opener anyway, and we _do_ want the returned // window. /* aForceNoOpener = */ false, + /* aLoadInfo = */ nullptr, getter_AddRefs(newWindow)); nsCOMPtr pwindow = do_QueryInterface(newWindow); pwindow.forget(aWindow); diff --git a/embedding/components/windowwatcher/nsPIWindowWatcher.idl b/embedding/components/windowwatcher/nsPIWindowWatcher.idl index 44ac81111..d98dd4c87 100644 --- a/embedding/components/windowwatcher/nsPIWindowWatcher.idl +++ b/embedding/components/windowwatcher/nsPIWindowWatcher.idl @@ -16,8 +16,9 @@ interface nsIWebBrowserChrome; interface nsIDocShellTreeItem; interface nsIArray; interface nsITabParent; +interface nsIDocShellLoadInfo; -[uuid(0f2d9d75-c46b-4114-802e-83b4655e61d2)] +[uuid(0f2d9d75-c46b-4114-802e-83b4655e61d3)] interface nsPIWindowWatcher : nsISupports { @@ -62,6 +63,12 @@ interface nsPIWindowWatcher : nsISupports looking for existing windows with the given name, not setting an opener on the newly opened window, and returning null from this method. + @param aLoadInfo if aNavigate is true, this allows the caller to pass in + an nsIDocShellLoadInfo to use for the navigation. + Callers can pass in null if they want the windowwatcher + to just construct a loadinfo itself. If aNavigate is + false, this argument is ignored. + @return the new window @note This method may examine the JS context stack for purposes of @@ -78,7 +85,8 @@ interface nsPIWindowWatcher : nsISupports in boolean aNavigate, in nsITabParent aOpeningTab, in nsISupports aArgs, in boolean aIsPopupSpam, - in boolean aForceNoOpener); + in boolean aForceNoOpener, + in nsIDocShellLoadInfo aLoadInfo); /** * Find a named docshell tree item amongst all windows registered diff --git a/embedding/components/windowwatcher/nsWindowWatcher.cpp b/embedding/components/windowwatcher/nsWindowWatcher.cpp index 89d1d730c..afeabe5d6 100644 --- a/embedding/components/windowwatcher/nsWindowWatcher.cpp +++ b/embedding/components/windowwatcher/nsWindowWatcher.cpp @@ -370,6 +370,7 @@ nsWindowWatcher::OpenWindow(nsIDOMWindow* aParent, /* navigate = */ true, nullptr, argv, /* aIsPopupSpam = */ false, /* aForceNoOpener = */ false, + /* aLoadInfo = */ nullptr, aResult); } @@ -430,6 +431,7 @@ nsWindowWatcher::OpenWindow2(nsIDOMWindow* aParent, nsISupports* aArguments, bool aIsPopupSpam, bool aForceNoOpener, + nsIDocShellLoadInfo* aLoadInfo, nsIDOMWindow** aResult) { nsCOMPtr argv = ConvertArgsToArray(aArguments); @@ -452,6 +454,7 @@ nsWindowWatcher::OpenWindow2(nsIDOMWindow* aParent, aNavigate, aOpeningTab, argv, aIsPopupSpam, aForceNoOpener, + aLoadInfo, aResult); } @@ -467,6 +470,7 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow* aParent, nsIArray* aArgv, bool aIsPopupSpam, bool aForceNoOpener, + nsIDocShellLoadInfo* aLoadInfo, nsIDOMWindow** aResult) { nsresult rv = NS_OK; @@ -967,8 +971,8 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow* aParent, } } - nsCOMPtr loadInfo; - if (uriToLoad && aNavigate) { + nsCOMPtr loadInfo = aLoadInfo; + if (uriToLoad && aNavigate && !loadInfo) { newDocShell->CreateLoadInfo(getter_AddRefs(loadInfo)); NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE); diff --git a/embedding/components/windowwatcher/nsWindowWatcher.h b/embedding/components/windowwatcher/nsWindowWatcher.h index d683ae5da..21e78aabc 100644 --- a/embedding/components/windowwatcher/nsWindowWatcher.h +++ b/embedding/components/windowwatcher/nsWindowWatcher.h @@ -88,6 +88,7 @@ protected: nsIArray* aArgv, bool aIsPopupSpam, bool aForceNoOpener, + nsIDocShellLoadInfo* aLoadInfo, nsIDOMWindow** aResult); static nsresult URIfromURL(const char* aURL,