diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp index 06242cbc7ae8..55b632a8d69e 100644 --- a/docshell/base/BrowsingContext.cpp +++ b/docshell/base/BrowsingContext.cpp @@ -16,7 +16,6 @@ #include "mozilla/dom/Element.h" #include "mozilla/dom/Location.h" #include "mozilla/dom/LocationBinding.h" -#include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/StructuredCloneTags.h" #include "mozilla/dom/UserActivationIPCUtils.h" #include "mozilla/dom/WindowBinding.h" @@ -472,14 +471,8 @@ void BrowsingContext::GetChildren(Children& aChildren) { // // See // https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name -BrowsingContext* BrowsingContext::FindWithName(const nsAString& aName) { - RefPtr requestingContext = this; - if (nsCOMPtr caller = do_GetInterface(GetEntryGlobal())) { - if (caller->GetBrowsingContext()) { - requestingContext = caller->GetBrowsingContext(); - } - } - +BrowsingContext* BrowsingContext::FindWithName( + const nsAString& aName, BrowsingContext& aRequestingContext) { BrowsingContext* found = nullptr; if (aName.IsEmpty()) { // You can't find a browsing context with an empty name. @@ -489,9 +482,9 @@ BrowsingContext* BrowsingContext::FindWithName(const nsAString& aName) { // a blank name. found = nullptr; } else if (IsSpecialName(aName)) { - found = FindWithSpecialName(aName, *requestingContext); + found = FindWithSpecialName(aName, aRequestingContext); } else if (BrowsingContext* child = - FindWithNameInSubtree(aName, *requestingContext)) { + FindWithNameInSubtree(aName, aRequestingContext)) { found = child; } else { BrowsingContext* current = this; @@ -505,7 +498,7 @@ BrowsingContext* BrowsingContext::FindWithName(const nsAString& aName) { // contexts in the same browsing context group. siblings = &mGroup->Toplevels(); } else if (parent->NameEquals(aName) && - requestingContext->CanAccess(parent) && + aRequestingContext.CanAccess(parent) && parent->IsTargetable()) { found = parent; break; @@ -519,7 +512,7 @@ BrowsingContext* BrowsingContext::FindWithName(const nsAString& aName) { } if (BrowsingContext* relative = - sibling->FindWithNameInSubtree(aName, *requestingContext)) { + sibling->FindWithNameInSubtree(aName, aRequestingContext)) { found = relative; // Breaks the outer loop parent = nullptr; @@ -533,7 +526,7 @@ BrowsingContext* BrowsingContext::FindWithName(const nsAString& aName) { // Helpers should perform access control checks, which means that we // only need to assert that we can access found. - MOZ_DIAGNOSTIC_ASSERT(!found || requestingContext->CanAccess(found)); + MOZ_DIAGNOSTIC_ASSERT(!found || aRequestingContext.CanAccess(found)); return found; } diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h index b5b029ff9afd..baf93f11ea88 100644 --- a/docshell/base/BrowsingContext.h +++ b/docshell/base/BrowsingContext.h @@ -259,7 +259,8 @@ class BrowsingContext : public nsWrapperCache, public BrowsingContextBase { // BrowsingContext::FindWithName(const nsAString&) is equivalent to // calling nsIDocShellTreeItem::FindItemWithName(aName, nullptr, // nullptr, false, ). - BrowsingContext* FindWithName(const nsAString& aName); + BrowsingContext* FindWithName(const nsAString& aName, + BrowsingContext& aRequestingContext); // Find a browsing context in this context's list of // children. Doesn't consider the special names, '_self', '_parent', diff --git a/docshell/base/BrowsingContextGroup.cpp b/docshell/base/BrowsingContextGroup.cpp index 53c7922a55fd..295ff8fea609 100644 --- a/docshell/base/BrowsingContextGroup.cpp +++ b/docshell/base/BrowsingContextGroup.cpp @@ -133,19 +133,6 @@ JSObject* BrowsingContextGroup::WrapObject(JSContext* aCx, return BrowsingContextGroup_Binding::Wrap(aCx, this, aGivenProto); } -static StaticRefPtr sChromeGroup; - -/* static */ -BrowsingContextGroup* BrowsingContextGroup::GetChromeGroup() { - MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); - if (!sChromeGroup && XRE_IsParentProcess()) { - sChromeGroup = new BrowsingContextGroup(); - ClearOnShutdown(&sChromeGroup); - } - - return sChromeGroup; -} - NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(BrowsingContextGroup, mContexts, mToplevels, mSubscribers, mCachedContexts) diff --git a/docshell/base/BrowsingContextGroup.h b/docshell/base/BrowsingContextGroup.h index 90efcfce3ff6..efaf125f3d26 100644 --- a/docshell/base/BrowsingContextGroup.h +++ b/docshell/base/BrowsingContextGroup.h @@ -108,8 +108,6 @@ class BrowsingContextGroup final : public nsWrapperCache { } } - static BrowsingContextGroup* GetChromeGroup(); - private: friend class CanonicalBrowsingContext; diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 636210ccc5b3..86128a92ae6c 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -2888,6 +2888,65 @@ static bool ItemIsActive(nsIDocShellTreeItem* aItem) { return false; } +NS_IMETHODIMP +nsDocShell::FindItemWithName(const nsAString& aName, + nsIDocShellTreeItem* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + bool aSkipTabGroup, + nsIDocShellTreeItem** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + // If we don't find one, we return NS_OK and a null result + *aResult = nullptr; + + if (aName.IsEmpty()) { + return NS_OK; + } + + if (aRequestor) { + // If aRequestor is not null we don't need to check special names, so + // just hand straight off to the search by actual name function. + return DoFindItemWithName(aName, aRequestor, aOriginalRequestor, + aSkipTabGroup, aResult); + } else { + // This is the entry point into the target-finding algorithm. Check + // for special names. This should only be done once, hence the check + // for a null aRequestor. + + nsCOMPtr foundItem; + if (aName.LowerCaseEqualsLiteral("_self")) { + foundItem = this; + } else if (aName.LowerCaseEqualsLiteral("_blank")) { + // Just return null. Caller must handle creating a new window with + // a blank name himself. + return NS_OK; + } else if (aName.LowerCaseEqualsLiteral("_parent")) { + GetInProcessSameTypeParent(getter_AddRefs(foundItem)); + if (!foundItem) { + foundItem = this; + } + } else if (aName.LowerCaseEqualsLiteral("_top")) { + GetInProcessSameTypeRootTreeItem(getter_AddRefs(foundItem)); + NS_ASSERTION(foundItem, "Must have this; worst case it's us!"); + } else { + // Do the search for item by an actual name. + DoFindItemWithName(aName, aRequestor, aOriginalRequestor, aSkipTabGroup, + getter_AddRefs(foundItem)); + } + + if (foundItem && !CanAccessItem(foundItem, aOriginalRequestor)) { + foundItem = nullptr; + } + + // DoFindItemWithName only returns active items and we don't check if + // the item is active for the special cases. + if (foundItem) { + foundItem.swap(*aResult); + } + return NS_OK; + } +} + void nsDocShell::AssertOriginAttributesMatchPrivateBrowsing() { // Chrome docshells must not have a private browsing OriginAttribute // Content docshells must maintain the equality: @@ -2900,6 +2959,64 @@ void nsDocShell::AssertOriginAttributesMatchPrivateBrowsing() { } } +nsresult nsDocShell::DoFindItemWithName(const nsAString& aName, + nsIDocShellTreeItem* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + bool aSkipTabGroup, + nsIDocShellTreeItem** aResult) { + // First we check our name. + if (mBrowsingContext->NameEquals(aName) && ItemIsActive(this) && + CanAccessItem(this, aOriginalRequestor)) { + NS_ADDREF(*aResult = this); + return NS_OK; + } + + // Second we check our children making sure not to ask a child if + // it is the aRequestor. +#ifdef DEBUG + nsresult rv = +#endif + FindChildWithName(aName, true, true, aRequestor, aOriginalRequestor, + aResult); + NS_ASSERTION(NS_SUCCEEDED(rv), + "FindChildWithName should not be failing here."); + if (*aResult) { + return NS_OK; + } + + // Third if we have a parent and it isn't the requestor then we + // should ask it to do the search. If it is the requestor we + // should just stop here and let the parent do the rest. If we + // don't have a parent, then we should ask the + // docShellTreeOwner to do the search. + nsCOMPtr parentAsTreeItem = + do_QueryInterface(GetAsSupports(mParent)); + if (parentAsTreeItem) { + if (parentAsTreeItem == aRequestor) { + return NS_OK; + } + + // If we have a same-type parent, respecting browser and app boundaries. + // NOTE: Could use GetInProcessSameTypeParent if the issues described in + // bug 1310344 are fixed. + if (!GetIsMozBrowser() && parentAsTreeItem->ItemType() == mItemType) { + return parentAsTreeItem->FindItemWithName(aName, this, aOriginalRequestor, + /* aSkipTabGroup = */ false, + aResult); + } + } + + // If we have a null parent or the parent is not of the same type, we need to + // give up on finding it in our tree, and start looking in our TabGroup. + nsCOMPtr window = GetWindow(); + if (window && !aSkipTabGroup) { + RefPtr tabGroup = window->TabGroup(); + tabGroup->FindItemWithName(aName, this, aOriginalRequestor, aResult); + } + + return NS_OK; +} + bool nsDocShell::IsSandboxedFrom(BrowsingContext* aTargetBC) { // If no target then not sandboxed. if (!aTargetBC) { @@ -8587,9 +8704,11 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState, MOZ_ASSERT(aLoadState, "need a load state!"); MOZ_ASSERT(!aLoadState->Target().IsEmpty(), "should have a target here!"); - nsresult rv = NS_OK; + nsresult rv; nsCOMPtr targetDocShell; + // Locate the target DocShell. + nsCOMPtr targetItem; // 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. @@ -8600,12 +8719,12 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState, aLoadState->Target().LowerCaseEqualsLiteral("_self") || aLoadState->Target().LowerCaseEqualsLiteral("_parent") || aLoadState->Target().LowerCaseEqualsLiteral("_top")) { - if (BrowsingContext* context = - mBrowsingContext->FindWithName(aLoadState->Target())) { - targetDocShell = context->GetDocShell(); - } + rv = FindItemWithName(aLoadState->Target(), nullptr, this, false, + getter_AddRefs(targetItem)); + NS_ENSURE_SUCCESS(rv, rv); } + targetDocShell = do_QueryInterface(targetItem); if (!targetDocShell) { // If the targetDocShell doesn't exist, then this is a new docShell // and we should consider this a TYPE_DOCUMENT load @@ -13464,6 +13583,35 @@ nsCommandManager* nsDocShell::GetCommandManager() { return mCommandManager; } +NS_IMETHODIMP +nsDocShell::GetIsOnlyToplevelInTabGroup(bool* aResult) { + MOZ_ASSERT(aResult); + + nsPIDOMWindowOuter* outer = GetWindow(); + MOZ_ASSERT(outer); + + // If we are not toplevel then we are not the only toplevel window in the + // tab group. + if (outer->GetInProcessScriptableParentOrNull()) { + *aResult = false; + return NS_OK; + } + + // If we have any other toplevel windows in our tab group, then we are not + // the only toplevel window in the tab group. + nsTArray toplevelWindows = + outer->TabGroup()->GetTopLevelWindows(); + if (toplevelWindows.Length() > 1) { + *aResult = false; + return NS_OK; + } + MOZ_ASSERT(toplevelWindows.Length() == 1); + MOZ_ASSERT(toplevelWindows[0] == outer); + + *aResult = true; + return NS_OK; +} + NS_IMETHODIMP nsDocShell::GetAwaitingLargeAlloc(bool* aResult) { MOZ_ASSERT(aResult); diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h index a7cec09dd668..f10249b45f84 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -942,6 +942,14 @@ class nsDocShell final : public nsDocLoader, MOZ_MUST_USE bool MaybeInitTiming(); void MaybeResetInitTiming(bool aReset); + // Separate function to do the actual name (i.e. not _top, _self etc.) + // searching for FindItemWithName. + nsresult DoFindItemWithName(const nsAString& aName, + nsIDocShellTreeItem* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + bool aSkipTabGroup, + nsIDocShellTreeItem** aResult); + // Convenience method for getting our parent docshell. Can return null already_AddRefed GetInProcessParentDocshell(); diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl index 2841d3a1abd8..7dd8692e3427 100644 --- a/docshell/base/nsIDocShell.idl +++ b/docshell/base/nsIDocShell.idl @@ -1055,6 +1055,20 @@ interface nsIDocShell : nsIDocShellTreeItem */ [infallible] attribute nsIDocShell_MetaViewportOverride metaViewportOverride; + /** + * This value is `true` if its corresponding unit of related browsing contexts + * (TabGroup) contains only 1 toplevel window, and that window is the outer + * window corresponding to this docshell. + * + * The value is `false` otherwise. This is the case if the docshell is an + * iframe, has window.opener set, or another window with window.opener + * referring to this window exists. + * + * If this value is `false`, it would be web content visible for a load + * occuring in this docshell to be performed within a different docshell. + */ + [infallible] readonly attribute boolean isOnlyToplevelInTabGroup; + /** * Returns `true` if this docshell was created due to a Large-Allocation * header, and has not seen the initiating load yet. diff --git a/docshell/base/nsIDocShellTreeItem.idl b/docshell/base/nsIDocShellTreeItem.idl index 000bbc873c74..97970cf7b157 100644 --- a/docshell/base/nsIDocShellTreeItem.idl +++ b/docshell/base/nsIDocShellTreeItem.idl @@ -92,6 +92,39 @@ interface nsIDocShellTreeItem : nsISupports [binaryname(InProcessSameTypeRootTreeItem)] readonly attribute nsIDocShellTreeItem sameTypeRootTreeItem; + /* + Returns the docShellTreeItem with the specified name. Search order is as + follows... + 1.) Check name of self, if it matches return it. + 2.) For each immediate child. + a.) Check name of child and if it matches return it. + b.) Ask the child to perform the check + i.) Do not ask a child if it is the aRequestor + ii.) Do not ask a child if it is of a different item type. + 3.) If there is a parent of the same item type ask parent to perform the check + a.) Do not ask parent if it is the aRequestor + 4.) If there is a tab group ask the tab group to perform the check + a.) Do not ask the tab group if aSkipTabGroup + b.) This should only be done if there is no parent of the same type. + + Return the child DocShellTreeItem with the specified name. + name - This is the name of the item that is trying to be found. + aRequestor - This is the object that is requesting the find. This + parameter is used to identify when the child is asking its parent to find + a child with the specific name. The parent uses this parameter to ensure + a resursive state does not occur by not again asking the requestor to find + a shell by the specified name. Inversely the child uses it to ensure it + does not ask its parent to do the search if its parent is the one that + asked it to search. Children also use this to test against the treeOwner; + aOriginalRequestor - The original treeitem that made the request, if any. + This is used to ensure that we don't run into cross-site issues. + aSkipTabGroup - Whether the tab group should be checked. + */ + nsIDocShellTreeItem findItemWithName(in AString name, + in nsIDocShellTreeItem aRequestor, + in nsIDocShellTreeItem aOriginalRequestor, + in bool aSkipTabGroup); + /* The owner of the DocShell Tree. This interface will be called upon when the docshell has things it needs to tell to the owner of the docshell. @@ -189,3 +222,4 @@ interface nsIDocShellTreeItem : nsISupports [noscript,nostdcall,notxpcom] Document getDocument(); [noscript,nostdcall,notxpcom] nsPIDOMWindowOuter getWindow(); }; + diff --git a/docshell/test/browser/browser.ini b/docshell/test/browser/browser.ini index c7ff9c1aec80..5b5f4b54322b 100644 --- a/docshell/test/browser/browser.ini +++ b/docshell/test/browser/browser.ini @@ -136,6 +136,8 @@ skip-if = true # Bug 1220415 [browser_click_link_within_view_source.js] [browser_browsingContext-01.js] [browser_browsingContext-02.js] +[browser_browsingContext-03.js] +skip-if = fission # Cross-process postMessage [browser_browsingContext-embedder.js] [browser_csp_uir.js] support-files = diff --git a/docshell/test/browser/browser_browsingContext-02.js b/docshell/test/browser/browser_browsingContext-02.js index e995b5875b4f..a712420a6641 100644 --- a/docshell/test/browser/browser_browsingContext-02.js +++ b/docshell/test/browser/browser_browsingContext-02.js @@ -110,12 +110,11 @@ add_task(async function() { // docShell. function findWithName(bc, name) { return content.SpecialPowers.spawn(bc, [bc, name], (bc, name) => { - return bc.findWithName(name); + return bc.findWithName(name, bc); }); } async function reachable(start, target) { - info(start.name, target.name); is( await findWithName(start, target.name), target, diff --git a/docshell/test/browser/browser_browsingContext-03.js b/docshell/test/browser/browser_browsingContext-03.js new file mode 100644 index 000000000000..aaa9cabf196f --- /dev/null +++ b/docshell/test/browser/browser_browsingContext-03.js @@ -0,0 +1,194 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function() { + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function(browser) { + const BASE1 = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://example.com" + ); + const BASE2 = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://test1.example.com" + ); + const URL = BASE1 + "onload_message.html"; + let sixth = BrowserTestUtils.waitForNewTab( + gBrowser, + URL + "#sixth", + true, + true + ); + await ContentTask.spawn( + browser, + { base1: BASE1, base2: BASE2 }, + async function({ base1, base2 }) { + let top = content; + top.name = "top"; + top.location.href += "#top"; + + let contexts = { + top: top.location.href, + first: base1 + "dummy_page.html#first", + third: base2 + "dummy_page.html#third", + second: base1 + "dummy_page.html#second", + fourth: base2 + "dummy_page.html#fourth", + fifth: base1 + "dummy_page.html#fifth", + sixth: base1 + "onload_message.html#sixth", + }; + + function addFrame(target, name) { + return content.SpecialPowers.spawn( + target, + [name, contexts[name]], + async (name, context) => { + let doc = this.content.document; + + let frame = doc.createElement("iframe"); + doc.body.appendChild(frame); + frame.name = name; + frame.src = context; + await new Promise(resolve => { + frame.addEventListener("load", resolve, { once: true }); + }); + return frame.browsingContext; + } + ); + } + + function addWindow(target, name) { + return content.SpecialPowers.spawn( + target, + [name, contexts[name]], + (name, context) => { + let win = this.content.open(context, name); + let bc = win && win.docShell.browsingContext; + + return new Promise(resolve => + this.content.addEventListener("message", () => resolve(bc)) + ); + } + ); + } + + // Generate all lists of length length with every combination of + // values in input + function* generate(input, length) { + let list = new Array(length); + + function* values(pos) { + if (pos >= list.length) { + yield list; + } else { + for (let v of input) { + list[pos] = v; + yield* values(pos + 1); + } + } + } + yield* values(0); + } + + // We're going to create a tree that looks like the + // follwing. + // + // top sixth + // / \ + // / \ / + // first second + // / \ / + // / \ + // third fourth - - - + // / + // / + // fifth + // + // The idea is to have one top level non-auxiliary browsing + // context, five nested, one top level auxiliary with an + // opener. Given that set of related browsing contexts we + // wish to confirm that targeting is semantically equivalent + // with how nsIDocShellTreeItem.findItemWithName works. The + // trick to ensure that is to give all frames the same name! + // and ensure that the find algorithms return the same nodes + // in the same order. + + let first = await addFrame(top, "first"); + let second = await addFrame(top, "second"); + let third = await addFrame(first, "third"); + let fourth = await addFrame(first, "fourth"); + let fifth = await addFrame(fourth, "fifth"); + let sixth = await addWindow(fourth, "sixth"); + + let browsingContexts = [ + BrowsingContext.getFromWindow(top), + first, + second, + third, + fourth, + fifth, + sixth, + ]; + let docShells = browsingContexts.map(context => context.docShell); + + ok( + top.docShell instanceof Ci.nsIDocShellTreeItem, + "When we remove nsIDocShellTreeItem this test should be removed" + ); + + // For every browsing context we generate all possible + // combinations of names for these browsing contexts using + // "dummy" and "target" as possible name. + for (let names of generate(["dummy", "target"], docShells.length)) { + for (let i = names.length - 1; i >= 0; --i) { + docShells[i].name = names[i]; + } + + for (let i = 0; i < docShells.length; ++i) { + let docShell = docShells[i].findItemWithName( + "target", + null, + docShells[i], + false + ); + let browsingContext = browsingContexts[i].findWithName( + "target", + browsingContexts[i] + ); + is( + docShell ? docShell.browsingContext : null, + browsingContext, + "findItemWithName should find same browsing context as findWithName" + ); + } + } + + for (let target of ["_self", "_top", "_parent", "_blank"]) { + for (let i = 0; i < docShells.length; ++i) { + let docShell = docShells[i].findItemWithName( + target, + null, + docShells[i], + false + ); + let browsingContext = browsingContexts[i].findWithName( + target, + browsingContexts[i] + ); + is( + docShell ? docShell.browsingContext : null, + browsingContext, + "findItemWithName should find same browsing context as findWithName for " + + target + ); + } + } + } + ); + + BrowserTestUtils.removeTab(await sixth); + } + ); +}); diff --git a/dom/base/TabGroup.cpp b/dom/base/TabGroup.cpp index 7af4257d3806..cb7da558e56e 100644 --- a/dom/base/TabGroup.cpp +++ b/dom/base/TabGroup.cpp @@ -204,6 +204,64 @@ void TabGroup::MaybeDestroy() { } } +nsresult TabGroup::FindItemWithName(const nsAString& aName, + nsIDocShellTreeItem* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + nsIDocShellTreeItem** aFoundItem) { + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(aFoundItem); + *aFoundItem = nullptr; + + MOZ_ASSERT(!aName.LowerCaseEqualsLiteral("_blank") && + !aName.LowerCaseEqualsLiteral("_top") && + !aName.LowerCaseEqualsLiteral("_parent") && + !aName.LowerCaseEqualsLiteral("_self")); + + for (nsPIDOMWindowOuter* outerWindow : mWindows) { + // Ignore non-toplevel windows + if (outerWindow->GetInProcessScriptableParentOrNull()) { + continue; + } + + nsCOMPtr docshell = outerWindow->GetDocShell(); + if (!docshell) { + continue; + } + + BrowsingContext* bc = outerWindow->GetBrowsingContext(); + if (!bc || !bc->IsTargetable()) { + continue; + } + + nsCOMPtr root; + docshell->GetInProcessSameTypeRootTreeItem(getter_AddRefs(root)); + MOZ_RELEASE_ASSERT(docshell == root); + if (root && aRequestor != root) { + root->FindItemWithName(aName, aRequestor, aOriginalRequestor, + /* aSkipTabGroup = */ true, aFoundItem); + if (*aFoundItem) { + break; + } + } + } + + return NS_OK; +} + +nsTArray TabGroup::GetTopLevelWindows() const { + MOZ_ASSERT(NS_IsMainThread()); + nsTArray array; + + for (nsPIDOMWindowOuter* outerWindow : mWindows) { + if (outerWindow->GetDocShell() && + !outerWindow->GetInProcessScriptableParentOrNull()) { + array.AppendElement(outerWindow); + } + } + + return array; +} + TabGroup::HashEntry::HashEntry(const nsACString* aKey) : nsCStringHashKey(aKey), mDocGroup(nullptr) {} diff --git a/dom/base/TabGroup.h b/dom/base/TabGroup.h index 0e9bc9520028..77efc859d154 100644 --- a/dom/base/TabGroup.h +++ b/dom/base/TabGroup.h @@ -101,6 +101,22 @@ class TabGroup final : public SchedulerGroup, // Count with 'aActiveOnly' = true uint32_t Count(bool aActiveOnly = false) const; + // Returns the nsIDocShellTreeItem with the given name, searching each of the + // docShell trees which are within this TabGroup. It will pass itself as + // aRequestor to each docShellTreeItem which it asks to search for the name, + // and will not search the docShellTreeItem which is passed as aRequestor. + // + // This method is used in order to correctly namespace named windows based on + // their unit of related browsing contexts. + // + // It is illegal to pass in the special case-insensitive names "_blank", + // "_self", "_parent" or "_top", as those should be handled elsewhere. + nsresult FindItemWithName(const nsAString& aName, + nsIDocShellTreeItem* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + nsIDocShellTreeItem** aFoundItem); + + nsTArray GetTopLevelWindows() const; const nsTArray& GetWindows() { return mWindows; } // This method is always safe to call off the main thread. The nsIEventTarget diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index a3cd66e2dcd1..6300598c88aa 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -45,8 +45,6 @@ #include "mozilla/DebugOnly.h" #include "mozilla/LoadInfo.h" #include "mozilla/dom/BlobURLProtocolHandler.h" -#include "mozilla/dom/BrowsingContext.h" -#include "mozilla/dom/BrowsingContextGroup.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/CustomElementRegistry.h" @@ -9245,11 +9243,7 @@ bool nsContentUtils::AttemptLargeAllocationLoad(nsIHttpChannel* aChannel) { } nsIDocShell* docShell = outer->GetDocShell(); - BrowsingContext* browsingContext = docShell->GetBrowsingContext(); - bool isOnlyToplevelBrowsingContext = - !browsingContext->GetParent() && - browsingContext->Group()->Toplevels().Length() == 1; - if (!isOnlyToplevelBrowsingContext) { + if (!docShell->GetIsOnlyToplevelInTabGroup()) { outer->SetLargeAllocStatus(LargeAllocStatus::NOT_ONLY_TOPLEVEL_IN_TABGROUP); return false; } diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp index 2ad885c5d18b..5c64191a88f4 100644 --- a/dom/base/nsGlobalWindowOuter.cpp +++ b/dom/base/nsGlobalWindowOuter.cpp @@ -4026,6 +4026,13 @@ bool nsGlobalWindowOuter::DispatchResizeEvent(const CSSIntSize& aSize) { return target->DispatchEvent(*domEvent, CallerType::System, IgnoreErrors()); } +static already_AddRefed GetCallerDocShellTreeItem() { + nsCOMPtr callerWebNav = do_GetInterface(GetEntryGlobal()); + nsCOMPtr callerItem = do_QueryInterface(callerWebNav); + + return callerItem.forget(); +} + bool nsGlobalWindowOuter::WindowExists(const nsAString& aName, bool aForceNoOpener, bool aLookForCallerOnJSStack) { @@ -4037,7 +4044,20 @@ bool nsGlobalWindowOuter::WindowExists(const nsAString& aName, aName.LowerCaseEqualsLiteral("_parent"); } - return !!mBrowsingContext->FindWithName(aName); + nsCOMPtr caller; + if (aLookForCallerOnJSStack) { + caller = GetCallerDocShellTreeItem(); + } + + if (!caller) { + caller = mDocShell; + } + + nsCOMPtr namedItem; + mDocShell->FindItemWithName(aName, nullptr, caller, + /* aSkipTabGroup = */ false, + getter_AddRefs(namedItem)); + return namedItem != nullptr; } already_AddRefed nsGlobalWindowOuter::GetMainWidget() { diff --git a/dom/chrome-webidl/BrowsingContext.webidl b/dom/chrome-webidl/BrowsingContext.webidl index a18419bb1cf9..ce9fe6890bd7 100644 --- a/dom/chrome-webidl/BrowsingContext.webidl +++ b/dom/chrome-webidl/BrowsingContext.webidl @@ -12,7 +12,7 @@ interface BrowsingContext { static BrowsingContext? getFromWindow(WindowProxy window); BrowsingContext? findChildWithName(DOMString name, BrowsingContext accessor); - BrowsingContext? findWithName(DOMString name); + BrowsingContext? findWithName(DOMString name, BrowsingContext accessor); readonly attribute DOMString name; diff --git a/toolkit/components/browser/nsWebBrowser.cpp b/toolkit/components/browser/nsWebBrowser.cpp index c0ca4df0b110..d0040f65d721 100644 --- a/toolkit/components/browser/nsWebBrowser.cpp +++ b/toolkit/components/browser/nsWebBrowser.cpp @@ -405,6 +405,20 @@ nsWebBrowser::GetInProcessSameTypeRootTreeItem( return NS_OK; } +NS_IMETHODIMP +nsWebBrowser::FindItemWithName(const nsAString& aName, + nsIDocShellTreeItem* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + bool aSkipTabGroup, + nsIDocShellTreeItem** aResult) { + NS_ENSURE_STATE(mDocShell); + NS_ASSERTION(mDocShellTreeOwner, + "This should always be set when in this situation"); + + return mDocShell->FindItemWithName(aName, aRequestor, aOriginalRequestor, + aSkipTabGroup, aResult); +} + dom::Document* nsWebBrowser::GetDocument() { return mDocShell ? mDocShell->GetDocument() : nullptr; } diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.cpp b/toolkit/components/windowwatcher/nsWindowWatcher.cpp index a0b596156048..32fb3e664be7 100644 --- a/toolkit/components/windowwatcher/nsWindowWatcher.cpp +++ b/toolkit/components/windowwatcher/nsWindowWatcher.cpp @@ -26,8 +26,6 @@ #include "nsIDocShellTreeItem.h" #include "nsIDocShellTreeOwner.h" #include "nsIDocumentLoader.h" -#include "mozilla/dom/BrowsingContext.h" -#include "mozilla/dom/BrowsingContextGroup.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/DocumentInlines.h" #include "nsIDOMWindow.h" @@ -1612,17 +1610,34 @@ nsWindowWatcher::GetWindowByName(const nsAString& aTargetName, *aResult = nullptr; - BrowsingContext* currentContext = - aCurrentWindow - ? nsPIDOMWindowOuter::From(aCurrentWindow)->GetBrowsingContext() - : nullptr; + nsPIDOMWindowOuter* currentWindow = + aCurrentWindow ? nsPIDOMWindowOuter::From(aCurrentWindow) : nullptr; - RefPtr context = - GetBrowsingContextByName(aTargetName, false, currentContext); + nsCOMPtr treeItem; - if (context) { - *aResult = context->GetDOMWindow(); - MOZ_ASSERT(*aResult); + nsCOMPtr startItem; + GetWindowTreeItem(currentWindow, getter_AddRefs(startItem)); + if (startItem) { + // Note: original requestor is null here, per idl comments + startItem->FindItemWithName(aTargetName, nullptr, nullptr, + /* aSkipTabGroup = */ false, + getter_AddRefs(treeItem)); + } else { + if (aTargetName.LowerCaseEqualsLiteral("_blank") || + aTargetName.LowerCaseEqualsLiteral("_top") || + aTargetName.LowerCaseEqualsLiteral("_parent") || + aTargetName.LowerCaseEqualsLiteral("_self")) { + return NS_OK; + } + + // Note: original requestor is null here, per idl comments + Unused << TabGroup::GetChromeTabGroup()->FindItemWithName( + aTargetName, nullptr, nullptr, getter_AddRefs(treeItem)); + } + + if (treeItem) { + nsCOMPtr domWindow = treeItem->GetWindow(); + domWindow.forget(aResult); } return NS_OK; @@ -2013,6 +2028,25 @@ int32_t nsWindowWatcher::WinHasOption(const nsACString& aOptions, return found; } +already_AddRefed nsWindowWatcher::GetCallerTreeItem( + nsIDocShellTreeItem* aParentItem) { + nsCOMPtr callerWebNav = do_GetInterface(GetEntryGlobal()); + nsCOMPtr callerItem = do_QueryInterface(callerWebNav); + if (!callerItem) { + callerItem = aParentItem; + } + + return callerItem.forget(); +} + +BrowsingContext* nsWindowWatcher::GetCallerBrowsingContext( + BrowsingContext* aParentItem) { + if (nsCOMPtr caller = do_GetInterface(GetEntryGlobal())) { + return caller->GetBrowsingContext(); + } + return aParentItem; +} + already_AddRefed nsWindowWatcher::GetBrowsingContextByName( const nsAString& aName, bool aForceNoOpener, BrowsingContext* aCurrentContext) { @@ -2029,8 +2063,12 @@ already_AddRefed nsWindowWatcher::GetBrowsingContextByName( } } - RefPtr currentContext(aCurrentContext); - if (!currentContext) { + RefPtr caller = GetCallerBrowsingContext(aCurrentContext); + + RefPtr foundContext; + if (aCurrentContext) { + foundContext = aCurrentContext->FindWithName(aName, *caller); + } else { if (aName.LowerCaseEqualsLiteral("_blank") || aName.LowerCaseEqualsLiteral("_top") || aName.LowerCaseEqualsLiteral("_parent") || @@ -2040,14 +2078,15 @@ already_AddRefed nsWindowWatcher::GetBrowsingContextByName( // If we are looking for an item and we don't have a docshell we are // checking on, let's just look in the chrome tab group! - currentContext = - BrowsingContextGroup::GetChromeGroup()->Toplevels().SafeElementAt(0); + nsCOMPtr foundItem; + Unused << TabGroup::GetChromeTabGroup()->FindItemWithName( + aName, nullptr, caller ? caller->GetDocShell() : nullptr, + getter_AddRefs(foundItem)); + if (foundItem) { + foundContext = foundItem->GetBrowsingContext(); + } } - RefPtr foundContext; - if (currentContext) { - foundContext = currentContext->FindWithName(aName); - } return foundContext.forget(); } diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.h b/toolkit/components/windowwatcher/nsWindowWatcher.h index b69a14f1521e..7d2e2a4abd94 100644 --- a/toolkit/components/windowwatcher/nsWindowWatcher.h +++ b/toolkit/components/windowwatcher/nsWindowWatcher.h @@ -66,6 +66,14 @@ class nsWindowWatcher : public nsIWindowWatcher, nsWatcherWindowEntry* FindWindowEntry(mozIDOMWindowProxy* aWindow); nsresult RemoveWindow(nsWatcherWindowEntry* aInfo); + // Get the caller tree item. Look on the JS stack, then fall back + // to the parent if there's nothing there. + already_AddRefed GetCallerTreeItem( + nsIDocShellTreeItem* aParentItem); + + mozilla::dom::BrowsingContext* GetCallerBrowsingContext( + mozilla::dom::BrowsingContext* aParent); + // Will first look for a caller on the JS stack, and then fall back on // aCurrentContext if it can't find one. // It also knows to not look for things if aForceNoOpener is set. diff --git a/toolkit/modules/E10SUtils.jsm b/toolkit/modules/E10SUtils.jsm index af15de4f7202..7803a4e748c5 100644 --- a/toolkit/modules/E10SUtils.jsm +++ b/toolkit/modules/E10SUtils.jsm @@ -659,14 +659,11 @@ var E10SUtils = { // to change processes, we want to load into a new process so that we can throw // this one out. We don't want to move into a new process if we have post data, // because we would accidentally throw out that data. - let isOnlyToplevelBrowsingContext = - !aDocShell.browsingContext.parent && - aDocShell.browsingContext.group.getToplevels().length == 1; if ( !aHasPostData && Services.appinfo.remoteType == LARGE_ALLOCATION_REMOTE_TYPE && !aDocShell.awaitingLargeAlloc && - isOnlyToplevelBrowsingContext + aDocShell.isOnlyToplevelInTabGroup ) { return false; }