michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=79: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsBindingManager.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsXBLService.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIURL.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "nsNetUtil.h" michael@0: #include "plstr.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsIXMLContentSink.h" michael@0: #include "nsContentCID.h" michael@0: #include "mozilla/dom/XMLDocument.h" michael@0: #include "nsIStreamListener.h" michael@0: #include "ChildIterator.h" michael@0: michael@0: #include "nsXBLBinding.h" michael@0: #include "nsXBLPrototypeBinding.h" michael@0: #include "nsXBLDocumentInfo.h" michael@0: #include "mozilla/dom/XBLChildrenElement.h" michael@0: michael@0: #include "nsIStyleRuleProcessor.h" michael@0: #include "nsRuleProcessorData.h" michael@0: #include "nsIWeakReference.h" michael@0: michael@0: #include "nsWrapperCacheInlines.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "nsDOMCID.h" michael@0: #include "nsIDOMScriptObjectFactory.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsTHashtable.h" michael@0: michael@0: #include "nsIScriptContext.h" michael@0: #include "xpcpublic.h" michael@0: #include "jswrapper.h" michael@0: #include "nsCxPusher.h" michael@0: michael@0: #include "nsThreadUtils.h" michael@0: #include "mozilla/dom/NodeListBinding.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: // Implement our nsISupports methods michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsBindingManager) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsBindingManager) michael@0: tmp->mDestroyed = true; michael@0: michael@0: if (tmp->mBoundContentSet) michael@0: tmp->mBoundContentSet->Clear(); michael@0: michael@0: if (tmp->mDocumentTable) michael@0: tmp->mDocumentTable->Clear(); michael@0: michael@0: if (tmp->mLoadingDocTable) michael@0: tmp->mLoadingDocTable->Clear(); michael@0: michael@0: if (tmp->mWrapperTable) { michael@0: tmp->mWrapperTable->Clear(); michael@0: tmp->mWrapperTable = nullptr; michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mAttachedStack) michael@0: michael@0: if (tmp->mProcessAttachedQueueEvent) { michael@0: tmp->mProcessAttachedQueueEvent->Revoke(); michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: michael@0: static PLDHashOperator michael@0: DocumentInfoHashtableTraverser(nsIURI* key, michael@0: nsXBLDocumentInfo* di, michael@0: void* userArg) michael@0: { michael@0: nsCycleCollectionTraversalCallback *cb = michael@0: static_cast(userArg); michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mDocumentTable value"); michael@0: cb->NoteXPCOMChild(di); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: LoadingDocHashtableTraverser(nsIURI* key, michael@0: nsIStreamListener* sl, michael@0: void* userArg) michael@0: { michael@0: nsCycleCollectionTraversalCallback *cb = michael@0: static_cast(userArg); michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mLoadingDocTable value"); michael@0: cb->NoteXPCOMChild(sl); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsBindingManager) michael@0: // The hashes keyed on nsIContent are traversed from the nsIContent itself. michael@0: if (tmp->mDocumentTable) michael@0: tmp->mDocumentTable->EnumerateRead(&DocumentInfoHashtableTraverser, &cb); michael@0: if (tmp->mLoadingDocTable) michael@0: tmp->mLoadingDocTable->EnumerateRead(&LoadingDocHashtableTraverser, &cb); michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAttachedStack) michael@0: // No need to traverse mProcessAttachedQueueEvent, since it'll just michael@0: // fire at some point or become revoke and drop its ref to us. michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsBindingManager) michael@0: NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsBindingManager) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsBindingManager) michael@0: michael@0: // Constructors/Destructors michael@0: nsBindingManager::nsBindingManager(nsIDocument* aDocument) michael@0: : mProcessingAttachedStack(false), michael@0: mDestroyed(false), michael@0: mAttachedStackSizeOnOutermost(0), michael@0: mDocument(aDocument) michael@0: { michael@0: } michael@0: michael@0: nsBindingManager::~nsBindingManager(void) michael@0: { michael@0: mDestroyed = true; michael@0: } michael@0: michael@0: nsXBLBinding* michael@0: nsBindingManager::GetBindingWithContent(nsIContent* aContent) michael@0: { michael@0: nsXBLBinding* binding = aContent ? aContent->GetXBLBinding() : nullptr; michael@0: return binding ? binding->GetBindingWithContent() : nullptr; michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::AddBoundContent(nsIContent* aContent) michael@0: { michael@0: if (!mBoundContentSet) { michael@0: mBoundContentSet = new nsTHashtable >; michael@0: } michael@0: mBoundContentSet->PutEntry(aContent); michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::RemoveBoundContent(nsIContent* aContent) michael@0: { michael@0: if (mBoundContentSet) { michael@0: mBoundContentSet->RemoveEntry(aContent); michael@0: } michael@0: michael@0: // The death of the bindings means the death of the JS wrapper. michael@0: SetWrappedJS(aContent, nullptr); michael@0: } michael@0: michael@0: nsIXPConnectWrappedJS* michael@0: nsBindingManager::GetWrappedJS(nsIContent* aContent) michael@0: { michael@0: if (!mWrapperTable) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!aContent || !aContent->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return mWrapperTable->GetWeak(aContent); michael@0: } michael@0: michael@0: nsresult michael@0: nsBindingManager::SetWrappedJS(nsIContent* aContent, nsIXPConnectWrappedJS* aWrappedJS) michael@0: { michael@0: if (mDestroyed) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (aWrappedJS) { michael@0: // lazily create the table, but only when adding elements michael@0: if (!mWrapperTable) { michael@0: mWrapperTable = new WrapperHashtable(); michael@0: } michael@0: aContent->SetFlags(NODE_MAY_BE_IN_BINDING_MNGR); michael@0: michael@0: NS_ASSERTION(aContent, "key must be non-null"); michael@0: if (!aContent) return NS_ERROR_INVALID_ARG; michael@0: michael@0: mWrapperTable->Put(aContent, aWrappedJS); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // no value, so remove the key from the table michael@0: if (mWrapperTable) { michael@0: mWrapperTable->Remove(aContent); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::RemovedFromDocumentInternal(nsIContent* aContent, michael@0: nsIDocument* aOldDocument) michael@0: { michael@0: NS_PRECONDITION(aOldDocument != nullptr, "no old document"); michael@0: michael@0: if (mDestroyed) michael@0: return; michael@0: michael@0: nsRefPtr binding = aContent->GetXBLBinding(); michael@0: if (binding) { michael@0: binding->PrototypeBinding()->BindingDetached(binding->GetBoundElement()); michael@0: binding->ChangeDocument(aOldDocument, nullptr); michael@0: aContent->SetXBLBinding(nullptr, this); michael@0: } michael@0: michael@0: // Clear out insertion parents and content lists. michael@0: aContent->SetXBLInsertionParent(nullptr); michael@0: } michael@0: michael@0: nsIAtom* michael@0: nsBindingManager::ResolveTag(nsIContent* aContent, int32_t* aNameSpaceID) michael@0: { michael@0: nsXBLBinding *binding = aContent->GetXBLBinding(); michael@0: michael@0: if (binding) { michael@0: nsIAtom* base = binding->GetBaseTag(aNameSpaceID); michael@0: michael@0: if (base) { michael@0: return base; michael@0: } michael@0: } michael@0: michael@0: *aNameSpaceID = aContent->GetNameSpaceID(); michael@0: return aContent->Tag(); michael@0: } michael@0: michael@0: nsresult michael@0: nsBindingManager::GetAnonymousNodesFor(nsIContent* aContent, michael@0: nsIDOMNodeList** aResult) michael@0: { michael@0: NS_IF_ADDREF(*aResult = GetAnonymousNodesFor(aContent)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsINodeList* michael@0: nsBindingManager::GetAnonymousNodesFor(nsIContent* aContent) michael@0: { michael@0: nsXBLBinding* binding = GetBindingWithContent(aContent); michael@0: return binding ? binding->GetAnonymousNodeList() : nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: nsBindingManager::ClearBinding(nsIContent* aContent) michael@0: { michael@0: // Hold a ref to the binding so it won't die when we remove it from our table michael@0: nsRefPtr binding = michael@0: aContent ? aContent->GetXBLBinding() : nullptr; michael@0: michael@0: if (!binding) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // For now we can only handle removing a binding if it's the only one michael@0: NS_ENSURE_FALSE(binding->GetBaseBinding(), NS_ERROR_FAILURE); michael@0: michael@0: // Hold strong ref in case removing the binding tries to close the michael@0: // window or something. michael@0: // XXXbz should that be ownerdoc? Wouldn't we need a ref to the michael@0: // currentdoc too? What's the one that should be passed to michael@0: // ChangeDocument? michael@0: nsCOMPtr doc = aContent->OwnerDoc(); michael@0: michael@0: // Finally remove the binding... michael@0: // XXXbz this doesn't remove the implementation! Should fix! Until michael@0: // then we need the explicit UnhookEventHandlers here. michael@0: binding->UnhookEventHandlers(); michael@0: binding->ChangeDocument(doc, nullptr); michael@0: aContent->SetXBLBinding(nullptr, this); michael@0: binding->MarkForDeath(); michael@0: michael@0: // ...and recreate its frames. We need to do this since the frames may have michael@0: // been removed and style may have changed due to the removal of the michael@0: // anonymous children. michael@0: // XXXbz this should be using the current doc (if any), not the owner doc. michael@0: nsIPresShell *presShell = doc->GetShell(); michael@0: NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); michael@0: michael@0: return presShell->RecreateFramesFor(aContent);; michael@0: } michael@0: michael@0: nsresult michael@0: nsBindingManager::LoadBindingDocument(nsIDocument* aBoundDoc, michael@0: nsIURI* aURL, michael@0: nsIPrincipal* aOriginPrincipal) michael@0: { michael@0: NS_PRECONDITION(aURL, "Must have a URI to load!"); michael@0: michael@0: // First we need to load our binding. michael@0: nsXBLService* xblService = nsXBLService::GetInstance(); michael@0: if (!xblService) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Load the binding doc. michael@0: nsRefPtr info; michael@0: xblService->LoadBindingDocumentInfo(nullptr, aBoundDoc, aURL, michael@0: aOriginPrincipal, true, michael@0: getter_AddRefs(info)); michael@0: if (!info) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::RemoveFromAttachedQueue(nsXBLBinding* aBinding) michael@0: { michael@0: // Don't remove items here as that could mess up an executing michael@0: // ProcessAttachedQueue. Instead, null the entry in the queue. michael@0: uint32_t index = mAttachedStack.IndexOf(aBinding); michael@0: if (index != mAttachedStack.NoIndex) { michael@0: mAttachedStack[index] = nullptr; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsBindingManager::AddToAttachedQueue(nsXBLBinding* aBinding) michael@0: { michael@0: if (!mAttachedStack.AppendElement(aBinding)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // If we're in the middle of processing our queue already, don't michael@0: // bother posting the event. michael@0: if (!mProcessingAttachedStack && !mProcessAttachedQueueEvent) { michael@0: PostProcessAttachedQueueEvent(); michael@0: } michael@0: michael@0: // Make sure that flushes will flush out the new items as needed. michael@0: mDocument->SetNeedStyleFlush(); michael@0: michael@0: return NS_OK; michael@0: michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::PostProcessAttachedQueueEvent() michael@0: { michael@0: mProcessAttachedQueueEvent = michael@0: NS_NewRunnableMethod(this, &nsBindingManager::DoProcessAttachedQueue); michael@0: nsresult rv = NS_DispatchToCurrentThread(mProcessAttachedQueueEvent); michael@0: if (NS_SUCCEEDED(rv) && mDocument) { michael@0: mDocument->BlockOnload(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::DoProcessAttachedQueue() michael@0: { michael@0: if (!mProcessingAttachedStack) { michael@0: ProcessAttachedQueue(); michael@0: michael@0: NS_ASSERTION(mAttachedStack.Length() == 0, michael@0: "Shouldn't have pending bindings!"); michael@0: michael@0: mProcessAttachedQueueEvent = nullptr; michael@0: } else { michael@0: // Someone's doing event processing from inside a constructor. michael@0: // They're evil, but we'll fight back! Just poll on them being michael@0: // done and repost the attached queue event. michael@0: PostProcessAttachedQueueEvent(); michael@0: } michael@0: michael@0: // No matter what, unblock onload for the event that's fired. michael@0: if (mDocument) { michael@0: // Hold a strong reference while calling UnblockOnload since that might michael@0: // run script. michael@0: nsCOMPtr doc = mDocument; michael@0: doc->UnblockOnload(true); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::ProcessAttachedQueue(uint32_t aSkipSize) michael@0: { michael@0: if (mProcessingAttachedStack || mAttachedStack.Length() <= aSkipSize) michael@0: return; michael@0: michael@0: mProcessingAttachedStack = true; michael@0: michael@0: // Excute constructors. Do this from high index to low michael@0: while (mAttachedStack.Length() > aSkipSize) { michael@0: uint32_t lastItem = mAttachedStack.Length() - 1; michael@0: nsRefPtr binding = mAttachedStack.ElementAt(lastItem); michael@0: mAttachedStack.RemoveElementAt(lastItem); michael@0: if (binding) { michael@0: binding->ExecuteAttachedHandler(); michael@0: } michael@0: } michael@0: michael@0: // If NodeWillBeDestroyed has run we don't want to clobber michael@0: // mProcessingAttachedStack set there. michael@0: if (mDocument) { michael@0: mProcessingAttachedStack = false; michael@0: } michael@0: michael@0: NS_ASSERTION(mAttachedStack.Length() == aSkipSize, "How did we get here?"); michael@0: michael@0: mAttachedStack.Compact(); michael@0: } michael@0: michael@0: // Keep bindings and bound elements alive while executing detached handlers. michael@0: struct BindingTableReadClosure michael@0: { michael@0: nsCOMArray mBoundElements; michael@0: nsBindingList mBindings; michael@0: }; michael@0: michael@0: static PLDHashOperator michael@0: AccumulateBindingsToDetach(nsRefPtrHashKey *aKey, michael@0: void* aClosure) michael@0: { michael@0: nsXBLBinding *binding = aKey->GetKey()->GetXBLBinding(); michael@0: BindingTableReadClosure* closure = michael@0: static_cast(aClosure); michael@0: if (binding && closure->mBindings.AppendElement(binding)) { michael@0: if (!closure->mBoundElements.AppendObject(binding->GetBoundElement())) { michael@0: closure->mBindings.RemoveElementAt(closure->mBindings.Length() - 1); michael@0: } michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::ExecuteDetachedHandlers() michael@0: { michael@0: // Walk our hashtable of bindings. michael@0: if (mBoundContentSet) { michael@0: BindingTableReadClosure closure; michael@0: mBoundContentSet->EnumerateEntries(AccumulateBindingsToDetach, &closure); michael@0: uint32_t i, count = closure.mBindings.Length(); michael@0: for (i = 0; i < count; ++i) { michael@0: closure.mBindings[i]->ExecuteDetachedHandler(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsBindingManager::PutXBLDocumentInfo(nsXBLDocumentInfo* aDocumentInfo) michael@0: { michael@0: NS_PRECONDITION(aDocumentInfo, "Must have a non-null documentinfo!"); michael@0: michael@0: if (!mDocumentTable) { michael@0: mDocumentTable = new nsRefPtrHashtable(16); michael@0: } michael@0: michael@0: mDocumentTable->Put(aDocumentInfo->DocumentURI(), aDocumentInfo); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::RemoveXBLDocumentInfo(nsXBLDocumentInfo* aDocumentInfo) michael@0: { michael@0: if (mDocumentTable) { michael@0: mDocumentTable->Remove(aDocumentInfo->DocumentURI()); michael@0: } michael@0: } michael@0: michael@0: nsXBLDocumentInfo* michael@0: nsBindingManager::GetXBLDocumentInfo(nsIURI* aURL) michael@0: { michael@0: if (!mDocumentTable) michael@0: return nullptr; michael@0: michael@0: return mDocumentTable->GetWeak(aURL); michael@0: } michael@0: michael@0: nsresult michael@0: nsBindingManager::PutLoadingDocListener(nsIURI* aURL, nsIStreamListener* aListener) michael@0: { michael@0: NS_PRECONDITION(aListener, "Must have a non-null listener!"); michael@0: michael@0: if (!mLoadingDocTable) { michael@0: mLoadingDocTable = new nsInterfaceHashtable(16); michael@0: } michael@0: mLoadingDocTable->Put(aURL, aListener); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIStreamListener* michael@0: nsBindingManager::GetLoadingDocListener(nsIURI* aURL) michael@0: { michael@0: if (!mLoadingDocTable) michael@0: return nullptr; michael@0: michael@0: return mLoadingDocTable->GetWeak(aURL); michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::RemoveLoadingDocListener(nsIURI* aURL) michael@0: { michael@0: if (mLoadingDocTable) { michael@0: mLoadingDocTable->Remove(aURL); michael@0: } michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: MarkForDeath(nsRefPtrHashKey *aKey, void* aClosure) michael@0: { michael@0: nsXBLBinding *binding = aKey->GetKey()->GetXBLBinding(); michael@0: michael@0: if (binding->MarkedForDeath()) michael@0: return PL_DHASH_NEXT; // Already marked for death. michael@0: michael@0: nsAutoCString path; michael@0: binding->PrototypeBinding()->DocURI()->GetPath(path); michael@0: michael@0: if (!strncmp(path.get(), "/skin", 5)) michael@0: binding->MarkForDeath(); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::FlushSkinBindings() michael@0: { michael@0: if (mBoundContentSet) { michael@0: mBoundContentSet->EnumerateEntries(MarkForDeath, nullptr); michael@0: } michael@0: } michael@0: michael@0: // Used below to protect from recurring in QI calls through XPConnect. michael@0: struct AntiRecursionData { michael@0: nsIContent* element; michael@0: REFNSIID iid; michael@0: AntiRecursionData* next; michael@0: michael@0: AntiRecursionData(nsIContent* aElement, michael@0: REFNSIID aIID, michael@0: AntiRecursionData* aNext) michael@0: : element(aElement), iid(aIID), next(aNext) {} michael@0: }; michael@0: michael@0: nsresult michael@0: nsBindingManager::GetBindingImplementation(nsIContent* aContent, REFNSIID aIID, michael@0: void** aResult) michael@0: { michael@0: *aResult = nullptr; michael@0: nsXBLBinding *binding = aContent ? aContent->GetXBLBinding() : nullptr; michael@0: if (binding) { michael@0: // The binding should not be asked for nsISupports michael@0: NS_ASSERTION(!aIID.Equals(NS_GET_IID(nsISupports)), "Asking a binding for nsISupports"); michael@0: if (binding->ImplementsInterface(aIID)) { michael@0: nsCOMPtr wrappedJS = GetWrappedJS(aContent); michael@0: michael@0: if (wrappedJS) { michael@0: // Protect from recurring in QI calls through XPConnect. michael@0: // This can happen when a second binding is being resolved. michael@0: // At that point a wrappedJS exists, but it doesn't yet know about michael@0: // the iid we are asking for. So, without this protection, michael@0: // AggregatedQueryInterface would end up recurring back into itself michael@0: // through this code. michael@0: // michael@0: // With this protection, when we detect the recursion we return michael@0: // NS_NOINTERFACE in the inner call. The outer call will then fall michael@0: // through (see below) and build a new chained wrappedJS for the iid. michael@0: // michael@0: // We're careful to not assume that only one direct nesting can occur michael@0: // because there is a call into JS in the middle and we can't assume michael@0: // that this code won't be reached by some more complex nesting path. michael@0: // michael@0: // NOTE: We *assume* this is single threaded, so we can use a michael@0: // static linked list to do the check. michael@0: michael@0: static AntiRecursionData* list = nullptr; michael@0: michael@0: for (AntiRecursionData* p = list; p; p = p->next) { michael@0: if (p->element == aContent && p->iid.Equals(aIID)) { michael@0: *aResult = nullptr; michael@0: return NS_NOINTERFACE; michael@0: } michael@0: } michael@0: michael@0: AntiRecursionData item(aContent, aIID, list); michael@0: list = &item; michael@0: michael@0: nsresult rv = wrappedJS->AggregatedQueryInterface(aIID, aResult); michael@0: michael@0: list = item.next; michael@0: michael@0: if (*aResult) michael@0: return rv; michael@0: michael@0: // No result was found, so this must be another XBL interface. michael@0: // Fall through to create a new wrapper. michael@0: } michael@0: michael@0: // We have never made a wrapper for this implementation. michael@0: // Create an XPC wrapper for the script object and hand it back. michael@0: michael@0: nsIDocument* doc = aContent->OwnerDoc(); michael@0: michael@0: nsCOMPtr global = michael@0: do_QueryInterface(doc->GetWindow()); michael@0: if (!global) michael@0: return NS_NOINTERFACE; michael@0: michael@0: nsIScriptContext *context = global->GetContext(); michael@0: if (!context) michael@0: return NS_NOINTERFACE; michael@0: michael@0: AutoPushJSContext cx(context->GetNativeContext()); michael@0: if (!cx) michael@0: return NS_NOINTERFACE; michael@0: michael@0: nsIXPConnect *xpConnect = nsContentUtils::XPConnect(); michael@0: michael@0: JS::Rooted jsobj(cx, aContent->GetWrapper()); michael@0: NS_ENSURE_TRUE(jsobj, NS_NOINTERFACE); michael@0: michael@0: // If we're using an XBL scope, we need to use the Xray view to the bound michael@0: // content in order to view the full array of methods defined in the michael@0: // binding, some of which may not be exposed on the prototype of michael@0: // untrusted content. michael@0: // michael@0: // If there's no separate XBL scope, or if the reflector itself lives in michael@0: // the XBL scope, we'll end up with the global of the reflector, and this michael@0: // will all be a no-op. michael@0: JS::Rooted xblScope(cx, xpc::GetXBLScopeOrGlobal(cx, jsobj)); michael@0: NS_ENSURE_TRUE(xblScope, NS_ERROR_UNEXPECTED); michael@0: JSAutoCompartment ac(cx, xblScope); michael@0: bool ok = JS_WrapObject(cx, &jsobj); michael@0: NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); michael@0: MOZ_ASSERT_IF(js::IsWrapper(jsobj), xpc::IsXrayWrapper(jsobj)); michael@0: michael@0: nsresult rv = xpConnect->WrapJSAggregatedToNative(aContent, cx, michael@0: jsobj, aIID, aResult); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // We successfully created a wrapper. We will own this wrapper for as long as the binding remains michael@0: // alive. At the time the binding is cleared out of the bindingManager, we will remove the wrapper michael@0: // from the bindingManager as well. michael@0: nsISupports* supp = static_cast(*aResult); michael@0: wrappedJS = do_QueryInterface(supp); michael@0: SetWrappedJS(aContent, wrappedJS); michael@0: michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: *aResult = nullptr; michael@0: return NS_NOINTERFACE; michael@0: } michael@0: michael@0: nsresult michael@0: nsBindingManager::WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc, michael@0: ElementDependentRuleProcessorData* aData, michael@0: bool* aCutOffInheritance) michael@0: { michael@0: *aCutOffInheritance = false; michael@0: michael@0: NS_ASSERTION(aData->mElement, "How did that happen?"); michael@0: michael@0: // Walk the binding scope chain, starting with the binding attached to our michael@0: // content, up till we run out of scopes or we get cut off. michael@0: nsIContent *content = aData->mElement; michael@0: michael@0: do { michael@0: nsXBLBinding *binding = content->GetXBLBinding(); michael@0: if (binding) { michael@0: aData->mTreeMatchContext.mScopedRoot = content; michael@0: binding->WalkRules(aFunc, aData); michael@0: // If we're not looking at our original content, allow the binding to cut michael@0: // off style inheritance michael@0: if (content != aData->mElement) { michael@0: if (!binding->InheritsStyle()) { michael@0: // Go no further; we're not inheriting style from anything above here michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (content->IsRootOfNativeAnonymousSubtree()) { michael@0: break; // Deliberately cut off style inheritance here. michael@0: } michael@0: michael@0: content = content->GetBindingParent(); michael@0: } while (content); michael@0: michael@0: // If "content" is non-null that means we cut off inheritance at some point michael@0: // in the loop. michael@0: *aCutOffInheritance = (content != nullptr); michael@0: michael@0: // Null out the scoped root that we set repeatedly michael@0: aData->mTreeMatchContext.mScopedRoot = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: typedef nsTHashtable > RuleProcessorSet; michael@0: michael@0: static PLDHashOperator michael@0: EnumRuleProcessors(nsRefPtrHashKey *aKey, void* aClosure) michael@0: { michael@0: nsIContent *boundContent = aKey->GetKey(); michael@0: nsAutoPtr *set = static_cast*>(aClosure); michael@0: for (nsXBLBinding *binding = boundContent->GetXBLBinding(); binding; michael@0: binding = binding->GetBaseBinding()) { michael@0: nsIStyleRuleProcessor *ruleProc = michael@0: binding->PrototypeBinding()->GetRuleProcessor(); michael@0: if (ruleProc) { michael@0: if (!(*set)) { michael@0: *set = new RuleProcessorSet; michael@0: } michael@0: (*set)->PutEntry(ruleProc); michael@0: } michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: struct WalkAllRulesData { michael@0: nsIStyleRuleProcessor::EnumFunc mFunc; michael@0: ElementDependentRuleProcessorData* mData; michael@0: }; michael@0: michael@0: static PLDHashOperator michael@0: EnumWalkAllRules(nsPtrHashKey *aKey, void* aClosure) michael@0: { michael@0: nsIStyleRuleProcessor *ruleProcessor = aKey->GetKey(); michael@0: michael@0: WalkAllRulesData *data = static_cast(aClosure); michael@0: michael@0: (*(data->mFunc))(ruleProcessor, data->mData); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::WalkAllRules(nsIStyleRuleProcessor::EnumFunc aFunc, michael@0: ElementDependentRuleProcessorData* aData) michael@0: { michael@0: if (!mBoundContentSet) { michael@0: return; michael@0: } michael@0: michael@0: nsAutoPtr set; michael@0: mBoundContentSet->EnumerateEntries(EnumRuleProcessors, &set); michael@0: if (!set) michael@0: return; michael@0: michael@0: WalkAllRulesData data = { aFunc, aData }; michael@0: set->EnumerateEntries(EnumWalkAllRules, &data); michael@0: } michael@0: michael@0: struct MediumFeaturesChangedData { michael@0: nsPresContext *mPresContext; michael@0: bool *mRulesChanged; michael@0: }; michael@0: michael@0: static PLDHashOperator michael@0: EnumMediumFeaturesChanged(nsPtrHashKey *aKey, void* aClosure) michael@0: { michael@0: nsIStyleRuleProcessor *ruleProcessor = aKey->GetKey(); michael@0: michael@0: MediumFeaturesChangedData *data = michael@0: static_cast(aClosure); michael@0: michael@0: bool thisChanged = ruleProcessor->MediumFeaturesChanged(data->mPresContext); michael@0: *data->mRulesChanged = *data->mRulesChanged || thisChanged; michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsresult michael@0: nsBindingManager::MediumFeaturesChanged(nsPresContext* aPresContext, michael@0: bool* aRulesChanged) michael@0: { michael@0: *aRulesChanged = false; michael@0: if (!mBoundContentSet) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoPtr set; michael@0: mBoundContentSet->EnumerateEntries(EnumRuleProcessors, &set); michael@0: if (!set) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: MediumFeaturesChangedData data = { aPresContext, aRulesChanged }; michael@0: set->EnumerateEntries(EnumMediumFeaturesChanged, &data); michael@0: return NS_OK; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: EnumAppendAllSheets(nsRefPtrHashKey *aKey, void* aClosure) michael@0: { michael@0: nsIContent *boundContent = aKey->GetKey(); michael@0: nsTArray* array = michael@0: static_cast*>(aClosure); michael@0: for (nsXBLBinding *binding = boundContent->GetXBLBinding(); binding; michael@0: binding = binding->GetBaseBinding()) { michael@0: nsXBLPrototypeResources::sheet_array_type* sheets = michael@0: binding->PrototypeBinding()->GetStyleSheets(); michael@0: if (sheets) { michael@0: // Copy from nsTArray > to michael@0: // nsTArray. michael@0: array->AppendElements(*sheets); michael@0: } michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::AppendAllSheets(nsTArray& aArray) michael@0: { michael@0: if (mBoundContentSet) { michael@0: mBoundContentSet->EnumerateEntries(EnumAppendAllSheets, &aArray); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: InsertAppendedContent(XBLChildrenElement* aPoint, michael@0: nsIContent* aFirstNewContent) michael@0: { michael@0: uint32_t insertionIndex; michael@0: if (nsIContent* prevSibling = aFirstNewContent->GetPreviousSibling()) { michael@0: // If we have a previous sibling, then it must already be in aPoint. Find michael@0: // it and insert after it. michael@0: insertionIndex = aPoint->IndexOfInsertedChild(prevSibling); michael@0: MOZ_ASSERT(insertionIndex != aPoint->NoIndex); michael@0: michael@0: // Our insertion index is one after our previous sibling's index. michael@0: ++insertionIndex; michael@0: } else { michael@0: // Otherwise, we append. michael@0: // TODO This is wrong for nested insertion points. In that case, we need to michael@0: // keep track of the right index to insert into. michael@0: insertionIndex = aPoint->mInsertedChildren.Length(); michael@0: } michael@0: michael@0: // Do the inserting. michael@0: for (nsIContent* currentChild = aFirstNewContent; michael@0: currentChild; michael@0: currentChild = currentChild->GetNextSibling()) { michael@0: aPoint->InsertInsertedChildAt(currentChild, insertionIndex++); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::ContentAppended(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aFirstNewContent, michael@0: int32_t aNewIndexInContainer) michael@0: { michael@0: if (aNewIndexInContainer == -1) { michael@0: return; michael@0: } michael@0: michael@0: // Try to find insertion points for all the new kids. michael@0: XBLChildrenElement* point = nullptr; michael@0: nsIContent* parent = aContainer; michael@0: bool first = true; michael@0: do { michael@0: nsXBLBinding* binding = GetBindingWithContent(parent); michael@0: if (!binding) { michael@0: break; michael@0: } michael@0: michael@0: if (binding->HasFilteredInsertionPoints()) { michael@0: // There are filtered insertion points involved, handle each child michael@0: // separately. michael@0: // We could optimize this in the case when we've nested a few levels michael@0: // deep already, without hitting bindings that have filtered insertion michael@0: // points. michael@0: int32_t currentIndex = aNewIndexInContainer; michael@0: for (nsIContent* currentChild = aFirstNewContent; currentChild; michael@0: currentChild = currentChild->GetNextSibling()) { michael@0: HandleChildInsertion(aContainer, currentChild, michael@0: currentIndex++, true); michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: point = binding->GetDefaultInsertionPoint(); michael@0: if (!point) { michael@0: break; michael@0: } michael@0: michael@0: // Even though we're in ContentAppended, nested insertion points force us michael@0: // to deal with this append as an insertion except in the outermost michael@0: // binding. michael@0: if (first) { michael@0: first = false; michael@0: for (nsIContent* child = aFirstNewContent; child; michael@0: child = child->GetNextSibling()) { michael@0: point->AppendInsertedChild(child); michael@0: } michael@0: } else { michael@0: InsertAppendedContent(point, aFirstNewContent); michael@0: } michael@0: michael@0: nsIContent* newParent = point->GetParent(); michael@0: if (newParent == parent) { michael@0: break; michael@0: } michael@0: parent = newParent; michael@0: } while (parent); michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::ContentInserted(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer) michael@0: { michael@0: if (aIndexInContainer == -1) { michael@0: return; michael@0: } michael@0: michael@0: HandleChildInsertion(aContainer, aChild, aIndexInContainer, false); michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::ContentRemoved(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer, michael@0: nsIContent* aPreviousSibling) michael@0: { michael@0: aChild->SetXBLInsertionParent(nullptr); michael@0: michael@0: XBLChildrenElement* point = nullptr; michael@0: nsIContent* parent = aContainer; michael@0: do { michael@0: nsXBLBinding* binding = GetBindingWithContent(parent); michael@0: if (!binding) { michael@0: // If aChild is XBL content, it might have elements michael@0: // somewhere under it. We need to inform those elements that they're no michael@0: // longer in the tree so they can tell their distributed children that michael@0: // they're no longer distributed under them. michael@0: // XXX This is wrong. We need to do far more work to update the parent michael@0: // binding's list of insertion points and to get the new insertion parent michael@0: // for the newly-distributed children correct. michael@0: if (aChild->GetBindingParent()) { michael@0: ClearInsertionPointsRecursively(aChild); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: point = binding->FindInsertionPointFor(aChild); michael@0: if (!point) { michael@0: break; michael@0: } michael@0: michael@0: point->RemoveInsertedChild(aChild); michael@0: michael@0: nsIContent* newParent = point->GetParent(); michael@0: if (newParent == parent) { michael@0: break; michael@0: } michael@0: parent = newParent; michael@0: } while (parent); michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::ClearInsertionPointsRecursively(nsIContent* aContent) michael@0: { michael@0: if (aContent->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) { michael@0: static_cast(aContent)->ClearInsertedChildren(); michael@0: } michael@0: michael@0: for (nsIContent* child = aContent->GetFirstChild(); child; michael@0: child = child->GetNextSibling()) { michael@0: ClearInsertionPointsRecursively(child); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::DropDocumentReference() michael@0: { michael@0: mDestroyed = true; michael@0: michael@0: // Make sure to not run any more XBL constructors michael@0: mProcessingAttachedStack = true; michael@0: if (mProcessAttachedQueueEvent) { michael@0: mProcessAttachedQueueEvent->Revoke(); michael@0: } michael@0: michael@0: if (mBoundContentSet) { michael@0: mBoundContentSet->Clear(); michael@0: } michael@0: michael@0: mDocument = nullptr; michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::Traverse(nsIContent *aContent, michael@0: nsCycleCollectionTraversalCallback &cb) michael@0: { michael@0: if (!aContent->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) || michael@0: !aContent->IsElement()) { michael@0: // Don't traverse if content is not in this binding manager. michael@0: // We also don't traverse non-elements because there should not michael@0: // be bindings (checking the flag alone is not sufficient because michael@0: // the flag is also set on children of insertion points that may be michael@0: // non-elements). michael@0: return; michael@0: } michael@0: michael@0: if (mBoundContentSet && mBoundContentSet->Contains(aContent)) { michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "[via binding manager] mBoundContentSet entry"); michael@0: cb.NoteXPCOMChild(aContent); michael@0: } michael@0: michael@0: nsIXPConnectWrappedJS *value = GetWrappedJS(aContent); michael@0: if (value) { michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "[via binding manager] mWrapperTable key"); michael@0: cb.NoteXPCOMChild(aContent); michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "[via binding manager] mWrapperTable value"); michael@0: cb.NoteXPCOMChild(value); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::BeginOutermostUpdate() michael@0: { michael@0: mAttachedStackSizeOnOutermost = mAttachedStack.Length(); michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::EndOutermostUpdate() michael@0: { michael@0: if (!mProcessingAttachedStack) { michael@0: ProcessAttachedQueue(mAttachedStackSizeOnOutermost); michael@0: mAttachedStackSizeOnOutermost = 0; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBindingManager::HandleChildInsertion(nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: uint32_t aIndexInContainer, michael@0: bool aAppend) michael@0: { michael@0: NS_PRECONDITION(aChild, "Must have child"); michael@0: NS_PRECONDITION(!aContainer || michael@0: uint32_t(aContainer->IndexOf(aChild)) == aIndexInContainer, michael@0: "Child not at the right index?"); michael@0: michael@0: XBLChildrenElement* point = nullptr; michael@0: nsIContent* parent = aContainer; michael@0: while (parent) { michael@0: nsXBLBinding* binding = GetBindingWithContent(parent); michael@0: if (!binding) { michael@0: break; michael@0: } michael@0: michael@0: point = binding->FindInsertionPointFor(aChild); michael@0: if (!point) { michael@0: break; michael@0: } michael@0: michael@0: // Insert the child into the proper insertion point. michael@0: // TODO If there were multiple insertion points, this approximation can be michael@0: // wrong. We need to re-run the distribution algorithm. In the meantime, michael@0: // this should work well enough. michael@0: uint32_t index = aAppend ? point->mInsertedChildren.Length() : 0; michael@0: for (nsIContent* currentSibling = aChild->GetPreviousSibling(); michael@0: currentSibling; michael@0: currentSibling = currentSibling->GetPreviousSibling()) { michael@0: // If we find one of our previous siblings in the insertion point, the michael@0: // index following it is the correct insertion point. Otherwise, we guess michael@0: // based on whether we're appending or inserting. michael@0: uint32_t pointIndex = point->IndexOfInsertedChild(currentSibling); michael@0: if (pointIndex != point->NoIndex) { michael@0: index = pointIndex + 1; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: point->InsertInsertedChildAt(aChild, index); michael@0: michael@0: nsIContent* newParent = point->GetParent(); michael@0: if (newParent == parent) { michael@0: break; michael@0: } michael@0: michael@0: parent = newParent; michael@0: } michael@0: } michael@0: michael@0: michael@0: nsIContent* michael@0: nsBindingManager::FindNestedInsertionPoint(nsIContent* aContainer, michael@0: nsIContent* aChild) michael@0: { michael@0: NS_PRECONDITION(aChild->GetParent() == aContainer, michael@0: "Wrong container"); michael@0: michael@0: nsIContent* parent = aContainer; michael@0: if (aContainer->IsActiveChildrenElement()) { michael@0: if (static_cast(aContainer)-> michael@0: HasInsertedChildren()) { michael@0: return nullptr; michael@0: } michael@0: parent = aContainer->GetParent(); michael@0: } michael@0: michael@0: while (parent) { michael@0: nsXBLBinding* binding = GetBindingWithContent(parent); michael@0: if (!binding) { michael@0: break; michael@0: } michael@0: michael@0: XBLChildrenElement* point = binding->FindInsertionPointFor(aChild); michael@0: if (!point) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIContent* newParent = point->GetParent(); michael@0: if (newParent == parent) { michael@0: break; michael@0: } michael@0: parent = newParent; michael@0: } michael@0: michael@0: return parent; michael@0: } michael@0: michael@0: nsIContent* michael@0: nsBindingManager::FindNestedSingleInsertionPoint(nsIContent* aContainer, michael@0: bool* aMulti) michael@0: { michael@0: *aMulti = false; michael@0: michael@0: nsIContent* parent = aContainer; michael@0: if (aContainer->IsActiveChildrenElement()) { michael@0: if (static_cast(aContainer)-> michael@0: HasInsertedChildren()) { michael@0: return nullptr; michael@0: } michael@0: parent = aContainer->GetParent(); michael@0: } michael@0: michael@0: while(parent) { michael@0: nsXBLBinding* binding = GetBindingWithContent(parent); michael@0: if (!binding) { michael@0: break; michael@0: } michael@0: michael@0: if (binding->HasFilteredInsertionPoints()) { michael@0: *aMulti = true; michael@0: return nullptr; michael@0: } michael@0: michael@0: XBLChildrenElement* point = binding->GetDefaultInsertionPoint(); michael@0: if (!point) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIContent* newParent = point->GetParent(); michael@0: if (newParent == parent) { michael@0: break; michael@0: } michael@0: parent = newParent; michael@0: } michael@0: michael@0: return parent; michael@0: }