docshell/base/BrowsingContext.cpp
author Andreas Farre <farre@mozilla.com>
Mon, 17 Dec 2018 10:45:37 +0000
changeset 451298 b068ba3cc5c3019d059e03321f6ee48d83a2cac8
parent 449391 80ae3a4f8e444e88a32c37917c1a8bcdfb50e17d
child 452208 aa9b106b15d93573e5125356667f3a1f474d2e5a
permissions -rw-r--r--
Bug 1505838 - Add BrowsingContextGroup. r=peterv Add top-level and auxiliary browsing contexts to a group of BrowsingContexts on creation and store a pointer to that group in all children of the BrowsingContexts in the group. With this it is possible to compute the transitive closure of related browsing contexts. Since we'll not be using linked lists of BrowsingContexts for neither groups nor children we can move children to be an array of BrowsingContexts and adjust to use a the more convenient HashMap for roots. Differential Revision: https://phabricator.services.mozilla.com/D13227

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/dom/BrowsingContext.h"

#include "mozilla/dom/ChromeBrowsingContext.h"
#include "mozilla/dom/BrowsingContextBinding.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/Assertions.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/HashTable.h"
#include "mozilla/Logging.h"
#include "mozilla/StaticPtr.h"

#include "nsDocShell.h"
#include "nsContentUtils.h"
#include "nsThreadUtils.h"

namespace mozilla {
namespace dom {

static LazyLogModule gBrowsingContextLog("BrowsingContext");

static StaticAutoPtr<BrowsingContext::Children> sRootBrowsingContexts;

template <template <typename> class PtrType>
using BrowsingContextMap =
    HashMap<uint64_t, PtrType<BrowsingContext>, DefaultHasher<uint64_t>,
            InfallibleAllocPolicy>;

static StaticAutoPtr<BrowsingContextMap<WeakPtr>> sBrowsingContexts;

// TODO(farre): This duplicates some of the work performed by the
// bfcache. This should be unified. [Bug 1471601]
static StaticAutoPtr<BrowsingContextMap<RefPtr>> sCachedBrowsingContexts;

static void Register(BrowsingContext* aBrowsingContext) {
  MOZ_ALWAYS_TRUE(
      sBrowsingContexts->putNew(aBrowsingContext->Id(), aBrowsingContext));
}

static void Sync(BrowsingContext* aBrowsingContext) {
  if (!XRE_IsContentProcess()) {
    return;
  }

  auto cc = ContentChild::GetSingleton();
  MOZ_DIAGNOSTIC_ASSERT(cc);
  nsAutoString name;
  aBrowsingContext->GetName(name);
  RefPtr<BrowsingContext> parent = aBrowsingContext->GetParent();
  BrowsingContext* opener = aBrowsingContext->GetOpener();
  cc->SendAttachBrowsingContext(BrowsingContextId(parent ? parent->Id() : 0),
                                BrowsingContextId(opener ? opener->Id() : 0),
                                BrowsingContextId(aBrowsingContext->Id()),
                                name);
}

/* static */ void BrowsingContext::Init() {
  if (!sRootBrowsingContexts) {
    sRootBrowsingContexts = new BrowsingContext::Children();
    ClearOnShutdown(&sRootBrowsingContexts);
  }

  if (!sBrowsingContexts) {
    sBrowsingContexts = new BrowsingContextMap<WeakPtr>();
    ClearOnShutdown(&sBrowsingContexts);
  }

  if (!sCachedBrowsingContexts) {
    sCachedBrowsingContexts = new BrowsingContextMap<RefPtr>();
    ClearOnShutdown(&sCachedBrowsingContexts);
  }
}

/* static */ LogModule* BrowsingContext::GetLog() {
  return gBrowsingContextLog;
}

/* static */ already_AddRefed<BrowsingContext> BrowsingContext::Get(
    uint64_t aId) {
  if (BrowsingContextMap<WeakPtr>::Ptr abc = sBrowsingContexts->lookup(aId)) {
    return do_AddRef(abc->value().get());
  }

  return nullptr;
}

/* static */ already_AddRefed<BrowsingContext> BrowsingContext::Create(
    BrowsingContext* aParent, BrowsingContext* aOpener, const nsAString& aName,
    Type aType) {
  MOZ_DIAGNOSTIC_ASSERT(!aParent || aParent->mType == aType);

  uint64_t id = nsContentUtils::GenerateBrowsingContextId();

  MOZ_LOG(GetLog(), LogLevel::Debug,
          ("Creating 0x%08" PRIx64 " in %s", id,
           XRE_IsParentProcess() ? "Parent" : "Child"));

  RefPtr<BrowsingContext> context;
  if (XRE_IsParentProcess()) {
    context = new ChromeBrowsingContext(aParent, aOpener, aName, id,
                                        /* aProcessId */ 0, aType);
  } else {
    context = new BrowsingContext(aParent, aOpener, aName, id, aType);
  }

  Register(context);

  // Attach the browsing context to the tree.
  context->Attach();

  return context.forget();
}

/* static */ already_AddRefed<BrowsingContext> BrowsingContext::CreateFromIPC(
    BrowsingContext* aParent, BrowsingContext* aOpener, const nsAString& aName,
    uint64_t aId, ContentParent* aOriginProcess) {
  MOZ_DIAGNOSTIC_ASSERT(aOriginProcess || XRE_IsContentProcess(),
                        "Parent Process IPC contexts need a Content Process.");
  MOZ_DIAGNOSTIC_ASSERT(!aParent || aParent->IsContent());

  MOZ_LOG(GetLog(), LogLevel::Debug,
          ("Creating 0x%08" PRIx64 " from IPC (origin=0x%08" PRIx64 ")", aId,
           aOriginProcess ? uint64_t(aOriginProcess->ChildID()) : 0));

  RefPtr<BrowsingContext> context;
  if (XRE_IsParentProcess()) {
    context = new ChromeBrowsingContext(
        aParent, aOpener, aName, aId, aOriginProcess->ChildID(), Type::Content);
  } else {
    context = new BrowsingContext(aParent, aOpener, aName, aId, Type::Content);
  }

  Register(context);

  context->Attach();

  return context.forget();
}

BrowsingContext::BrowsingContext(BrowsingContext* aParent,
                                 BrowsingContext* aOpener,
                                 const nsAString& aName,
                                 uint64_t aBrowsingContextId, Type aType)
    : mType(aType),
      mBrowsingContextId(aBrowsingContextId),
      mParent(aParent),
      mOpener(aOpener),
      mName(aName) {
  if (mParent) {
    mBrowsingContextGroup = mParent->mBrowsingContextGroup;
  } else if (mOpener) {
    mBrowsingContextGroup = mOpener->mBrowsingContextGroup;
  } else {
    mBrowsingContextGroup = new BrowsingContextGroup();
  }

  if (!mParent) {
    // If we don't have a parent we're either a top level or auxiliary
    // BrowsingContext.
    mBrowsingContextGroup->AppendElement(this);
  }
}

void BrowsingContext::SetDocShell(nsIDocShell* aDocShell) {
  // XXX(nika): We should communicate that we are now an active BrowsingContext
  // process to the parent & do other validation here.
  MOZ_RELEASE_ASSERT(nsDocShell::Cast(aDocShell)->GetBrowsingContext() == this);
  mDocShell = aDocShell;
}

void BrowsingContext::Attach() {
  MOZ_LOG(GetLog(), LogLevel::Debug,
          ("%s: %s 0x%08" PRIx64 " to 0x%08" PRIx64,
           XRE_IsParentProcess() ? "Parent" : "Child",
           sCachedBrowsingContexts->has(Id()) ? "Re-connecting" : "Connecting",
           Id(), mParent ? mParent->Id() : 0));

  sCachedBrowsingContexts->remove(Id());

  auto* children = mParent ? &mParent->mChildren : sRootBrowsingContexts.get();
  MOZ_DIAGNOSTIC_ASSERT(!children->Contains(this));

  children->AppendElement(this);

  Sync(this);
}

void BrowsingContext::Detach() {
  MOZ_LOG(GetLog(), LogLevel::Debug,
          ("%s: Detaching 0x%08" PRIx64 " from 0x%08" PRIx64,
           XRE_IsParentProcess() ? "Parent" : "Child", Id(),
           mParent ? mParent->Id() : 0));

  RefPtr<BrowsingContext> kungFuDeathGrip(this);

  BrowsingContextMap<RefPtr>::Ptr p;
  if (sCachedBrowsingContexts && (p = sCachedBrowsingContexts->lookup(Id()))) {
    MOZ_DIAGNOSTIC_ASSERT(!mParent || !mParent->mChildren.Contains(this));
    MOZ_DIAGNOSTIC_ASSERT(!sRootBrowsingContexts->Contains(this));
    sCachedBrowsingContexts->remove(p);
  } else {
    auto* children =
        mParent ? &mParent->mChildren : sRootBrowsingContexts.get();
    // 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));

    children->RemoveElement(this);
  }

  if (!XRE_IsContentProcess()) {
    return;
  }

  auto cc = ContentChild::GetSingleton();
  MOZ_DIAGNOSTIC_ASSERT(cc);
  cc->SendDetachBrowsingContext(BrowsingContextId(Id()),
                                false /* aMoveToBFCache */);
}

void BrowsingContext::CacheChildren() {
  if (mChildren.IsEmpty()) {
    return;
  }

  MOZ_LOG(GetLog(), LogLevel::Debug,
          ("%s: Caching children of 0x%08" PRIx64 "",
           XRE_IsParentProcess() ? "Parent" : "Child", Id()));

  MOZ_ALWAYS_TRUE(sCachedBrowsingContexts->reserve(mChildren.Length()));

  for (BrowsingContext* child : mChildren) {
    MOZ_ALWAYS_TRUE(sCachedBrowsingContexts->putNew(child->Id(), child));
  }
  mChildren.Clear();

  if (!XRE_IsContentProcess()) {
    return;
  }

  auto cc = ContentChild::GetSingleton();
  MOZ_DIAGNOSTIC_ASSERT(cc);
  cc->SendDetachBrowsingContext(BrowsingContextId(Id()),
                                true /* aMoveToBFCache */);
}

bool BrowsingContext::IsCached() { return sCachedBrowsingContexts->has(Id()); }

void BrowsingContext::GetChildren(
    nsTArray<RefPtr<BrowsingContext>>& aChildren) {
  MOZ_ALWAYS_TRUE(aChildren.AppendElements(mChildren));
}

void BrowsingContext::SetOpener(BrowsingContext* aOpener) {
  if (mOpener == aOpener) {
    return;
  }

  mOpener = aOpener;

  if (!XRE_IsContentProcess()) {
    return;
  }

  auto cc = ContentChild::GetSingleton();
  MOZ_DIAGNOSTIC_ASSERT(cc);
  cc->SendSetOpenerBrowsingContext(
      BrowsingContextId(Id()), BrowsingContextId(aOpener ? aOpener->Id() : 0));
}

/* static */ void BrowsingContext::GetRootBrowsingContexts(
    nsTArray<RefPtr<BrowsingContext>>& aBrowsingContexts) {
  MOZ_ALWAYS_TRUE(aBrowsingContexts.AppendElements(*sRootBrowsingContexts));
}

BrowsingContext::~BrowsingContext() {
  MOZ_DIAGNOSTIC_ASSERT(!mParent || !mParent->mChildren.Contains(this));
  MOZ_DIAGNOSTIC_ASSERT(!sRootBrowsingContexts ||
                        !sRootBrowsingContexts->Contains(this));
  MOZ_DIAGNOSTIC_ASSERT(!sCachedBrowsingContexts ||
                        !sCachedBrowsingContexts->has(Id()));

  if (sBrowsingContexts) {
    sBrowsingContexts->remove(Id());
  }
}

nsISupports* BrowsingContext::GetParentObject() const {
  return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
}

JSObject* BrowsingContext::WrapObject(JSContext* aCx,
                                      JS::Handle<JSObject*> aGivenProto) {
  return BrowsingContext_Binding::Wrap(aCx, this, aGivenProto);
}

NS_IMPL_CYCLE_COLLECTION_CLASS(BrowsingContext)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(BrowsingContext)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell, mChildren, mParent)
  if (XRE_IsParentProcess()) {
    ChromeBrowsingContext::Cast(tmp)->Unlink();
  }
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(BrowsingContext)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell, mChildren, mParent)
  if (XRE_IsParentProcess()) {
    ChromeBrowsingContext::Cast(tmp)->Traverse(cb);
  }
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(BrowsingContext)

NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(BrowsingContext, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(BrowsingContext, Release)

}  // namespace dom
}  // namespace mozilla