Bug 1515646 - Add FindWithName and FindChildWithName to BrowsingContext. r=peterv

This implements the step of choosing a browsing context with
FindWithName, which should be equivalent to calling
nsIDocShellTreeItem.findItemWithName passing null for 'aRequestor' and
'aOriginalRequestor' and false for 'aSkipTabGroup'.

Differential Revision: https://phabricator.services.mozilla.com/D15190

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Andreas Farre 2019-02-15 09:59:21 +00:00
parent 3b21ed28db
commit 48da8e2402
5 changed files with 199 additions and 22 deletions

View File

@ -221,16 +221,16 @@ void BrowsingContext::Detach() {
MOZ_DIAGNOSTIC_ASSERT(!mGroup || !mGroup->Toplevels().Contains(this));
sCachedBrowsingContexts->remove(p);
} else {
auto* children = mParent ? &mParent->mChildren : &mGroup->Toplevels();
Children& children = mParent ? mParent->mChildren : mGroup->Toplevels();
// TODO(farre): This assert looks extremely fishy, I know, but
// what we're actually saying is this: if we're detaching, but our
// parent doesn't have any children, it is because we're being
// detached by the cycle collector destroying docshells out of
// order.
MOZ_DIAGNOSTIC_ASSERT(children->IsEmpty() || children->Contains(this));
MOZ_DIAGNOSTIC_ASSERT(children.IsEmpty() || children.Contains(this));
children->RemoveElement(this);
children.RemoveElement(this);
}
Group()->Unregister(this);
@ -292,6 +292,160 @@ void BrowsingContext::SetOpener(BrowsingContext* aOpener) {
cc->SendSetOpenerBrowsingContext(this, aOpener);
}
// FindWithName follows the rules for choosing a browsing context,
// with the exception of sandboxing for iframes. The implementation
// for arbitrarily choosing between two browsing contexts with the
// same name is as follows:
//
// 1) The start browsing context, i.e. 'this'
// 2) Descendants in insertion order
// 3) The parent
// 4) Siblings and their children, both in insertion order
// 5) After this we iteratively follow the parent chain, repeating 3
// and 4 until
// 6) If there is no parent, consider all other top level browsing
// contexts and their children, both in insertion order
//
// 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) {
BrowsingContext* found = nullptr;
if (aName.IsEmpty()) {
// You can't find a browsing context with an empty name.
found = nullptr;
} else if (BrowsingContext* special = FindWithSpecialName(aName)) {
found = special;
} else if (aName.LowerCaseEqualsLiteral("_blank")) {
// Just return null. Caller must handle creating a new window with
// a blank name.
found = nullptr;
} else if (BrowsingContext* child = FindWithNameInSubtree(aName, this)) {
found = child;
} else {
BrowsingContext* current = this;
do {
Children* siblings;
BrowsingContext* parent = current->mParent;
if (!parent) {
// We've reached the root of the tree, consider browsing
// contexts in the same browsing context group.
siblings = &mGroup->Toplevels();
} else if (parent->NameEquals(aName) && CanAccess(parent) &&
parent->IsActive()) {
found = parent;
break;
} else {
siblings = &parent->mChildren;
}
for (BrowsingContext* sibling : *siblings) {
if (sibling == current) {
continue;
}
if (BrowsingContext* relative =
sibling->FindWithNameInSubtree(aName, this)) {
found = relative;
// Breaks the outer loop
parent = nullptr;
break;
}
}
current = parent;
} while (current);
}
// Helpers should perform access control checks, which means that we
// only need to assert that we can access found.
MOZ_DIAGNOSTIC_ASSERT(!found || CanAccess(found));
return found;
}
BrowsingContext* BrowsingContext::FindChildWithName(const nsAString& aName) {
if (aName.IsEmpty()) {
// You can't find a browsing context with the empty name.
return nullptr;
}
for (BrowsingContext* child : mChildren) {
if (child->NameEquals(aName) && CanAccess(child) && child->IsActive()) {
return child;
}
}
return nullptr;
}
BrowsingContext* BrowsingContext::FindWithSpecialName(const nsAString& aName) {
// TODO(farre): Neither BrowsingContext nor nsDocShell checks if the
// browsing context pointed to by a special name is active. Should
// it be? See Bug 1527913.
if (aName.LowerCaseEqualsLiteral("_self")) {
return this;
}
if (aName.LowerCaseEqualsLiteral("_parent")) {
return mParent && CanAccess(mParent.get()) ? mParent.get() : this;
}
if (aName.LowerCaseEqualsLiteral("_top")) {
BrowsingContext* top = TopLevelBrowsingContext();
return CanAccess(top) ? top : nullptr;
}
return nullptr;
}
BrowsingContext* BrowsingContext::FindWithNameInSubtree(
const nsAString& aName, BrowsingContext* aRequestingContext) {
MOZ_DIAGNOSTIC_ASSERT(!aName.IsEmpty());
if (NameEquals(aName) && aRequestingContext->CanAccess(this) && IsActive()) {
return this;
}
for (BrowsingContext* child : mChildren) {
if (BrowsingContext* found =
child->FindWithNameInSubtree(aName, aRequestingContext)) {
return found;
}
}
return nullptr;
}
bool BrowsingContext::CanAccess(BrowsingContext* aContext) {
// TODO(farre): Bouncing this to nsDocShell::CanAccessItem is
// temporary, we should implement a replacement for this in
// BrowsingContext. See Bug 151590.
return aContext && nsDocShell::CanAccessItem(aContext->mDocShell, mDocShell);
}
bool BrowsingContext::IsActive() const {
// TODO(farre): Mimicking the bahaviour from
// ItemIsActive(nsIDocShellTreeItem* aItem) is temporary, we should
// implement a replacement for this using mClosed only. See Bug
// 1527321.
if (!mDocShell) {
return mClosed;
}
if (nsCOMPtr<nsPIDOMWindowOuter> window = mDocShell->GetWindow()) {
auto* win = nsGlobalWindowOuter::Cast(window);
if (!win->GetClosedOuter()) {
return true;
}
}
return false;
}
BrowsingContext::~BrowsingContext() {
MOZ_DIAGNOSTIC_ASSERT(!mParent || !mParent->mChildren.Contains(this));
MOZ_DIAGNOSTIC_ASSERT(!mGroup || !mGroup->Toplevels().Contains(this));
@ -496,22 +650,6 @@ void BrowsingContext::PostMessageMoz(JSContext* aCx,
aSubjectPrincipal, aError);
}
already_AddRefed<BrowsingContext> BrowsingContext::FindChildWithName(
const nsAString& aName) {
// FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=1515646 will reimplement
// this on top of the BC tree.
MOZ_ASSERT(mDocShell);
nsCOMPtr<nsIDocShellTreeItem> child;
mDocShell->FindChildWithName(aName, false, true, nullptr, nullptr,
getter_AddRefs(child));
nsCOMPtr<nsIDocShell> childDS = do_QueryInterface(child);
RefPtr<BrowsingContext> bc;
if (childDS) {
childDS->GetBrowsingContext(getter_AddRefs(bc));
}
return bc.forget();
}
} // namespace dom
namespace ipc {

View File

@ -130,6 +130,7 @@ class BrowsingContext : public nsWrapperCache,
// process. [Bug 1490303]
void SetName(const nsAString& aName) { mName = aName; }
const nsString& Name() const { return mName; }
void GetName(nsAString& aName) { aName = mName; }
bool NameEquals(const nsAString& aName) { return mName.Equals(aName); }
bool IsContent() const { return mType == Type::Content; }
@ -146,6 +147,24 @@ class BrowsingContext : public nsWrapperCache,
BrowsingContextGroup* Group() { return mGroup; }
// Using the rules for choosing a browsing context we try to find
// the browsing context with the given name in the set of
// transitively reachable browsing contexts. Performs access control
// with regards to this.
// See
// https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name.
//
// BrowsingContext::FindWithName(const nsAString&) is equivalent to
// calling nsIDocShellTreeItem::FindItemWithName(aName, nullptr,
// nullptr, false, <return value>).
BrowsingContext* FindWithName(const nsAString& aName);
// Find a browsing context in this context's list of
// children. Doesn't consider the special names, '_self', '_parent',
// '_top', or '_blank'. Performs access control with regard to
// 'this'.
BrowsingContext* FindChildWithName(const nsAString& aName);
nsISupports* GetParentObject() const;
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
@ -204,8 +223,6 @@ class BrowsingContext : public nsWrapperCache,
const WindowPostMessageOptions& aOptions,
nsIPrincipal& aSubjectPrincipal, ErrorResult& aError);
already_AddRefed<BrowsingContext> FindChildWithName(const nsAString& aName);
JSObject* WrapObject(JSContext* aCx);
protected:
@ -215,6 +232,22 @@ class BrowsingContext : public nsWrapperCache,
Type aType);
private:
// Find the special browsing context if aName is '_self', '_parent',
// '_top', but not '_blank'. The latter is handled in FindWithName
BrowsingContext* FindWithSpecialName(const nsAString& aName);
// Find a browsing context in the subtree rooted at 'this' Doesn't
// consider the special names, '_self', '_parent', '_top', or
// '_blank'. Performs access control with regard to
// 'aRequestingContext'.
BrowsingContext* FindWithNameInSubtree(const nsAString& aName,
BrowsingContext* aRequestingContext);
// Performs access control to check that 'this' can access 'aContext'.
bool CanAccess(BrowsingContext* aContext);
bool IsActive() const;
friend class ::nsOuterWindowProxy;
friend class ::nsGlobalWindowOuter;
// Update the window proxy object that corresponds to this browsing context.

View File

@ -402,6 +402,7 @@ class nsDocShell final : public nsDocLoader,
friend class FramingChecker;
friend class OnLinkClickEvent;
friend class nsIDocShell;
friend class mozilla::dom::BrowsingContext;
// It is necessary to allow adding a timeline marker wherever a docshell
// instance is available. This operation happens frequently and needs to

View File

@ -3933,7 +3933,7 @@ already_AddRefed<BrowsingContext> nsGlobalWindowOuter::GetChildWindow(
const nsAString& aName) {
NS_ENSURE_TRUE(mBrowsingContext, nullptr);
return mBrowsingContext->FindChildWithName(aName);
return do_AddRef(mBrowsingContext->FindChildWithName(aName));
}
bool nsGlobalWindowOuter::DispatchCustomEvent(const nsAString& aEventName) {

View File

@ -9,6 +9,11 @@ interface nsIDocShell;
interface BrowsingContext {
static BrowsingContext? get(unsigned long long aId);
BrowsingContext? findChildWithName(DOMString name);
BrowsingContext? findWithName(DOMString name);
readonly attribute DOMString name;
readonly attribute BrowsingContext? parent;
sequence<BrowsingContext> getChildren();