зеркало из https://github.com/mozilla/gecko-dev.git
895 строки
28 KiB
C++
895 строки
28 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "nsBindingManager.h"
|
|
|
|
#include "nsAutoPtr.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsXBLService.h"
|
|
#include "nsIInputStream.h"
|
|
#include "nsIURI.h"
|
|
#include "nsIURL.h"
|
|
#include "nsIChannel.h"
|
|
#include "nsString.h"
|
|
#include "plstr.h"
|
|
#include "nsIContent.h"
|
|
#include "nsIContentInlines.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsIXMLContentSink.h"
|
|
#include "nsContentCID.h"
|
|
#include "mozilla/dom/XMLDocument.h"
|
|
#include "nsIStreamListener.h"
|
|
#include "ChildIterator.h"
|
|
#include "nsITimer.h"
|
|
|
|
#include "nsXBLBinding.h"
|
|
#include "nsXBLPrototypeBinding.h"
|
|
#include "nsXBLDocumentInfo.h"
|
|
#include "mozilla/dom/XBLChildrenElement.h"
|
|
#ifdef MOZ_XUL
|
|
# include "nsXULPrototypeCache.h"
|
|
#endif
|
|
|
|
#include "nsIWeakReference.h"
|
|
|
|
#include "nsWrapperCacheInlines.h"
|
|
#include "nsIXPConnect.h"
|
|
#include "nsDOMCID.h"
|
|
#include "nsIScriptGlobalObject.h"
|
|
#include "nsTHashtable.h"
|
|
|
|
#include "nsIScriptContext.h"
|
|
#include "xpcpublic.h"
|
|
#include "js/Wrapper.h"
|
|
|
|
#include "nsThreadUtils.h"
|
|
#include "mozilla/dom/NodeListBinding.h"
|
|
#include "mozilla/dom/ScriptSettings.h"
|
|
#include "mozilla/PresShell.h"
|
|
#include "mozilla/PresShellInlines.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(Document* 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, Document* aOldDocument,
|
|
DestructorHandling aDestructorHandling) {
|
|
MOZ_ASSERT(aOldDocument != nullptr, "no old document");
|
|
|
|
RefPtr<nsXBLBinding> binding = aContent->GetXBLBinding();
|
|
if (binding) {
|
|
// The binding manager may have been destroyed before a runnable
|
|
// has had a chance to reach this point. If so, we bail out on calling
|
|
// BindingDetached (which may invoke a XBL destructor) and
|
|
// ChangeDocument, but we still want to clear out the binding
|
|
// and insertion parent that may hold references.
|
|
if (!mDestroyed && aDestructorHandling == eRunDtor) {
|
|
binding->PrototypeBinding()->BindingDetached(binding->GetBoundElement());
|
|
binding->ChangeDocument(aOldDocument, nullptr);
|
|
}
|
|
|
|
aContent->AsElement()->SetXBLBinding(nullptr, this);
|
|
}
|
|
|
|
// Clear out insertion point and content lists.
|
|
aContent->SetXBLInsertionPoint(nullptr);
|
|
}
|
|
|
|
nsINodeList* nsBindingManager::GetAnonymousNodesFor(nsIContent* aContent) {
|
|
nsXBLBinding* binding = GetBindingWithContent(aContent);
|
|
return binding ? binding->GetAnonymousNodeList() : nullptr;
|
|
}
|
|
|
|
nsresult nsBindingManager::ClearBinding(Element* aElement) {
|
|
// Hold a ref to the binding so it won't die when we remove it from our table
|
|
RefPtr<nsXBLBinding> binding = aElement ? aElement->GetXBLBinding() : nullptr;
|
|
|
|
if (!binding) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// For now we can only handle removing a binding if it's the only one
|
|
NS_ENSURE_FALSE(binding->GetBaseBinding(), NS_ERROR_FAILURE);
|
|
|
|
// Hold strong ref in case removing the binding tries to close the
|
|
// window or something.
|
|
// XXXbz should that be ownerdoc? Wouldn't we need a ref to the
|
|
// currentdoc too? What's the one that should be passed to
|
|
// ChangeDocument?
|
|
nsCOMPtr<Document> doc = aElement->OwnerDoc();
|
|
|
|
// Destroy the frames here before the UnbindFromTree happens.
|
|
if (PresShell* presShell = doc->GetPresShell()) {
|
|
presShell->DestroyFramesForAndRestyle(aElement);
|
|
}
|
|
|
|
// Finally remove the binding...
|
|
// XXXbz this doesn't remove the implementation! Should fix! Until
|
|
// then we need the explicit UnhookEventHandlers here.
|
|
binding->UnhookEventHandlers();
|
|
binding->ChangeDocument(doc, nullptr);
|
|
aElement->SetXBLBinding(nullptr, this);
|
|
binding->MarkForDeath();
|
|
|
|
// ...and recreate its frames. We need to do this since the frames may have
|
|
// been removed and style may have changed due to the removal of the
|
|
// anonymous children.
|
|
// XXXbz this should be using the current doc (if any), not the owner doc.
|
|
// get the shell again, just in case it changed
|
|
PresShell* presShell = doc->GetPresShell();
|
|
NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
|
|
presShell->PostRecreateFramesFor(aElement);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsBindingManager::LoadBindingDocument(Document* aBoundDoc,
|
|
nsIURI* aURL,
|
|
nsIPrincipal* aOriginPrincipal) {
|
|
MOZ_ASSERT(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 != nsBindingList::NoIndex) {
|
|
mAttachedStack[index] = nullptr;
|
|
}
|
|
}
|
|
|
|
nsresult nsBindingManager::AddToAttachedQueue(nsXBLBinding* aBinding) {
|
|
mAttachedStack.AppendElement(aBinding);
|
|
|
|
// If we're in the middle of processing our queue already, don't
|
|
// bother posting the event.
|
|
if (!mProcessingAttachedStack && !mProcessAttachedQueueEvent) {
|
|
PostProcessAttachedQueueEvent();
|
|
}
|
|
|
|
// Make sure that flushes will flush out the new items as needed.
|
|
if (PresShell* presShell = mDocument->GetPresShell()) {
|
|
presShell->SetNeedStyleFlush();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsBindingManager::PostProcessAttachedQueueEvent() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
if (!mDocument) {
|
|
return;
|
|
}
|
|
mProcessAttachedQueueEvent =
|
|
NewRunnableMethod("nsBindingManager::DoProcessAttachedQueue", this,
|
|
&nsBindingManager::DoProcessAttachedQueue);
|
|
nsresult rv = mDocument->EventTargetFor(TaskCategory::Other)
|
|
->Dispatch(do_AddRef(mProcessAttachedQueueEvent));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mDocument->BlockOnload();
|
|
}
|
|
}
|
|
|
|
// static
|
|
void nsBindingManager::PostPAQEventCallback(nsITimer* aTimer, void* aClosure) {
|
|
RefPtr<nsBindingManager> mgr = already_AddRefed<nsBindingManager>(
|
|
static_cast<nsBindingManager*>(aClosure));
|
|
mgr->PostProcessAttachedQueueEvent();
|
|
NS_RELEASE(aTimer);
|
|
}
|
|
|
|
void nsBindingManager::DoProcessAttachedQueue() {
|
|
if (!mProcessingAttachedStack) {
|
|
ProcessAttachedQueue();
|
|
|
|
NS_ASSERTION(mAttachedStack.Length() == 0,
|
|
"Shouldn't have pending bindings!");
|
|
|
|
mProcessAttachedQueueEvent = nullptr;
|
|
} else {
|
|
// Someone's doing event processing from inside a constructor.
|
|
// They're evil, but we'll fight back! Just poll on them being
|
|
// done and repost the attached queue event.
|
|
//
|
|
// But don't poll in a tight loop -- otherwise we keep the Gecko
|
|
// event loop non-empty and trigger bug 1021240 on OS X.
|
|
nsresult rv = NS_ERROR_FAILURE;
|
|
nsCOMPtr<nsITimer> timer;
|
|
rv = NS_NewTimerWithFuncCallback(
|
|
getter_AddRefs(timer), PostPAQEventCallback, this, 100,
|
|
nsITimer::TYPE_ONE_SHOT, "nsBindingManager::DoProcessAttachedQueue");
|
|
if (NS_SUCCEEDED(rv)) {
|
|
NS_ADDREF_THIS();
|
|
// We drop our reference to the timer here, since the timer callback is
|
|
// responsible for releasing the object.
|
|
Unused << timer.forget().take();
|
|
}
|
|
}
|
|
|
|
// No matter what, unblock onload for the event that's fired.
|
|
if (mDocument) {
|
|
// Hold a strong reference while calling UnblockOnload since that might
|
|
// run script.
|
|
nsCOMPtr<Document> 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.RemoveLastElement();
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t i, count = bindings.Length();
|
|
for (i = 0; i < count; ++i) {
|
|
bindings[i]->ExecuteDetachedHandler();
|
|
}
|
|
}
|
|
|
|
nsresult nsBindingManager::PutXBLDocumentInfo(
|
|
nsXBLDocumentInfo* aDocumentInfo) {
|
|
MOZ_ASSERT(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) {
|
|
MOZ_ASSERT(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);
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
JSAutoRealm ar(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;
|
|
}
|
|
|
|
static void InsertAppendedContent(XBLChildrenElement* aPoint,
|
|
nsIContent* aFirstNewContent) {
|
|
int32_t insertionIndex;
|
|
if (nsIContent* prevSibling = aFirstNewContent->GetPreviousSibling()) {
|
|
// If we have a previous sibling, then it must already be in aPoint. Find
|
|
// it and insert after it.
|
|
insertionIndex = aPoint->IndexOfInsertedChild(prevSibling);
|
|
MOZ_ASSERT(insertionIndex != -1);
|
|
|
|
// Our insertion index is one after our previous sibling's index.
|
|
++insertionIndex;
|
|
} else {
|
|
// Otherwise, we append.
|
|
// TODO This is wrong for nested insertion points. In that case, we need to
|
|
// keep track of the right index to insert into.
|
|
insertionIndex = aPoint->InsertedChildrenLength();
|
|
}
|
|
|
|
// Do the inserting.
|
|
for (nsIContent* currentChild = aFirstNewContent; currentChild;
|
|
currentChild = currentChild->GetNextSibling()) {
|
|
aPoint->InsertInsertedChildAt(currentChild, insertionIndex++);
|
|
}
|
|
}
|
|
|
|
void nsBindingManager::ContentAppended(nsIContent* aFirstNewContent) {
|
|
// Try to find insertion points for all the new kids.
|
|
XBLChildrenElement* point = nullptr;
|
|
nsIContent* container = aFirstNewContent->GetParent();
|
|
nsIContent* parent = container;
|
|
|
|
// Handle appending of default content.
|
|
if (parent && parent->IsActiveChildrenElement()) {
|
|
XBLChildrenElement* childrenEl = static_cast<XBLChildrenElement*>(parent);
|
|
if (childrenEl->HasInsertedChildren()) {
|
|
// Appending default content that isn't being used. Ignore.
|
|
return;
|
|
}
|
|
|
|
childrenEl->MaybeSetupDefaultContent();
|
|
parent = childrenEl->GetParent();
|
|
}
|
|
|
|
bool first = true;
|
|
do {
|
|
nsXBLBinding* binding = GetBindingWithContent(parent);
|
|
if (!binding) {
|
|
break;
|
|
}
|
|
|
|
if (binding->HasFilteredInsertionPoints()) {
|
|
// There are filtered insertion points involved, handle each child
|
|
// separately.
|
|
// We could optimize this in the case when we've nested a few levels
|
|
// deep already, without hitting bindings that have filtered insertion
|
|
// points.
|
|
for (nsIContent* currentChild = aFirstNewContent; currentChild;
|
|
currentChild = currentChild->GetNextSibling()) {
|
|
HandleChildInsertion(container, currentChild, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
point = binding->GetDefaultInsertionPoint();
|
|
if (!point) {
|
|
break;
|
|
}
|
|
|
|
// Even though we're in ContentAppended, nested insertion points force us
|
|
// to deal with this append as an insertion except in the outermost
|
|
// binding.
|
|
if (first) {
|
|
first = false;
|
|
for (nsIContent* child = aFirstNewContent; child;
|
|
child = child->GetNextSibling()) {
|
|
point->AppendInsertedChild(child, true);
|
|
}
|
|
} else {
|
|
InsertAppendedContent(point, aFirstNewContent);
|
|
}
|
|
|
|
nsIContent* newParent = point->GetParent();
|
|
if (newParent == parent) {
|
|
break;
|
|
}
|
|
parent = newParent;
|
|
} while (parent);
|
|
}
|
|
|
|
void nsBindingManager::ContentInserted(nsIContent* aChild) {
|
|
HandleChildInsertion(aChild->GetParent(), aChild, false);
|
|
}
|
|
|
|
void nsBindingManager::ContentRemoved(nsIContent* aChild,
|
|
nsIContent* aPreviousSibling) {
|
|
aChild->SetXBLInsertionPoint(nullptr);
|
|
|
|
XBLChildrenElement* point = nullptr;
|
|
nsIContent* parent = aChild->GetParent();
|
|
|
|
// Handle appending of default content.
|
|
if (parent && parent->IsActiveChildrenElement()) {
|
|
XBLChildrenElement* childrenEl = static_cast<XBLChildrenElement*>(parent);
|
|
if (childrenEl->HasInsertedChildren()) {
|
|
// Removing default content that isn't being used. Ignore.
|
|
return;
|
|
}
|
|
|
|
parent = childrenEl->GetParent();
|
|
}
|
|
|
|
do {
|
|
nsXBLBinding* binding = GetBindingWithContent(parent);
|
|
if (!binding) {
|
|
// If aChild is XBL content, it might have <xbl:children> elements
|
|
// somewhere under it. We need to inform those elements that they're no
|
|
// longer in the tree so they can tell their distributed children that
|
|
// they're no longer distributed under them.
|
|
// XXX This is wrong. We need to do far more work to update the parent
|
|
// binding's list of insertion points and to get the new insertion parent
|
|
// for the newly-distributed children correct.
|
|
if (aChild->GetBindingParent()) {
|
|
ClearInsertionPointsRecursively(aChild);
|
|
}
|
|
return;
|
|
}
|
|
|
|
point = binding->FindInsertionPointFor(aChild);
|
|
if (!point) {
|
|
break;
|
|
}
|
|
|
|
point->RemoveInsertedChild(aChild);
|
|
|
|
nsIContent* newParent = point->GetParent();
|
|
if (newParent == parent) {
|
|
break;
|
|
}
|
|
parent = newParent;
|
|
} while (parent);
|
|
}
|
|
|
|
void nsBindingManager::ClearInsertionPointsRecursively(nsIContent* aContent) {
|
|
if (aContent->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
|
|
static_cast<XBLChildrenElement*>(aContent)->ClearInsertedChildren();
|
|
}
|
|
|
|
for (nsIContent* child = aContent->GetFirstChild(); child;
|
|
child = child->GetNextSibling()) {
|
|
ClearInsertionPointsRecursively(child);
|
|
}
|
|
}
|
|
|
|
void nsBindingManager::DropDocumentReference() {
|
|
mDestroyed = true;
|
|
|
|
// Make sure to not run any more XBL constructors
|
|
mProcessingAttachedStack = true;
|
|
if (mProcessAttachedQueueEvent) {
|
|
mProcessAttachedQueueEvent->Revoke();
|
|
}
|
|
|
|
if (mBoundContentSet) {
|
|
mBoundContentSet->Clear();
|
|
}
|
|
|
|
mDocument = nullptr;
|
|
}
|
|
|
|
void nsBindingManager::Traverse(nsIContent* aContent,
|
|
nsCycleCollectionTraversalCallback& cb) {
|
|
if (!aContent->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) ||
|
|
!aContent->IsElement()) {
|
|
// Don't traverse if content is not in this binding manager.
|
|
// We also don't traverse non-elements because there should not
|
|
// be bindings (checking the flag alone is not sufficient because
|
|
// the flag is also set on children of insertion points that may be
|
|
// non-elements).
|
|
return;
|
|
}
|
|
|
|
if (mBoundContentSet && mBoundContentSet->Contains(aContent)) {
|
|
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
|
|
cb, "[via binding manager] mBoundContentSet entry");
|
|
cb.NoteXPCOMChild(aContent);
|
|
}
|
|
|
|
nsIXPConnectWrappedJS* value = GetWrappedJS(aContent);
|
|
if (value) {
|
|
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
|
|
cb, "[via binding manager] mWrapperTable key");
|
|
cb.NoteXPCOMChild(aContent);
|
|
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
|
|
cb, "[via binding manager] mWrapperTable value");
|
|
cb.NoteXPCOMChild(value);
|
|
}
|
|
}
|
|
|
|
void nsBindingManager::HandleChildInsertion(nsIContent* aContainer,
|
|
nsIContent* aChild, bool aAppend) {
|
|
MOZ_ASSERT(aChild, "Must have child");
|
|
|
|
XBLChildrenElement* point = nullptr;
|
|
nsIContent* parent = aContainer;
|
|
|
|
// Handle insertion of default content.
|
|
if (parent && parent->IsActiveChildrenElement()) {
|
|
XBLChildrenElement* childrenEl = static_cast<XBLChildrenElement*>(parent);
|
|
if (childrenEl->HasInsertedChildren()) {
|
|
// Inserting default content that isn't being used. Ignore.
|
|
return;
|
|
}
|
|
|
|
childrenEl->MaybeSetupDefaultContent();
|
|
parent = childrenEl->GetParent();
|
|
}
|
|
|
|
while (parent) {
|
|
nsXBLBinding* binding = GetBindingWithContent(parent);
|
|
if (!binding) {
|
|
break;
|
|
}
|
|
|
|
point = binding->FindInsertionPointFor(aChild);
|
|
if (!point) {
|
|
break;
|
|
}
|
|
|
|
// Insert the child into the proper insertion point.
|
|
// TODO If there were multiple insertion points, this approximation can be
|
|
// wrong. We need to re-run the distribution algorithm. In the meantime,
|
|
// this should work well enough.
|
|
uint32_t index = aAppend ? point->InsertedChildrenLength() : 0;
|
|
for (nsIContent* currentSibling = aChild->GetPreviousSibling();
|
|
currentSibling;
|
|
currentSibling = currentSibling->GetPreviousSibling()) {
|
|
// If we find one of our previous siblings in the insertion point, the
|
|
// index following it is the correct insertion point. Otherwise, we guess
|
|
// based on whether we're appending or inserting.
|
|
int32_t pointIndex = point->IndexOfInsertedChild(currentSibling);
|
|
if (pointIndex != -1) {
|
|
index = pointIndex + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
point->InsertInsertedChildAt(aChild, index);
|
|
|
|
nsIContent* newParent = point->GetParent();
|
|
if (newParent == parent) {
|
|
break;
|
|
}
|
|
|
|
parent = newParent;
|
|
}
|
|
}
|
|
|
|
nsIContent* nsBindingManager::FindNestedSingleInsertionPoint(
|
|
nsIContent* aContainer, bool* aMulti) {
|
|
*aMulti = false;
|
|
|
|
nsIContent* parent = aContainer;
|
|
if (aContainer->IsActiveChildrenElement()) {
|
|
if (static_cast<XBLChildrenElement*>(aContainer)->HasInsertedChildren()) {
|
|
return nullptr;
|
|
}
|
|
parent = aContainer->GetParent();
|
|
}
|
|
|
|
while (parent) {
|
|
nsXBLBinding* binding = GetBindingWithContent(parent);
|
|
if (!binding) {
|
|
break;
|
|
}
|
|
|
|
if (binding->HasFilteredInsertionPoints()) {
|
|
*aMulti = true;
|
|
return nullptr;
|
|
}
|
|
|
|
XBLChildrenElement* point = binding->GetDefaultInsertionPoint();
|
|
if (!point) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsIContent* newParent = point->GetParent();
|
|
if (newParent == parent) {
|
|
break;
|
|
}
|
|
parent = newParent;
|
|
}
|
|
|
|
return parent;
|
|
}
|
|
|
|
size_t nsBindingManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
|
|
size_t n = aMallocSizeOf(this);
|
|
|
|
#define SHALLOW_SIZE_INCLUDING(field_) \
|
|
n += field_ ? field_->ShallowSizeOfIncludingThis(aMallocSizeOf) : 0;
|
|
SHALLOW_SIZE_INCLUDING(mBoundContentSet);
|
|
SHALLOW_SIZE_INCLUDING(mWrapperTable);
|
|
SHALLOW_SIZE_INCLUDING(mLoadingDocTable);
|
|
#undef SHALLOW_SIZE_INCLUDING
|
|
n += mAttachedStack.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
|
|
|
if (mDocumentTable) {
|
|
n += mDocumentTable->ShallowSizeOfIncludingThis(aMallocSizeOf);
|
|
#ifdef MOZ_XUL
|
|
nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
|
|
#endif
|
|
for (auto iter = mDocumentTable->Iter(); !iter.Done(); iter.Next()) {
|
|
nsXBLDocumentInfo* docInfo = iter.UserData();
|
|
#ifdef MOZ_XUL
|
|
nsXBLDocumentInfo* cachedInfo = cache->GetXBLDocumentInfo(iter.Key());
|
|
if (cachedInfo == docInfo) {
|
|
// If this binding has been cached, skip it since it can be
|
|
// reused by other documents.
|
|
continue;
|
|
}
|
|
#endif
|
|
n += docInfo->SizeOfIncludingThis(aMallocSizeOf);
|
|
}
|
|
}
|
|
|
|
return n;
|
|
}
|