summaryrefslogtreecommitdiffstats
path: root/dom/xbl/nsBindingManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/xbl/nsBindingManager.cpp')
-rw-r--r--dom/xbl/nsBindingManager.cpp1160
1 files changed, 1160 insertions, 0 deletions
diff --git a/dom/xbl/nsBindingManager.cpp b/dom/xbl/nsBindingManager.cpp
new file mode 100644
index 000000000..405c7aac7
--- /dev/null
+++ b/dom/xbl/nsBindingManager.cpp
@@ -0,0 +1,1160 @@
+/* -*- 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 "nsXPIDLString.h"
+#include "plstr.h"
+#include "nsIContent.h"
+#include "nsIDOMElement.h"
+#include "nsIDocument.h"
+#include "nsContentUtils.h"
+#include "nsIPresShell.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"
+
+#include "nsIStyleRuleProcessor.h"
+#include "nsRuleProcessorData.h"
+#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 "jswrapper.h"
+
+#include "nsThreadUtils.h"
+#include "mozilla/dom/NodeListBinding.h"
+#include "mozilla/dom/ScriptSettings.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->SetXBLBinding(nullptr, this);
+ }
+
+ // Clear out insertion parent and content lists.
+ aContent->SetXBLInsertionParent(nullptr);
+}
+
+nsIAtom*
+nsBindingManager::ResolveTag(nsIContent* aContent, int32_t* aNameSpaceID)
+{
+ nsXBLBinding *binding = aContent->GetXBLBinding();
+
+ if (binding) {
+ nsIAtom* 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(nsIContent* aContent)
+{
+ // Hold a ref to the binding so it won't die when we remove it from our table
+ RefPtr<nsXBLBinding> binding =
+ aContent ? aContent->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 = aContent->OwnerDoc();
+
+ // 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);
+ aContent->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.
+ nsIPresShell *presShell = doc->GetShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ return presShell->RecreateFramesFor(aContent);;
+}
+
+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.
+ mDocument->SetNeedStyleFlush();
+
+ return NS_OK;
+
+}
+
+void
+nsBindingManager::PostProcessAttachedQueueEvent()
+{
+ mProcessAttachedQueueEvent =
+ NewRunnableMethod(this, &nsBindingManager::DoProcessAttachedQueue);
+ nsresult rv = NS_DispatchToCurrentThread(mProcessAttachedQueueEvent);
+ if (NS_SUCCEEDED(rv) && mDocument) {
+ 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 = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (timer) {
+ rv = timer->InitWithFuncCallback(PostPAQEventCallback, this,
+ 100, nsITimer::TYPE_ONE_SHOT);
+ }
+ 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()->GetPath(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;
+}
+
+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) {
+ aData->mTreeMatchContext.mScopedRoot = content;
+ 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);
+
+ // Null out the scoped root that we set repeatedly
+ aData->mTreeMatchContext.mScopedRoot = nullptr;
+
+ return NS_OK;
+}
+
+typedef nsTHashtable<nsPtrHashKey<nsIStyleRuleProcessor> > RuleProcessorSet;
+
+static RuleProcessorSet*
+GetContentSetRuleProcessors(nsTHashtable<nsRefPtrHashKey<nsIContent>>* aContentSet)
+{
+ RuleProcessorSet* set = nullptr;
+
+ for (auto iter = aContentSet->Iter(); !iter.Done(); iter.Next()) {
+ nsIContent* boundContent = iter.Get()->GetKey();
+ for (nsXBLBinding* binding = boundContent->GetXBLBinding(); binding;
+ binding = binding->GetBaseBinding()) {
+ nsIStyleRuleProcessor* ruleProc =
+ binding->PrototypeBinding()->GetRuleProcessor();
+ if (ruleProc) {
+ if (!set) {
+ set = new RuleProcessorSet;
+ }
+ set->PutEntry(ruleProc);
+ }
+ }
+ }
+
+ return set;
+}
+
+void
+nsBindingManager::WalkAllRules(nsIStyleRuleProcessor::EnumFunc aFunc,
+ ElementDependentRuleProcessorData* aData)
+{
+ if (!mBoundContentSet) {
+ return;
+ }
+
+ nsAutoPtr<RuleProcessorSet> set;
+ set = GetContentSetRuleProcessors(mBoundContentSet);
+ if (!set) {
+ return;
+ }
+
+ for (auto iter = set->Iter(); !iter.Done(); iter.Next()) {
+ nsIStyleRuleProcessor* ruleProcessor = iter.Get()->GetKey();
+ (*(aFunc))(ruleProcessor, aData);
+ }
+}
+
+nsresult
+nsBindingManager::MediumFeaturesChanged(nsPresContext* aPresContext,
+ bool* aRulesChanged)
+{
+ *aRulesChanged = false;
+ if (!mBoundContentSet) {
+ return NS_OK;
+ }
+
+ nsAutoPtr<RuleProcessorSet> set;
+ set = GetContentSetRuleProcessors(mBoundContentSet);
+ if (!set) {
+ return NS_OK;
+ }
+
+ for (auto iter = set->Iter(); !iter.Done(); iter.Next()) {
+ nsIStyleRuleProcessor* ruleProcessor = iter.Get()->GetKey();
+ bool thisChanged = ruleProcessor->MediumFeaturesChanged(aPresContext);
+ *aRulesChanged = *aRulesChanged || thisChanged;
+ }
+
+ return NS_OK;
+}
+
+void
+nsBindingManager::AppendAllSheets(nsTArray<StyleSheet*>& aArray)
+{
+ if (!mBoundContentSet) {
+ return;
+ }
+
+ for (auto iter = mBoundContentSet->Iter(); !iter.Done(); iter.Next()) {
+ nsIContent* boundContent = iter.Get()->GetKey();
+ for (nsXBLBinding* binding = boundContent->GetXBLBinding(); binding;
+ binding = binding->GetBaseBinding()) {
+ binding->PrototypeBinding()->AppendStyleSheetsTo(aArray);
+ }
+ }
+}
+
+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(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer)
+{
+ if (aNewIndexInContainer == -1) {
+ return;
+ }
+
+ // Try to find insertion points for all the new kids.
+ XBLChildrenElement* point = nullptr;
+ nsIContent* parent = aContainer;
+
+ // 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.
+ int32_t currentIndex = aNewIndexInContainer;
+ for (nsIContent* currentChild = aFirstNewContent; currentChild;
+ currentChild = currentChild->GetNextSibling()) {
+ HandleChildInsertion(aContainer, currentChild,
+ currentIndex++, 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(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ if (aIndexInContainer == -1) {
+ return;
+ }
+
+ HandleChildInsertion(aContainer, aChild, aIndexInContainer, false);
+}
+
+void
+nsBindingManager::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ aChild->SetXBLInsertionParent(nullptr);
+
+ XBLChildrenElement* point = nullptr;
+ nsIContent* parent = aContainer;
+
+ // 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,
+ uint32_t aIndexInContainer,
+ bool aAppend)
+{
+ NS_PRECONDITION(aChild, "Must have child");
+ NS_PRECONDITION(!aContainer ||
+ uint32_t(aContainer->IndexOf(aChild)) == aIndexInContainer,
+ "Child not at the right index?");
+
+ 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::FindNestedInsertionPoint(nsIContent* aContainer,
+ nsIContent* aChild)
+{
+ NS_PRECONDITION(aChild->GetParent() == aContainer,
+ "Wrong container");
+
+ 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;
+ }
+
+ XBLChildrenElement* point = binding->FindInsertionPointFor(aChild);
+ if (!point) {
+ return nullptr;
+ }
+
+ nsIContent* newParent = point->GetParent();
+ if (newParent == parent) {
+ break;
+ }
+ parent = newParent;
+ }
+
+ return parent;
+}
+
+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;
+}