Bug 1580766 - Add a unique ID for the BrowsingContext tree inside a browser element. r=kmag

This adds a `browserId` property to all browsing contexts. This ID is the same
for the entire tree of contexts inside a frame element. Each new top-level
context created for a given frame also inherits this ID. This allows identifying
the frame element for a given browsing context.

Originally authored by :mossop in D56245.

Differential Revision: https://phabricator.services.mozilla.com/D77911
This commit is contained in:
Kashav Madan 2020-06-16 18:12:46 +00:00
Родитель 92e1bc94a4
Коммит 1a71fed80e
16 изменённых файлов: 269 добавлений и 23 удалений

Просмотреть файл

@ -1,7 +1,35 @@
// Swaps the content of tab a into tab b and then closes tab a.
function swapTabsAndCloseOther(a, b) {
gBrowser.swapBrowsersAndCloseOther(gBrowser.tabs[b], gBrowser.tabs[a]);
}
// Mirrors the effect of the above function on an array.
function swapArrayContentsAndRemoveOther(arr, a, b) {
arr[b] = arr[a];
arr.splice(a, 1);
}
function checkBrowserIds(expected) {
is(
gBrowser.tabs.length,
expected.length,
"Should have the right number of tabs."
);
for (let [i, tab] of gBrowser.tabs.entries()) {
is(
tab.linkedBrowser.browserId,
expected[i],
`Tab ${i} should have the right browser ID.`
);
is(
tab.linkedBrowser.browserId,
tab.linkedBrowser.browsingContext.browserId,
`Browser for tab ${i} has the same browserId as its BrowsingContext`
);
}
}
var getClicks = function(tab) {
return SpecialPowers.spawn(tab.linkedBrowser, [], function() {
return content.wrappedJSObject.clicks;
@ -112,23 +140,40 @@ add_task(async function() {
);
await BrowserTestUtils.switchTab(gBrowser, tabs[3]);
swapTabsAndCloseOther(2, 3); // now: 0 1 2 4
let browserIds = tabs.map(t => t.linkedBrowser.browserId);
checkBrowserIds(browserIds);
is(gBrowser.tabs[1], tabs[1], "tab1");
is(gBrowser.tabs[2], tabs[3], "tab3");
is(gBrowser.tabs[3], tabs[4], "tab4");
delete tabs[2];
is(gBrowser.tabs[2], tabs[2], "tab2");
is(gBrowser.tabs[3], tabs[3], "tab3");
is(gBrowser.tabs[4], tabs[4], "tab4");
swapTabsAndCloseOther(2, 3); // now: 0 1 2 4
// Tab 2 is gone (what was tab 3 is displaying its content).
tabs.splice(2, 1);
swapArrayContentsAndRemoveOther(browserIds, 2, 3);
is(gBrowser.tabs[1], tabs[1], "tab1");
is(gBrowser.tabs[2], tabs[2], "tab2");
is(gBrowser.tabs[3], tabs[3], "tab4");
checkBrowserIds(browserIds);
info("about to cacheObjectValue");
await cacheObjectValue(tabs[4].linkedBrowser);
await cacheObjectValue(tabs[3].linkedBrowser);
info("just finished cacheObjectValue");
swapTabsAndCloseOther(3, 2); // now: 0 1 4
tabs.splice(3, 1);
swapArrayContentsAndRemoveOther(browserIds, 3, 2);
is(
Array.prototype.indexOf.call(gBrowser.tabs, gBrowser.selectedTab),
2,
"The third tab should be selected"
);
delete tabs[4];
checkBrowserIds(browserIds);
ok(
await checkObjectValue(gBrowser.tabs[2].linkedBrowser),
@ -136,15 +181,19 @@ add_task(async function() {
);
is(gBrowser.tabs[1], tabs[1], "tab1");
is(gBrowser.tabs[2], tabs[3], "tab4");
is(gBrowser.tabs[2], tabs[2], "tab4");
let clicks = await getClicks(gBrowser.tabs[2]);
is(clicks, 0, "no click on BODY so far");
await clickTest(gBrowser.tabs[2]);
swapTabsAndCloseOther(2, 1); // now: 0 4
is(gBrowser.tabs[1], tabs[1], "tab1");
delete tabs[3];
tabs.splice(2, 1);
swapArrayContentsAndRemoveOther(browserIds, 2, 1);
is(gBrowser.tabs[1], tabs[1], "tab4");
checkBrowserIds(browserIds);
ok(
await checkObjectValue(gBrowser.tabs[1].linkedBrowser),
@ -175,9 +224,14 @@ add_task(async function() {
await loadURI(tabs[1], "about:blank");
let key = tabs[1].linkedBrowser.permanentKey;
checkBrowserIds(browserIds);
let win = gBrowser.replaceTabWithWindow(tabs[1]);
await new Promise(resolve => whenDelayedStartupFinished(win, resolve));
delete tabs[1];
let newWinBrowserId = browserIds[1];
browserIds.splice(1, 1);
checkBrowserIds(browserIds);
// Verify that the original window now only has the initial tab left in it.
is(gBrowser.tabs[0], tabs[0], "tab0");
@ -185,6 +239,12 @@ add_task(async function() {
let tab = win.gBrowser.tabs[0];
is(tab.linkedBrowser.permanentKey, key, "Should have kept the key");
is(tab.linkedBrowser.browserId, newWinBrowserId, "Should have kept the ID");
is(
tab.linkedBrowser.browserId,
tab.linkedBrowser.browsingContext.browserId,
"Should have kept the ID"
);
let awaitPageShow = BrowserTestUtils.waitForContentEvent(
tab.linkedBrowser,

Просмотреть файл

@ -30,6 +30,7 @@
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/WindowProxyHolder.h"
#include "mozilla/dom/SyncedContextInlines.h"
#include "mozilla/dom/XULFrameElement.h"
#include "mozilla/net/DocumentLoadListener.h"
#include "mozilla/net/RequestContextService.h"
#include "mozilla/Assertions.h"
@ -50,6 +51,7 @@
#include "nsGlobalWindowOuter.h"
#include "nsIObserverService.h"
#include "nsContentUtils.h"
#include "nsQueryObject.h"
#include "nsSandboxFlags.h"
#include "nsScriptError.h"
#include "nsThreadUtils.h"
@ -187,10 +189,14 @@ bool BrowsingContext::SameOriginWithTop() {
/* static */
already_AddRefed<BrowsingContext> BrowsingContext::CreateDetached(
nsGlobalWindowInner* aParent, BrowsingContext* aOpener,
const nsAString& aName, Type aType) {
MOZ_DIAGNOSTIC_ASSERT(!aParent ||
aParent->GetBrowsingContext()->mType == aType);
MOZ_DIAGNOSTIC_ASSERT(!aParent || aParent->GetWindowContext());
const nsAString& aName, Type aType, uint64_t aBrowserId) {
if (aParent) {
MOZ_DIAGNOSTIC_ASSERT(aParent->GetWindowContext());
MOZ_DIAGNOSTIC_ASSERT(aParent->GetBrowsingContext()->mType == aType);
MOZ_DIAGNOSTIC_ASSERT(aParent->GetBrowsingContext()->GetBrowserId() == 0 ||
aParent->GetBrowsingContext()->GetBrowserId() ==
aBrowserId);
}
MOZ_DIAGNOSTIC_ASSERT(aType != Type::Chrome || XRE_IsParentProcess());
@ -232,6 +238,7 @@ already_AddRefed<BrowsingContext> BrowsingContext::CreateDetached(
context->mFields.SetWithoutSyncing<IDX_OpenerId>(aOpener->Id());
context->mFields.SetWithoutSyncing<IDX_HadOriginalOpener>(true);
}
if (aParent) {
MOZ_DIAGNOSTIC_ASSERT(parentBC->Group() == context->Group());
MOZ_DIAGNOSTIC_ASSERT(parentBC->mType == context->mType);
@ -244,6 +251,8 @@ already_AddRefed<BrowsingContext> BrowsingContext::CreateDetached(
context->mFields.SetWithoutSyncing<IDX_OpenerPolicy>(
nsILoadInfo::OPENER_POLICY_UNSAFE_NONE);
context->mFields.SetWithoutSyncing<IDX_BrowserId>(aBrowserId);
if (aOpener && aOpener->SameOriginWithTop()) {
// We inherit the opener policy if there is a creator and if the creator's
// origin is same origin with the creator's top-level origin.
@ -327,8 +336,10 @@ already_AddRefed<BrowsingContext> BrowsingContext::CreateDetached(
already_AddRefed<BrowsingContext> BrowsingContext::CreateIndependent(
Type aType) {
uint64_t browserId =
aType == Type::Content ? nsContentUtils::GenerateBrowserId() : 0;
RefPtr<BrowsingContext> bc(
CreateDetached(nullptr, nullptr, EmptyString(), aType));
CreateDetached(nullptr, nullptr, EmptyString(), aType, browserId));
bc->mWindowless = bc->IsContent();
bc->EnsureAttached();
return bc.forget();
@ -500,6 +511,29 @@ static bool OwnerAllowsFullscreen(const Element& aEmbedder) {
void BrowsingContext::SetEmbedderElement(Element* aEmbedder) {
mEmbeddedByThisProcess = true;
// Update the browser ID on the embedder if necessary. We currently don't care
// about browser IDs for chrome-type BrowsingContexts.
if (RefPtr<nsFrameLoaderOwner> owner = do_QueryObject(aEmbedder);
owner && !IsChrome()) {
uint64_t browserId = GetBrowserId();
uint64_t frameBrowserId = owner->GetBrowserId();
MOZ_DIAGNOSTIC_ASSERT(browserId != 0);
if (frameBrowserId == 0) {
// We'll arrive here if we're a top-level BrowsingContext for a window
// or tab that was opened in a content process. There should be no
// children to update at this point. This ID was generated in
// ContentChild::ProvideWindowCommon.
MOZ_DIAGNOSTIC_ASSERT(IsTopContent());
MOZ_DIAGNOSTIC_ASSERT(Children().IsEmpty());
owner->SetBrowserId(browserId);
} else {
// We would've inherited or generated an ID in CreateBrowsingContext.
MOZ_DIAGNOSTIC_ASSERT(browserId == frameBrowserId);
}
}
// Update embedder-element-specific fields in a shared transaction.
// Don't do this when clearing our embedder, as we're being destroyed either
// way.
@ -2402,6 +2436,13 @@ void BrowsingContext::DidSet(FieldIndex<IDX_HasSessionHistory>,
CreateChildSHistory();
}
bool BrowsingContext::CanSet(FieldIndex<IDX_BrowserId>, const uint32_t& aValue,
ContentParent* aSource) {
// We should only be able to set this for toplevel contexts which don't have
// an ID yet.
return GetBrowserId() == 0 && IsTop() && Children().IsEmpty();
}
} // namespace dom
namespace ipc {

Просмотреть файл

@ -107,6 +107,13 @@ class WindowProxyHolder;
FIELD(FeaturePolicy, RefPtr<mozilla::dom::FeaturePolicy>) \
/* See nsSandboxFlags.h for the possible flags. */ \
FIELD(SandboxFlags, uint32_t) \
/* A unique identifier for the browser element that is hosting this \
* BrowsingContext tree. Every BrowsingContext in the element's tree will \
* return the same ID in all processes and it will remain stable \
* regardless of process changes. When a browser element's frameloader is \
* switched to another browser element this ID will remain the same but \
* hosted under the under the new browser element. */ \
FIELD(BrowserId, uint64_t) \
FIELD(HistoryID, nsID) \
FIELD(InRDMPane, bool) \
FIELD(Loading, bool) \
@ -198,7 +205,7 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
// DocShell, BrowserParent, or BrowserBridgeChild.
static already_AddRefed<BrowsingContext> CreateDetached(
nsGlobalWindowInner* aParent, BrowsingContext* aOpener,
const nsAString& aName, Type aType);
const nsAString& aName, Type aType, uint64_t aBrowserId);
void EnsureAttached();
@ -396,6 +403,8 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
bool UseGlobalHistory() const { return GetUseGlobalHistory(); }
uint64_t BrowserId() const { return GetBrowserId(); }
bool IsLoading();
// ScreenOrientation related APIs
@ -763,6 +772,9 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
void DidSet(FieldIndex<IDX_HasSessionHistory>, bool aOldValue);
bool CanSet(FieldIndex<IDX_BrowserId>, const uint32_t& aValue,
ContentParent* aSource);
template <size_t I, typename T>
bool CanSet(FieldIndex<I>, const T&, ContentParent*) {
return true;

Просмотреть файл

@ -28,7 +28,7 @@ add_task(async function() {
true,
true
);
await SpecialPowers.spawn(
let browserIds = await SpecialPowers.spawn(
browser,
[{ base1: BASE1, base2: BASE2 }],
async function({ base1, base2 }) {
@ -172,10 +172,58 @@ add_task(async function() {
}
await unreachable(start, seventh);
}
let topBrowserId = topBC.browserId;
ok(topBrowserId > 0, "Should have a browser ID.");
for (let [name, bc] of Object.entries({
first,
second,
third,
fourth,
fifth,
})) {
is(
bc.browserId,
topBrowserId,
`${name} frame should have the same browserId as top.`
);
}
ok(sixth.browserId > 0, "sixth should have a browserId.");
isnot(
sixth.browserId,
topBrowserId,
"sixth frame should have a different browserId to top."
);
return [topBrowserId, sixth.browserId];
}
);
for (let tab of await Promise.all([sixth, seventh])) {
[sixth, seventh] = await Promise.all([sixth, seventh]);
is(
browser.browserId,
browserIds[0],
"browser should have the right browserId."
);
is(
browser.browsingContext.browserId,
browserIds[0],
"browser's BrowsingContext should have the right browserId."
);
is(
sixth.linkedBrowser.browserId,
browserIds[1],
"sixth should have the right browserId."
);
is(
sixth.linkedBrowser.browsingContext.browserId,
browserIds[1],
"sixth's BrowsingContext should have the right browserId."
);
for (let tab of [sixth, seventh]) {
BrowserTestUtils.removeTab(tab);
}
}

Просмотреть файл

@ -9716,6 +9716,14 @@ uint64_t nsContentUtils::GenerateTabId() {
return GenerateProcessSpecificId(++gNextTabId);
}
// Next process-local Browser ID.
static uint64_t gNextBrowserId = 0;
/* static */
uint64_t nsContentUtils::GenerateBrowserId() {
return GenerateProcessSpecificId(++gNextBrowserId);
}
// Next process-local Browsing Context ID.
static uint64_t gNextBrowsingContextId = 0;

Просмотреть файл

@ -3142,6 +3142,11 @@ class nsContentUtils {
*/
static uint64_t GenerateTabId();
/**
* Compose a browser id with process id and a serial number.
*/
static uint64_t GenerateBrowserId();
/**
* Generate an id for a BrowsingContext using a range of serial
* numbers reserved for the current process.

Просмотреть файл

@ -83,6 +83,7 @@
#include "mozilla/dom/MozFrameLoaderOwnerBinding.h"
#include "mozilla/dom/SessionStoreListener.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/XULFrameElement.h"
#include "mozilla/gfx/CrossProcessPaint.h"
#include "nsGenericHTMLFrameElement.h"
#include "GeckoProfiler.h"
@ -295,6 +296,10 @@ static already_AddRefed<BrowsingContext> CreateBrowsingContext(
nsAutoString frameName;
GetFrameName(aOwner, frameName);
// By default we just use the same browser ID as the parent.
uint64_t browserId = parentBC->GetBrowserId();
RefPtr<nsFrameLoaderOwner> owner = do_QueryObject(aOwner);
// Create our BrowsingContext without immediately attaching it. It's possible
// that no DocShell or remote browser will ever be created for this
// FrameLoader, particularly if the document that we were created for is not
@ -302,16 +307,38 @@ static already_AddRefed<BrowsingContext> CreateBrowsingContext(
// it will wind up attached as a child of the currently active inner window
// for the BrowsingContext, and cause no end of trouble.
if (IsTopContent(parentBC, aOwner)) {
if (owner && owner->GetBrowserId() != 0) {
// This frame has already been assigned an ID. This can happen for example
// if a frame is re-inserted into the DOM (i.e. on a remoteness change).
browserId = owner->GetBrowserId();
// This implies that we do not support changing a frame's "type"
// attribute. Doing so would mean needing to change the browser ID for the
// frame and the intent is to never change this.
MOZ_DIAGNOSTIC_ASSERT(browserId != parentBC->GetBrowserId());
} else {
browserId = nsContentUtils::GenerateBrowserId();
if (owner) {
owner->SetBrowserId(browserId);
}
}
// Create toplevel content without a parent & as Type::Content.
return BrowsingContext::CreateDetached(nullptr, opener, frameName,
BrowsingContext::Type::Content);
return BrowsingContext::CreateDetached(
nullptr, opener, frameName, BrowsingContext::Type::Content, browserId);
}
MOZ_ASSERT(!aOpenWindowInfo,
"Can't have openWindowInfo for non-toplevel context");
if (owner) {
MOZ_DIAGNOSTIC_ASSERT(owner->GetBrowserId() == 0 ||
owner->GetBrowserId() == browserId);
owner->SetBrowserId(browserId);
}
return BrowsingContext::CreateDetached(parentInner, nullptr, frameName,
parentBC->GetType());
parentBC->GetType(), browserId);
}
static bool InitialLoadIsRemote(Element* aOwner) {
@ -1294,6 +1321,13 @@ nsresult nsFrameLoader::SwapWithOtherRemoteLoader(
MaybeUpdatePrimaryBrowserParent(eBrowserParentRemoved);
aOther->MaybeUpdatePrimaryBrowserParent(eBrowserParentRemoved);
// When embedding the frame in SetOwnerContent, we check that the
// BrowsingContext's browser ID matches that of the embedder element, so swap
// the IDs here.
uint64_t ourBrowserId = aThisOwner->GetBrowserId();
aThisOwner->SetBrowserId(aOtherOwner->GetBrowserId());
aOtherOwner->SetBrowserId(ourBrowserId);
SetOwnerContent(otherContent);
aOther->SetOwnerContent(ourContent);
@ -1708,6 +1742,13 @@ nsresult nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther,
otherDocshell, ourOwner,
ourBc->IsContent() ? ourChromeEventHandler.get() : nullptr);
// When embedding the frame in SetOwnerContent, we check that the
// BrowsingContext's browser ID matches that of the embedder element, so swap
// the IDs here.
uint64_t ourBrowserId = aThisOwner->GetBrowserId();
aThisOwner->SetBrowserId(aOtherOwner->GetBrowserId());
aOtherOwner->SetBrowserId(ourBrowserId);
// Switch the owner content before we start calling AddTreeItemToTreeOwner.
// Note that we rely on this to deal with setting mObservingOwnerContent to
// false and calling RemoveMutationObserver as needed.

Просмотреть файл

@ -286,3 +286,9 @@ void nsFrameLoaderOwner::SubframeCrashed() {
/* inProgress */ false,
/* isRemote */ false, frameLoaderInit, IgnoreErrors());
}
void nsFrameLoaderOwner::UnbindFromTree() {
// If we're being adopted into a different document, we'll want to inherit a
// browser ID from our new BrowsingContext, so clear our current ID here.
mBrowserId = 0;
}

Просмотреть файл

@ -74,6 +74,12 @@ class nsFrameLoaderOwner : public nsISupports {
void SubframeCrashed();
// Prepare for a frame to be removed from its current DOM tree.
void UnbindFromTree();
uint64_t GetBrowserId() { return mBrowserId; }
void SetBrowserId(uint64_t aBrowserId) { mBrowserId = aBrowserId; }
private:
bool UseRemoteSubframes();
@ -99,6 +105,8 @@ class nsFrameLoaderOwner : public nsISupports {
std::function<void()>& aFrameLoaderInit,
mozilla::ErrorResult& aRv);
uint64_t mBrowserId = 0;
protected:
virtual ~nsFrameLoaderOwner() = default;
RefPtr<nsFrameLoader> mFrameLoader;

Просмотреть файл

@ -568,6 +568,7 @@ nsresult nsObjectLoadingContent::BindToTree(BindContext& aContext,
}
void nsObjectLoadingContent::UnbindFromTree(bool aNullParent) {
nsFrameLoaderOwner::UnbindFromTree();
nsImageLoadingContent::UnbindFromTree(aNullParent);
nsCOMPtr<Element> thisElement =

Просмотреть файл

@ -105,6 +105,16 @@ interface BrowsingContext {
// The watchedByDevTools flag indicates whether or not DevTools are currently
// debugging this browsing context.
[SetterThrows] attribute boolean watchedByDevTools;
/**
* A unique identifier for the browser element that is hosting this
* BrowsingContext tree. Every BrowsingContext in the element's tree will
* return the same ID in all processes and it will remain stable regardless of
* process changes. When a browser element's frameloader is switched to
* another browser element this ID will remain the same but hosted under the
* under the new browser element.
*/
attribute unsigned long long browserId;
};
BrowsingContext includes LoadContextMixin;

Просмотреть файл

@ -16,7 +16,9 @@ interface XULFrameElement : XULElement
readonly attribute nsIWebNavigation? webNavigation;
readonly attribute WindowProxy? contentWindow;
readonly attribute Document? contentDocument;
readonly attribute Document? contentDocument;
readonly attribute unsigned long long browserId;
};
XULFrameElement includes MozFrameLoaderOwner;

Просмотреть файл

@ -208,6 +208,7 @@ void nsGenericHTMLFrameElement::UnbindFromTree(bool aNullParent) {
mFrameLoader = nullptr;
}
nsFrameLoaderOwner::UnbindFromTree();
nsGenericHTMLElement::UnbindFromTree(aNullParent);
}

Просмотреть файл

@ -949,8 +949,9 @@ nsresult ContentChild::ProvideWindowCommon(
openerBC = parent;
}
uint64_t browserId(nsContentUtils::GenerateBrowserId());
RefPtr<BrowsingContext> browsingContext = BrowsingContext::CreateDetached(
nullptr, openerBC, aName, BrowsingContext::Type::Content);
nullptr, openerBC, aName, BrowsingContext::Type::Content, browserId);
MOZ_ALWAYS_SUCCEEDS(browsingContext->SetRemoteTabs(true));
MOZ_ALWAYS_SUCCEEDS(browsingContext->SetRemoteSubframes(useRemoteSubframes));
MOZ_ALWAYS_SUCCEEDS(browsingContext->SetOriginAttributes(

Просмотреть файл

@ -154,6 +154,7 @@ void XULFrameElement::UnbindFromTree(bool aNullParent) {
}
mFrameLoader = nullptr;
nsFrameLoaderOwner::UnbindFromTree();
nsXULElement::UnbindFromTree(aNullParent);
}

Просмотреть файл

@ -39,6 +39,7 @@ class XULFrameElement final : public nsXULElement, public nsFrameLoaderOwner {
already_AddRefed<nsIWebNavigation> GetWebNavigation();
Nullable<WindowProxyHolder> GetContentWindow();
Document* GetContentDocument();
uint64_t BrowserId() { return GetBrowserId(); }
void SwapFrameLoaders(mozilla::dom::HTMLIFrameElement& aOtherLoaderOwner,
mozilla::ErrorResult& rv);