diff options
Diffstat (limited to 'dom/xbl/nsBindingManager.cpp')
-rw-r--r-- | dom/xbl/nsBindingManager.cpp | 1160 |
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; +} |