Blob Blame History Raw
/* -*- 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 "nsBindingManager.h"

#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsXBLService.h"
#include "nsIInputStream.h"
#include "nsIURI.h"
#include "nsIURL.h"
#include "nsIChannel.h"
#include "nsString.h"
#include "plstr.h"
#include "nsIContent.h"
#include "nsIDOMElement.h"
#include "nsIDocument.h"
#include "nsContentUtils.h"
#include "nsIPresShell.h"
#include "nsIPresShellInlines.h"
#include "nsIXMLContentSink.h"
#include "nsContentCID.h"
#include "mozilla/dom/XMLDocument.h"
#include "nsIStreamListener.h"
#include "ChildIterator.h"
#include "nsITimer.h"

#include "nsXBLBinding.h"
#include "nsXBLPrototypeBinding.h"
#include "nsXBLDocumentInfo.h"
#include "mozilla/dom/XBLChildrenElement.h"
#ifdef MOZ_XUL
#include "nsXULPrototypeCache.h"
#endif

#ifdef MOZ_OLD_STYLE
#include "nsIStyleRuleProcessor.h"
#include "nsRuleProcessorData.h"
#endif
#include "nsIWeakReference.h"

#include "nsWrapperCacheInlines.h"
#include "nsIXPConnect.h"
#include "nsDOMCID.h"
#include "nsIScriptGlobalObject.h"
#include "nsTHashtable.h"

#include "nsIScriptContext.h"
#include "xpcpublic.h"
#include "js/Wrapper.h"

#include "nsThreadUtils.h"
#include "mozilla/dom/NodeListBinding.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/Unused.h"

using namespace mozilla;
using namespace mozilla::dom;

// Implement our nsISupports methods

NS_IMPL_CYCLE_COLLECTION_CLASS(nsBindingManager)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsBindingManager)
  tmp->mDestroyed = true;

  if (tmp->mBoundContentSet) tmp->mBoundContentSet->Clear();

  if (tmp->mDocumentTable) tmp->mDocumentTable->Clear();

  if (tmp->mLoadingDocTable) tmp->mLoadingDocTable->Clear();

  if (tmp->mWrapperTable) {
    tmp->mWrapperTable->Clear();
    tmp->mWrapperTable = nullptr;
  }

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAttachedStack)

  if (tmp->mProcessAttachedQueueEvent) {
    tmp->mProcessAttachedQueueEvent->Revoke();
  }
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsBindingManager)
  // The hashes keyed on nsIContent are traversed from the nsIContent itself.
  if (tmp->mDocumentTable) {
    for (auto iter = tmp->mDocumentTable->Iter(); !iter.Done(); iter.Next()) {
      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDocumentTable value");
      cb.NoteXPCOMChild(iter.UserData());
    }
  }
  if (tmp->mLoadingDocTable) {
    for (auto iter = tmp->mLoadingDocTable->Iter(); !iter.Done(); iter.Next()) {
      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mLoadingDocTable value");
      cb.NoteXPCOMChild(iter.UserData());
    }
  }
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAttachedStack)
  // No need to traverse mProcessAttachedQueueEvent, since it'll just
  // fire at some point or become revoke and drop its ref to us.
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsBindingManager)
  NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(nsBindingManager)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsBindingManager)

// Constructors/Destructors
nsBindingManager::nsBindingManager(nsIDocument* aDocument)
    : mProcessingAttachedStack(false),
      mDestroyed(false),
      mAttachedStackSizeOnOutermost(0),
      mDocument(aDocument) {}

nsBindingManager::~nsBindingManager(void) { mDestroyed = true; }

nsXBLBinding* nsBindingManager::GetBindingWithContent(
    const nsIContent* aContent) {
  nsXBLBinding* binding = aContent ? aContent->GetXBLBinding() : nullptr;
  return binding ? binding->GetBindingWithContent() : nullptr;
}

void nsBindingManager::AddBoundContent(nsIContent* aContent) {
  if (!mBoundContentSet) {
    mBoundContentSet = new nsTHashtable<nsRefPtrHashKey<nsIContent>>;
  }
  mBoundContentSet->PutEntry(aContent);
}

void nsBindingManager::RemoveBoundContent(nsIContent* aContent) {
  if (mBoundContentSet) {
    mBoundContentSet->RemoveEntry(aContent);
  }

  // The death of the bindings means the death of the JS wrapper.
  SetWrappedJS(aContent, nullptr);
}

nsIXPConnectWrappedJS* nsBindingManager::GetWrappedJS(nsIContent* aContent) {
  if (!mWrapperTable) {
    return nullptr;
  }

  if (!aContent || !aContent->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
    return nullptr;
  }

  return mWrapperTable->GetWeak(aContent);
}

nsresult nsBindingManager::SetWrappedJS(nsIContent* aContent,
                                        nsIXPConnectWrappedJS* aWrappedJS) {
  if (mDestroyed) {
    return NS_OK;
  }

  if (aWrappedJS) {
    // lazily create the table, but only when adding elements
    if (!mWrapperTable) {
      mWrapperTable = new WrapperHashtable();
    }
    aContent->SetFlags(NODE_MAY_BE_IN_BINDING_MNGR);

    NS_ASSERTION(aContent, "key must be non-null");
    if (!aContent) return NS_ERROR_INVALID_ARG;

    mWrapperTable->Put(aContent, aWrappedJS);

    return NS_OK;
  }

  // no value, so remove the key from the table
  if (mWrapperTable) {
    mWrapperTable->Remove(aContent);
  }

  return NS_OK;
}

void nsBindingManager::RemovedFromDocumentInternal(
    nsIContent* aContent, nsIDocument* aOldDocument,
    DestructorHandling aDestructorHandling) {
  NS_PRECONDITION(aOldDocument != nullptr, "no old document");

  RefPtr<nsXBLBinding> binding = aContent->GetXBLBinding();
  if (binding) {
    // The binding manager may have been destroyed before a runnable
    // has had a chance to reach this point. If so, we bail out on calling
    // BindingDetached (which may invoke a XBL destructor) and
    // ChangeDocument, but we still want to clear out the binding
    // and insertion parent that may hold references.
    if (!mDestroyed && aDestructorHandling == eRunDtor) {
      binding->PrototypeBinding()->BindingDetached(binding->GetBoundElement());
      binding->ChangeDocument(aOldDocument, nullptr);
    }

    aContent->AsElement()->SetXBLBinding(nullptr, this);
  }

  // Clear out insertion point and content lists.
  aContent->SetXBLInsertionPoint(nullptr);
}

nsAtom* nsBindingManager::ResolveTag(nsIContent* aContent,
                                     int32_t* aNameSpaceID) {
  nsXBLBinding* binding = aContent->GetXBLBinding();

  if (binding) {
    nsAtom* base = binding->GetBaseTag(aNameSpaceID);

    if (base) {
      return base;
    }
  }

  *aNameSpaceID = aContent->GetNameSpaceID();
  return aContent->NodeInfo()->NameAtom();
}

nsresult nsBindingManager::GetAnonymousNodesFor(nsIContent* aContent,
                                                nsIDOMNodeList** aResult) {
  NS_IF_ADDREF(*aResult = GetAnonymousNodesFor(aContent));
  return NS_OK;
}

nsINodeList* nsBindingManager::GetAnonymousNodesFor(nsIContent* aContent) {
  nsXBLBinding* binding = GetBindingWithContent(aContent);
  return binding ? binding->GetAnonymousNodeList() : nullptr;
}

nsresult nsBindingManager::ClearBinding(Element* aElement) {
  // Hold a ref to the binding so it won't die when we remove it from our table
  RefPtr<nsXBLBinding> binding = aElement ? aElement->GetXBLBinding() : nullptr;

  if (!binding) {
    return NS_OK;
  }

  // For now we can only handle removing a binding if it's the only one
  NS_ENSURE_FALSE(binding->GetBaseBinding(), NS_ERROR_FAILURE);

  // Hold strong ref in case removing the binding tries to close the
  // window or something.
  // XXXbz should that be ownerdoc?  Wouldn't we need a ref to the
  // currentdoc too?  What's the one that should be passed to
  // ChangeDocument?
  nsCOMPtr<nsIDocument> doc = aElement->OwnerDoc();

  // Destroy the frames here before the UnbindFromTree happens.
  nsIPresShell* presShell = doc->GetShell();
  if (presShell) {
    presShell->DestroyFramesForAndRestyle(aElement);
  }

  // Finally remove the binding...
  // XXXbz this doesn't remove the implementation!  Should fix!  Until
  // then we need the explicit UnhookEventHandlers here.
  binding->UnhookEventHandlers();
  binding->ChangeDocument(doc, nullptr);
  aElement->SetXBLBinding(nullptr, this);
  binding->MarkForDeath();

  // ...and recreate its frames. We need to do this since the frames may have
  // been removed and style may have changed due to the removal of the
  // anonymous children.
  // XXXbz this should be using the current doc (if any), not the owner doc.
  presShell = doc->GetShell();  // get the shell again, just in case it changed
  NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);

  presShell->PostRecreateFramesFor(aElement);
  return NS_OK;
}

nsresult nsBindingManager::LoadBindingDocument(nsIDocument* aBoundDoc,
                                               nsIURI* aURL,
                                               nsIPrincipal* aOriginPrincipal) {
  NS_PRECONDITION(aURL, "Must have a URI to load!");

  // First we need to load our binding.
  nsXBLService* xblService = nsXBLService::GetInstance();
  if (!xblService) return NS_ERROR_FAILURE;

  // Load the binding doc.
  RefPtr<nsXBLDocumentInfo> info;
  xblService->LoadBindingDocumentInfo(
      nullptr, aBoundDoc, aURL, aOriginPrincipal, true, getter_AddRefs(info));
  if (!info) return NS_ERROR_FAILURE;

  return NS_OK;
}

void nsBindingManager::RemoveFromAttachedQueue(nsXBLBinding* aBinding) {
  // Don't remove items here as that could mess up an executing
  // ProcessAttachedQueue. Instead, null the entry in the queue.
  size_t index = mAttachedStack.IndexOf(aBinding);
  if (index != mAttachedStack.NoIndex) {
    mAttachedStack[index] = nullptr;
  }
}

nsresult nsBindingManager::AddToAttachedQueue(nsXBLBinding* aBinding) {
  mAttachedStack.AppendElement(aBinding);

  // If we're in the middle of processing our queue already, don't
  // bother posting the event.
  if (!mProcessingAttachedStack && !mProcessAttachedQueueEvent) {
    PostProcessAttachedQueueEvent();
  }

  // Make sure that flushes will flush out the new items as needed.
  if (nsIPresShell* shell = mDocument->GetShell()) {
    shell->SetNeedStyleFlush();
  }

  return NS_OK;
}

void nsBindingManager::PostProcessAttachedQueueEvent() {
  MOZ_ASSERT(NS_IsMainThread());
  if (!mDocument) {
    return;
  }
  mProcessAttachedQueueEvent =
      NewRunnableMethod("nsBindingManager::DoProcessAttachedQueue", this,
                        &nsBindingManager::DoProcessAttachedQueue);
  nsresult rv = mDocument->EventTargetFor(TaskCategory::Other)
                    ->Dispatch(do_AddRef(mProcessAttachedQueueEvent));
  if (NS_SUCCEEDED(rv)) {
    mDocument->BlockOnload();
  }
}

// static
void nsBindingManager::PostPAQEventCallback(nsITimer* aTimer, void* aClosure) {
  RefPtr<nsBindingManager> mgr = already_AddRefed<nsBindingManager>(
      static_cast<nsBindingManager*>(aClosure));
  mgr->PostProcessAttachedQueueEvent();
  NS_RELEASE(aTimer);
}

void nsBindingManager::DoProcessAttachedQueue() {
  if (!mProcessingAttachedStack) {
    ProcessAttachedQueue();

    NS_ASSERTION(mAttachedStack.Length() == 0,
                 "Shouldn't have pending bindings!");

    mProcessAttachedQueueEvent = nullptr;
  } else {
    // Someone's doing event processing from inside a constructor.
    // They're evil, but we'll fight back!  Just poll on them being
    // done and repost the attached queue event.
    //
    // But don't poll in a tight loop -- otherwise we keep the Gecko
    // event loop non-empty and trigger bug 1021240 on OS X.
    nsresult rv = NS_ERROR_FAILURE;
    nsCOMPtr<nsITimer> timer;
    rv = NS_NewTimerWithFuncCallback(
        getter_AddRefs(timer), PostPAQEventCallback, this, 100,
        nsITimer::TYPE_ONE_SHOT, "nsBindingManager::DoProcessAttachedQueue");
    if (NS_SUCCEEDED(rv)) {
      NS_ADDREF_THIS();
      // We drop our reference to the timer here, since the timer callback is
      // responsible for releasing the object.
      Unused << timer.forget().take();
    }
  }

  // No matter what, unblock onload for the event that's fired.
  if (mDocument) {
    // Hold a strong reference while calling UnblockOnload since that might
    // run script.
    nsCOMPtr<nsIDocument> doc = mDocument;
    doc->UnblockOnload(true);
  }
}

void nsBindingManager::ProcessAttachedQueueInternal(uint32_t aSkipSize) {
  mProcessingAttachedStack = true;

  // Excute constructors. Do this from high index to low
  while (mAttachedStack.Length() > aSkipSize) {
    uint32_t lastItem = mAttachedStack.Length() - 1;
    RefPtr<nsXBLBinding> binding = mAttachedStack.ElementAt(lastItem);
    mAttachedStack.RemoveElementAt(lastItem);
    if (binding) {
      binding->ExecuteAttachedHandler();
    }
  }

  // If NodeWillBeDestroyed has run we don't want to clobber
  // mProcessingAttachedStack set there.
  if (mDocument) {
    mProcessingAttachedStack = false;
  }

  NS_ASSERTION(mAttachedStack.Length() == aSkipSize, "How did we get here?");

  mAttachedStack.Compact();
}

// Keep bindings and bound elements alive while executing detached handlers.
void nsBindingManager::ExecuteDetachedHandlers() {
  // Walk our hashtable of bindings.
  if (!mBoundContentSet) {
    return;
  }

  nsCOMArray<nsIContent> boundElements;
  nsBindingList bindings;

  for (auto iter = mBoundContentSet->Iter(); !iter.Done(); iter.Next()) {
    nsXBLBinding* binding = iter.Get()->GetKey()->GetXBLBinding();
    if (binding && bindings.AppendElement(binding)) {
      if (!boundElements.AppendObject(binding->GetBoundElement())) {
        bindings.RemoveElementAt(bindings.Length() - 1);
      }
    }
  }

  uint32_t i, count = bindings.Length();
  for (i = 0; i < count; ++i) {
    bindings[i]->ExecuteDetachedHandler();
  }
}

nsresult nsBindingManager::PutXBLDocumentInfo(
    nsXBLDocumentInfo* aDocumentInfo) {
  NS_PRECONDITION(aDocumentInfo, "Must have a non-null documentinfo!");

  if (!mDocumentTable) {
    mDocumentTable = new nsRefPtrHashtable<nsURIHashKey, nsXBLDocumentInfo>();
  }

  mDocumentTable->Put(aDocumentInfo->DocumentURI(), aDocumentInfo);

  return NS_OK;
}

void nsBindingManager::RemoveXBLDocumentInfo(nsXBLDocumentInfo* aDocumentInfo) {
  if (mDocumentTable) {
    mDocumentTable->Remove(aDocumentInfo->DocumentURI());
  }
}

nsXBLDocumentInfo* nsBindingManager::GetXBLDocumentInfo(nsIURI* aURL) {
  if (!mDocumentTable) return nullptr;

  return mDocumentTable->GetWeak(aURL);
}

nsresult nsBindingManager::PutLoadingDocListener(nsIURI* aURL,
                                                 nsIStreamListener* aListener) {
  NS_PRECONDITION(aListener, "Must have a non-null listener!");

  if (!mLoadingDocTable) {
    mLoadingDocTable =
        new nsInterfaceHashtable<nsURIHashKey, nsIStreamListener>();
  }
  mLoadingDocTable->Put(aURL, aListener);

  return NS_OK;
}

nsIStreamListener* nsBindingManager::GetLoadingDocListener(nsIURI* aURL) {
  if (!mLoadingDocTable) return nullptr;

  return mLoadingDocTable->GetWeak(aURL);
}

void nsBindingManager::RemoveLoadingDocListener(nsIURI* aURL) {
  if (mLoadingDocTable) {
    mLoadingDocTable->Remove(aURL);
  }
}

void nsBindingManager::FlushSkinBindings() {
  if (!mBoundContentSet) {
    return;
  }

  for (auto iter = mBoundContentSet->Iter(); !iter.Done(); iter.Next()) {
    nsXBLBinding* binding = iter.Get()->GetKey()->GetXBLBinding();

    if (binding->MarkedForDeath()) {
      continue;
    }

    nsAutoCString path;
    binding->PrototypeBinding()->DocURI()->GetPathQueryRef(path);

    if (!strncmp(path.get(), "/skin", 5)) {
      binding->MarkForDeath();
    }
  }
}

// Used below to protect from recurring in QI calls through XPConnect.
struct AntiRecursionData {
  nsIContent* element;
  REFNSIID iid;
  AntiRecursionData* next;

  AntiRecursionData(nsIContent* aElement, REFNSIID aIID,
                    AntiRecursionData* aNext)
      : element(aElement), iid(aIID), next(aNext) {}
};

nsresult nsBindingManager::GetBindingImplementation(nsIContent* aContent,
                                                    REFNSIID aIID,
                                                    void** aResult) {
  *aResult = nullptr;
  nsXBLBinding* binding = aContent ? aContent->GetXBLBinding() : nullptr;
  if (binding) {
    // The binding should not be asked for nsISupports
    NS_ASSERTION(!aIID.Equals(NS_GET_IID(nsISupports)),
                 "Asking a binding for nsISupports");
    if (binding->ImplementsInterface(aIID)) {
      nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS = GetWrappedJS(aContent);

      if (wrappedJS) {
        // Protect from recurring in QI calls through XPConnect.
        // This can happen when a second binding is being resolved.
        // At that point a wrappedJS exists, but it doesn't yet know about
        // the iid we are asking for. So, without this protection,
        // AggregatedQueryInterface would end up recurring back into itself
        // through this code.
        //
        // With this protection, when we detect the recursion we return
        // NS_NOINTERFACE in the inner call. The outer call will then fall
        // through (see below) and build a new chained wrappedJS for the iid.
        //
        // We're careful to not assume that only one direct nesting can occur
        // because there is a call into JS in the middle and we can't assume
        // that this code won't be reached by some more complex nesting path.
        //
        // NOTE: We *assume* this is single threaded, so we can use a
        // static linked list to do the check.

        static AntiRecursionData* list = nullptr;

        for (AntiRecursionData* p = list; p; p = p->next) {
          if (p->element == aContent && p->iid.Equals(aIID)) {
            *aResult = nullptr;
            return NS_NOINTERFACE;
          }
        }

        AntiRecursionData item(aContent, aIID, list);
        list = &item;

        nsresult rv = wrappedJS->AggregatedQueryInterface(aIID, aResult);

        list = item.next;

        if (*aResult) return rv;

        // No result was found, so this must be another XBL interface.
        // Fall through to create a new wrapper.
      }

      // We have never made a wrapper for this implementation.
      // Create an XPC wrapper for the script object and hand it back.
      AutoJSAPI jsapi;
      jsapi.Init();
      JSContext* cx = jsapi.cx();

      nsIXPConnect* xpConnect = nsContentUtils::XPConnect();

      JS::Rooted<JSObject*> jsobj(cx, aContent->GetWrapper());
      NS_ENSURE_TRUE(jsobj, NS_NOINTERFACE);

      // If we're using an XBL scope, we need to use the Xray view to the bound
      // content in order to view the full array of methods defined in the
      // binding, some of which may not be exposed on the prototype of
      // untrusted content. We don't need to consider add-on scopes here
      // because they're chrome-only and no Xrays are involved.
      //
      // If there's no separate XBL scope, or if the reflector itself lives in
      // the XBL scope, we'll end up with the global of the reflector.
      JS::Rooted<JSObject*> xblScope(cx, xpc::GetXBLScopeOrGlobal(cx, jsobj));
      NS_ENSURE_TRUE(xblScope, NS_ERROR_UNEXPECTED);
      JSAutoCompartment ac(cx, xblScope);
      bool ok = JS_WrapObject(cx, &jsobj);
      NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
      MOZ_ASSERT_IF(js::IsWrapper(jsobj), xpc::IsXrayWrapper(jsobj));

      nsresult rv = xpConnect->WrapJSAggregatedToNative(aContent, cx, jsobj,
                                                        aIID, aResult);
      if (NS_FAILED(rv)) return rv;

      // We successfully created a wrapper.  We will own this wrapper for as
      // long as the binding remains alive.  At the time the binding is cleared
      // out of the bindingManager, we will remove the wrapper from the
      // bindingManager as well.
      nsISupports* supp = static_cast<nsISupports*>(*aResult);
      wrappedJS = do_QueryInterface(supp);
      SetWrappedJS(aContent, wrappedJS);

      return rv;
    }
  }

  *aResult = nullptr;
  return NS_NOINTERFACE;
}

#ifdef MOZ_OLD_STYLE
nsresult nsBindingManager::WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc,
                                     ElementDependentRuleProcessorData* aData,
                                     bool* aCutOffInheritance) {
  *aCutOffInheritance = false;

  NS_ASSERTION(aData->mElement, "How did that happen?");

  // Walk the binding scope chain, starting with the binding attached to our
  // content, up till we run out of scopes or we get cut off.
  nsIContent* content = aData->mElement;

  do {
    nsXBLBinding* binding = content->GetXBLBinding();
    if (binding) {
      binding->WalkRules(aFunc, aData);
      // If we're not looking at our original content, allow the binding to cut
      // off style inheritance
      if (content != aData->mElement) {
        if (!binding->InheritsStyle()) {
          // Go no further; we're not inheriting style from anything above here
          break;
        }
      }
    }

    if (content->IsRootOfNativeAnonymousSubtree()) {
      break;  // Deliberately cut off style inheritance here.
    }

    content = content->GetBindingParent();
  } while (content);

  // If "content" is non-null that means we cut off inheritance at some point
  // in the loop.
  *aCutOffInheritance = (content != nullptr);

  return NS_OK;
}
#endif

bool nsBindingManager::EnumerateBoundContentProtoBindings(
    const BoundContentProtoBindingCallback& aCallback) const {
  if (!mBoundContentSet) {
    return true;
  }

  nsTHashtable<nsPtrHashKey<nsXBLPrototypeBinding>> bindings;
  for (auto iter = mBoundContentSet->Iter(); !iter.Done(); iter.Next()) {
    nsIContent* boundContent = iter.Get()->GetKey();
    for (nsXBLBinding* binding = boundContent->GetXBLBinding(); binding;
         binding = binding->GetBaseBinding()) {
      nsXBLPrototypeBinding* proto = binding->PrototypeBinding();
      // If we have already invoked the callback with a binding, we
      // should have also invoked it for all its base bindings, so we
      // don't need to continue this loop anymore.
      if (!bindings.EnsureInserted(proto)) {
        break;
      }
      if (!aCallback(proto)) {
        return false;
      }
    }
  }

  return true;
}

#ifdef MOZ_OLD_STYLE
void nsBindingManager::WalkAllRules(nsIStyleRuleProcessor::EnumFunc aFunc,
                                    ElementDependentRuleProcessorData* aData) {
  EnumerateBoundContentProtoBindings([=](nsXBLPrototypeBinding* aProto) {
    nsIStyleRuleProcessor* ruleProcessor = aProto->GetRuleProcessor();
    if (ruleProcessor) {
      (*(aFunc))(ruleProcessor, aData);
    }
    return true;
  });
}
#endif

bool nsBindingManager::MediumFeaturesChanged(
    nsPresContext* aPresContext, mozilla::MediaFeatureChangeReason aReason) {
  MOZ_ASSERT(!mDocument->IsStyledByServo());
#ifdef MOZ_OLD_STYLE
  bool rulesChanged = false;
  RefPtr<nsPresContext> presContext = aPresContext;
  EnumerateBoundContentProtoBindings(
      [=, &rulesChanged](nsXBLPrototypeBinding* aProto) {
        nsIStyleRuleProcessor* ruleProcessor = aProto->GetRuleProcessor();
        if (ruleProcessor) {
          bool thisChanged = ruleProcessor->MediumFeaturesChanged(presContext);
          rulesChanged = rulesChanged || thisChanged;
        }
        return true;
      });
  return rulesChanged;
#else
  MOZ_CRASH("old style system disabled");
  return false;
#endif
}

void nsBindingManager::AppendAllSheets(nsTArray<StyleSheet*>& aArray) {
  EnumerateBoundContentProtoBindings([&aArray](nsXBLPrototypeBinding* aProto) {
    aProto->AppendStyleSheetsTo(aArray);
    return true;
  });
}

static void InsertAppendedContent(XBLChildrenElement* aPoint,
                                  nsIContent* aFirstNewContent) {
  int32_t insertionIndex;
  if (nsIContent* prevSibling = aFirstNewContent->GetPreviousSibling()) {
    // If we have a previous sibling, then it must already be in aPoint. Find
    // it and insert after it.
    insertionIndex = aPoint->IndexOfInsertedChild(prevSibling);
    MOZ_ASSERT(insertionIndex != -1);

    // Our insertion index is one after our previous sibling's index.
    ++insertionIndex;
  } else {
    // Otherwise, we append.
    // TODO This is wrong for nested insertion points. In that case, we need to
    // keep track of the right index to insert into.
    insertionIndex = aPoint->InsertedChildrenLength();
  }

  // Do the inserting.
  for (nsIContent* currentChild = aFirstNewContent; currentChild;
       currentChild = currentChild->GetNextSibling()) {
    aPoint->InsertInsertedChildAt(currentChild, insertionIndex++);
  }
}

void nsBindingManager::ContentAppended(nsIContent* aFirstNewContent) {
  // Try to find insertion points for all the new kids.
  XBLChildrenElement* point = nullptr;
  nsIContent* container = aFirstNewContent->GetParent();
  nsIContent* parent = container;

  // Handle appending of default content.
  if (parent && parent->IsActiveChildrenElement()) {
    XBLChildrenElement* childrenEl = static_cast<XBLChildrenElement*>(parent);
    if (childrenEl->HasInsertedChildren()) {
      // Appending default content that isn't being used. Ignore.
      return;
    }

    childrenEl->MaybeSetupDefaultContent();
    parent = childrenEl->GetParent();
  }

  bool first = true;
  do {
    nsXBLBinding* binding = GetBindingWithContent(parent);
    if (!binding) {
      break;
    }

    if (binding->HasFilteredInsertionPoints()) {
      // There are filtered insertion points involved, handle each child
      // separately.
      // We could optimize this in the case when we've nested a few levels
      // deep already, without hitting bindings that have filtered insertion
      // points.
      for (nsIContent* currentChild = aFirstNewContent; currentChild;
           currentChild = currentChild->GetNextSibling()) {
        HandleChildInsertion(container, currentChild, true);
      }

      return;
    }

    point = binding->GetDefaultInsertionPoint();
    if (!point) {
      break;
    }

    // Even though we're in ContentAppended, nested insertion points force us
    // to deal with this append as an insertion except in the outermost
    // binding.
    if (first) {
      first = false;
      for (nsIContent* child = aFirstNewContent; child;
           child = child->GetNextSibling()) {
        point->AppendInsertedChild(child);
      }
    } else {
      InsertAppendedContent(point, aFirstNewContent);
    }

    nsIContent* newParent = point->GetParent();
    if (newParent == parent) {
      break;
    }
    parent = newParent;
  } while (parent);
}

void nsBindingManager::ContentInserted(nsIContent* aChild) {
  HandleChildInsertion(aChild->GetParent(), aChild, false);
}

void nsBindingManager::ContentRemoved(nsIContent* aChild,
                                      nsIContent* aPreviousSibling) {
  aChild->SetXBLInsertionPoint(nullptr);

  XBLChildrenElement* point = nullptr;
  nsIContent* parent = aChild->GetParent();

  // Handle appending of default content.
  if (parent && parent->IsActiveChildrenElement()) {
    XBLChildrenElement* childrenEl = static_cast<XBLChildrenElement*>(parent);
    if (childrenEl->HasInsertedChildren()) {
      // Removing default content that isn't being used. Ignore.
      return;
    }

    parent = childrenEl->GetParent();
  }

  do {
    nsXBLBinding* binding = GetBindingWithContent(parent);
    if (!binding) {
      // If aChild is XBL content, it might have <xbl:children> elements
      // somewhere under it. We need to inform those elements that they're no
      // longer in the tree so they can tell their distributed children that
      // they're no longer distributed under them.
      // XXX This is wrong. We need to do far more work to update the parent
      // binding's list of insertion points and to get the new insertion parent
      // for the newly-distributed children correct.
      if (aChild->GetBindingParent()) {
        ClearInsertionPointsRecursively(aChild);
      }
      return;
    }

    point = binding->FindInsertionPointFor(aChild);
    if (!point) {
      break;
    }

    point->RemoveInsertedChild(aChild);

    nsIContent* newParent = point->GetParent();
    if (newParent == parent) {
      break;
    }
    parent = newParent;
  } while (parent);
}

void nsBindingManager::ClearInsertionPointsRecursively(nsIContent* aContent) {
  if (aContent->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
    static_cast<XBLChildrenElement*>(aContent)->ClearInsertedChildren();
  }

  for (nsIContent* child = aContent->GetFirstChild(); child;
       child = child->GetNextSibling()) {
    ClearInsertionPointsRecursively(child);
  }
}

void nsBindingManager::DropDocumentReference() {
  mDestroyed = true;

  // Make sure to not run any more XBL constructors
  mProcessingAttachedStack = true;
  if (mProcessAttachedQueueEvent) {
    mProcessAttachedQueueEvent->Revoke();
  }

  if (mBoundContentSet) {
    mBoundContentSet->Clear();
  }

  mDocument = nullptr;
}

void nsBindingManager::Traverse(nsIContent* aContent,
                                nsCycleCollectionTraversalCallback& cb) {
  if (!aContent->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) ||
      !aContent->IsElement()) {
    // Don't traverse if content is not in this binding manager.
    // We also don't traverse non-elements because there should not
    // be bindings (checking the flag alone is not sufficient because
    // the flag is also set on children of insertion points that may be
    // non-elements).
    return;
  }

  if (mBoundContentSet && mBoundContentSet->Contains(aContent)) {
    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
        cb, "[via binding manager] mBoundContentSet entry");
    cb.NoteXPCOMChild(aContent);
  }

  nsIXPConnectWrappedJS* value = GetWrappedJS(aContent);
  if (value) {
    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
        cb, "[via binding manager] mWrapperTable key");
    cb.NoteXPCOMChild(aContent);
    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
        cb, "[via binding manager] mWrapperTable value");
    cb.NoteXPCOMChild(value);
  }
}

void nsBindingManager::HandleChildInsertion(nsIContent* aContainer,
                                            nsIContent* aChild, bool aAppend) {
  MOZ_ASSERT(aChild, "Must have child");

  XBLChildrenElement* point = nullptr;
  nsIContent* parent = aContainer;

  // Handle insertion of default content.
  if (parent && parent->IsActiveChildrenElement()) {
    XBLChildrenElement* childrenEl = static_cast<XBLChildrenElement*>(parent);
    if (childrenEl->HasInsertedChildren()) {
      // Inserting default content that isn't being used. Ignore.
      return;
    }

    childrenEl->MaybeSetupDefaultContent();
    parent = childrenEl->GetParent();
  }

  while (parent) {
    nsXBLBinding* binding = GetBindingWithContent(parent);
    if (!binding) {
      break;
    }

    point = binding->FindInsertionPointFor(aChild);
    if (!point) {
      break;
    }

    // Insert the child into the proper insertion point.
    // TODO If there were multiple insertion points, this approximation can be
    // wrong. We need to re-run the distribution algorithm. In the meantime,
    // this should work well enough.
    uint32_t index = aAppend ? point->InsertedChildrenLength() : 0;
    for (nsIContent* currentSibling = aChild->GetPreviousSibling();
         currentSibling;
         currentSibling = currentSibling->GetPreviousSibling()) {
      // If we find one of our previous siblings in the insertion point, the
      // index following it is the correct insertion point. Otherwise, we guess
      // based on whether we're appending or inserting.
      int32_t pointIndex = point->IndexOfInsertedChild(currentSibling);
      if (pointIndex != -1) {
        index = pointIndex + 1;
        break;
      }
    }

    point->InsertInsertedChildAt(aChild, index);

    nsIContent* newParent = point->GetParent();
    if (newParent == parent) {
      break;
    }

    parent = newParent;
  }
}

nsIContent* nsBindingManager::FindNestedSingleInsertionPoint(
    nsIContent* aContainer, bool* aMulti) {
  *aMulti = false;

  nsIContent* parent = aContainer;
  if (aContainer->IsActiveChildrenElement()) {
    if (static_cast<XBLChildrenElement*>(aContainer)->HasInsertedChildren()) {
      return nullptr;
    }
    parent = aContainer->GetParent();
  }

  while (parent) {
    nsXBLBinding* binding = GetBindingWithContent(parent);
    if (!binding) {
      break;
    }

    if (binding->HasFilteredInsertionPoints()) {
      *aMulti = true;
      return nullptr;
    }

    XBLChildrenElement* point = binding->GetDefaultInsertionPoint();
    if (!point) {
      return nullptr;
    }

    nsIContent* newParent = point->GetParent();
    if (newParent == parent) {
      break;
    }
    parent = newParent;
  }

  return parent;
}

size_t nsBindingManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
  size_t n = aMallocSizeOf(this);

#define SHALLOW_SIZE_INCLUDING(field_) \
  n += field_ ? field_->ShallowSizeOfIncludingThis(aMallocSizeOf) : 0;
  SHALLOW_SIZE_INCLUDING(mBoundContentSet);
  SHALLOW_SIZE_INCLUDING(mWrapperTable);
  SHALLOW_SIZE_INCLUDING(mLoadingDocTable);
#undef SHALLOW_SIZE_INCLUDING
  n += mAttachedStack.ShallowSizeOfExcludingThis(aMallocSizeOf);

  if (mDocumentTable) {
    n += mDocumentTable->ShallowSizeOfIncludingThis(aMallocSizeOf);
#ifdef MOZ_XUL
    nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
    StyleBackendType backendType = mDocument->GetStyleBackendType();
#endif
    for (auto iter = mDocumentTable->Iter(); !iter.Done(); iter.Next()) {
      nsXBLDocumentInfo* docInfo = iter.UserData();
#ifdef MOZ_XUL
      nsXBLDocumentInfo* cachedInfo =
          cache->GetXBLDocumentInfo(iter.Key(), backendType);
      if (cachedInfo == docInfo) {
        // If this binding has been cached, skip it since it can be
        // reused by other documents.
        continue;
      }
#endif
      n += docInfo->SizeOfIncludingThis(aMallocSizeOf);
    }
  }

  return n;
}