/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Communicator client code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Brendan Eich (brendan@mozilla.org) * Scott MacGregor (mscott@netscape.com) * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsCOMPtr.h" #include "nsIAtom.h" #include "nsIXBLDocumentInfo.h" #include "nsIInputStream.h" #include "nsINameSpaceManager.h" #include "nsHashtable.h" #include "nsIURI.h" #include "nsIURL.h" #include "nsIDOMEventTarget.h" #include "nsIChannel.h" #include "nsXPIDLString.h" #include "nsReadableUtils.h" #include "nsIParser.h" #include "nsParserCIID.h" #include "nsNetUtil.h" #include "plstr.h" #include "nsIContent.h" #include "nsIDocument.h" #include "nsContentUtils.h" #ifdef MOZ_XUL #include "nsIXULDocument.h" #endif #include "nsIXMLContentSink.h" #include "nsContentCID.h" #include "nsXMLDocument.h" #include "nsIDOMElement.h" #include "nsIDOMText.h" #include "jsapi.h" #include "nsXBLService.h" #include "nsXBLInsertionPoint.h" #include "nsIXPConnect.h" #include "nsIScriptContext.h" #include "nsCRT.h" // Event listeners #include "nsIEventListenerManager.h" #include "nsIDOMMouseListener.h" #include "nsIDOMMouseMotionListener.h" #include "nsIDOMLoadListener.h" #include "nsIDOMFocusListener.h" #include "nsIDOMKeyListener.h" #include "nsIDOMFormListener.h" #include "nsIDOMXULListener.h" #include "nsIDOMDragListener.h" #include "nsIDOMContextMenuListener.h" #include "nsIDOMEventGroup.h" #include "nsAttrName.h" #include "nsGkAtoms.h" #include "nsIDOMAttr.h" #include "nsIDOMNamedNodeMap.h" #include "nsXBLPrototypeHandler.h" #include "nsXBLPrototypeBinding.h" #include "nsXBLBinding.h" #include "nsIPrincipal.h" #include "nsIScriptSecurityManager.h" #include "nsIEventListenerManager.h" #include "nsGUIEvent.h" #include "prprf.h" #include "nsNodeUtils.h" // Nasty hack. Maybe we could move some of the classinfo utility methods // (e.g. WrapNative and ThrowJSException) over to nsContentUtils? #include "nsDOMClassInfo.h" #include "nsJSUtils.h" // Helper classes /***********************************************************************/ // // The JS class for XBLBinding // JS_STATIC_DLL_CALLBACK(void) XBLFinalize(JSContext *cx, JSObject *obj) { nsIXBLDocumentInfo* docInfo = static_cast(::JS_GetPrivate(cx, obj)); NS_RELEASE(docInfo); nsXBLJSClass* c = static_cast(::JS_GetClass(cx, obj)); c->Drop(); } JS_STATIC_DLL_CALLBACK(JSBool) XBLResolve(JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp) { // Note: if we get here, that means that the implementation for some binding // was installed, which means that AllowScripts() tested true. Hence no need // to do checks like that here. // Default to not resolving things. NS_ASSERTION(*objp, "Must have starting object"); JSObject* origObj = *objp; *objp = NULL; if (!JSVAL_IS_STRING(id)) { return JS_TRUE; } nsDependentJSString fieldName(id); jsval slotVal; ::JS_GetReservedSlot(cx, obj, 0, &slotVal); NS_ASSERTION(!JSVAL_IS_VOID(slotVal), "How did that happen?"); nsXBLPrototypeBinding* protoBinding = static_cast(JSVAL_TO_PRIVATE(slotVal)); NS_ASSERTION(protoBinding, "Must have prototype binding!"); nsXBLProtoImplField* field = protoBinding->FindField(fieldName); if (!field) { return JS_TRUE; } // We have this field. Time to install it. Get our node. JSClass* nodeClass = ::JS_GetClass(cx, origObj); if (!nodeClass) { return JS_FALSE; } if (~nodeClass->flags & (JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS)) { nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_UNEXPECTED); return JS_FALSE; } nsCOMPtr xpcWrapper = do_QueryInterface(static_cast(::JS_GetPrivate(cx, origObj))); if (!xpcWrapper) { // Looks like whatever |origObj| is it's not our nsIContent. It might well // be the proto our binding installed, however, where the private is the // nsIXBLDocumentInfo, so just baul out quietly. Do NOT throw an exception // here. // We could make this stricter by checking the class maybe, but whatever return JS_TRUE; } nsCOMPtr content = do_QueryWrappedNative(xpcWrapper); if (!content) { nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_UNEXPECTED); return JS_FALSE; } if (content->HasFlag(NODE_IS_IN_BINDING_TEARDOWN)) { // Don't evaluate fields now! return JS_TRUE; } // This mirrors code in nsXBLProtoImpl::InstallImplementation nsIDocument* doc = content->GetOwnerDoc(); if (!doc) { return JS_TRUE; } nsIScriptGlobalObject* global = doc->GetScriptGlobalObject(); if (!global) { return JS_TRUE; } nsCOMPtr context = global->GetContext(); if (!context) { return JS_TRUE; } // Now we either resolve or fail PRBool didInstall; nsresult rv = field->InstallField(context, origObj, protoBinding->DocURI(), &didInstall); if (NS_FAILED(rv)) { if (!::JS_IsExceptionPending(cx)) { nsDOMClassInfo::ThrowJSException(cx, rv); } return JS_FALSE; } if (didInstall) { *objp = origObj; } // else we didn't resolve this field after all return JS_TRUE; } nsXBLJSClass::nsXBLJSClass(const nsAFlatCString& aClassName) { memset(this, 0, sizeof(nsXBLJSClass)); next = prev = static_cast(this); name = ToNewCString(aClassName); flags = JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_NEW_RESOLVE | JSCLASS_NEW_RESOLVE_GETS_START | // Our one reserved slot holds the relevant nsXBLPrototypeBinding JSCLASS_HAS_RESERVED_SLOTS(1); addProperty = delProperty = setProperty = getProperty = ::JS_PropertyStub; enumerate = ::JS_EnumerateStub; resolve = (JSResolveOp)XBLResolve; convert = ::JS_ConvertStub; finalize = XBLFinalize; } nsrefcnt nsXBLJSClass::Destroy() { NS_ASSERTION(next == prev && prev == static_cast(this), "referenced nsXBLJSClass is on LRU list already!?"); if (nsXBLService::gClassTable) { nsCStringKey key(name); (nsXBLService::gClassTable)->Remove(&key); } if (nsXBLService::gClassLRUListLength >= nsXBLService::gClassLRUListQuota) { // Over LRU list quota, just unhash and delete this class. delete this; } else { // Put this most-recently-used class on end of the LRU-sorted freelist. JSCList* mru = static_cast(this); JS_APPEND_LINK(mru, &nsXBLService::gClassLRUList); nsXBLService::gClassLRUListLength++; } return 0; } // Implementation ///////////////////////////////////////////////////////////////// // Constructors/Destructors nsXBLBinding::nsXBLBinding(nsXBLPrototypeBinding* aBinding) : mPrototypeBinding(aBinding), mInsertionPointTable(nsnull), mIsStyleBinding(PR_TRUE), mMarkedForDeath(PR_FALSE) { NS_ASSERTION(mPrototypeBinding, "Must have a prototype binding!"); // Grab a ref to the document info so the prototype binding won't die NS_ADDREF(mPrototypeBinding->XBLDocumentInfo()); } nsXBLBinding::~nsXBLBinding(void) { delete mInsertionPointTable; nsIXBLDocumentInfo* info = mPrototypeBinding->XBLDocumentInfo(); NS_RELEASE(info); } PR_STATIC_CALLBACK(PLDHashOperator) TraverseKey(nsISupports* aKey, nsInsertionPointList* aData, void* aClosure) { nsCycleCollectionTraversalCallback &cb = *static_cast(aClosure); cb.NoteXPCOMChild(aKey); if (aData) { NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY(*aData, nsXBLInsertionPoint) } return PL_DHASH_NEXT; } NS_IMPL_CYCLE_COLLECTION_NATIVE_CLASS(nsXBLBinding) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_NATIVE(nsXBLBinding) // XXX Probably can't unlink mPrototypeBinding->XBLDocumentInfo(), because // mPrototypeBinding is weak. NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mContent) // XXX What about mNextBinding and mInsertionPointTable? NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_BEGIN(nsXBLBinding) cb.NoteXPCOMChild(tmp->mPrototypeBinding->XBLDocumentInfo()); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mContent) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mNextBinding, nsXBLBinding) if (tmp->mInsertionPointTable) tmp->mInsertionPointTable->EnumerateRead(TraverseKey, &cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsXBLBinding, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsXBLBinding, Release) void nsXBLBinding::SetBaseBinding(nsXBLBinding* aBinding) { if (mNextBinding) { NS_ERROR("Base XBL binding is already defined!"); return; } mNextBinding = aBinding; // Comptr handles rel/add } void nsXBLBinding::InstallAnonymousContent(nsIContent* aAnonParent, nsIContent* aElement) { // We need to ensure two things. // (1) The anonymous content should be fooled into thinking it's in the bound // element's document, assuming that the bound element is in a document // Note that we don't change the current doc of aAnonParent here, since that // quite simply does not matter. aAnonParent is just a way of keeping refs // to all its kids, which are anonymous content from the point of view of // aElement. // (2) The children's parent back pointer should not be to this synthetic root // but should instead point to the enclosing parent element. nsIDocument* doc = aElement->GetCurrentDoc(); PRBool allowScripts = AllowScripts(); PRUint32 childCount = aAnonParent->GetChildCount(); for (PRUint32 i = 0; i < childCount; i++) { nsIContent *child = aAnonParent->GetChildAt(i); child->UnbindFromTree(); nsresult rv = child->BindToTree(doc, aElement, mBoundElement, allowScripts); if (NS_FAILED(rv)) { // Oh, well... Just give up. // XXXbz This really shouldn't be a void method! child->UnbindFromTree(); return; } #ifdef MOZ_XUL // To make XUL templates work (and other goodies that happen when // an element is added to a XUL document), we need to notify the // XUL document using its special API. nsCOMPtr xuldoc(do_QueryInterface(doc)); if (xuldoc) xuldoc->AddSubtreeToDocument(child); #endif } } void nsXBLBinding::SetBoundElement(nsIContent* aElement) { mBoundElement = aElement; if (mNextBinding) mNextBinding->SetBoundElement(aElement); } nsXBLBinding* nsXBLBinding::GetFirstBindingWithConstructor() { if (mPrototypeBinding->GetConstructor()) return this; if (mNextBinding) return mNextBinding->GetFirstBindingWithConstructor(); return nsnull; } PRBool nsXBLBinding::HasStyleSheets() const { // Find out if we need to re-resolve style. We'll need to do this // if we have additional stylesheets in our binding document. if (mPrototypeBinding->HasStyleSheets()) return PR_TRUE; return mNextBinding ? mNextBinding->HasStyleSheets() : PR_FALSE; } struct EnumData { nsXBLBinding* mBinding; EnumData(nsXBLBinding* aBinding) :mBinding(aBinding) {} }; struct ContentListData : public EnumData { nsBindingManager* mBindingManager; nsresult mRv; ContentListData(nsXBLBinding* aBinding, nsBindingManager* aManager) :EnumData(aBinding), mBindingManager(aManager), mRv(NS_OK) {} }; PR_STATIC_CALLBACK(PLDHashOperator) BuildContentLists(nsISupports* aKey, nsAutoPtr& aData, void* aClosure) { ContentListData* data = (ContentListData*)aClosure; nsBindingManager* bm = data->mBindingManager; nsXBLBinding* binding = data->mBinding; nsIContent *boundElement = binding->GetBoundElement(); PRInt32 count = aData->Length(); if (count == 0) return PL_DHASH_NEXT; // XXX Could this array just be altered in place and passed directly to // SetContentListFor? We'd save space if we could pull this off. nsInsertionPointList* contentList = new nsInsertionPointList; if (!contentList) { data->mRv = NS_ERROR_OUT_OF_MEMORY; return PL_DHASH_STOP; } // Figure out the relevant content node. nsXBLInsertionPoint* currPoint = aData->ElementAt(0); nsCOMPtr parent = currPoint->GetInsertionParent(); if (!parent) { data->mRv = NS_ERROR_FAILURE; return PL_DHASH_STOP; } PRInt32 currIndex = currPoint->GetInsertionIndex(); nsCOMPtr nodeList; if (parent == boundElement) { // We are altering anonymous nodes to accommodate insertion points. nodeList = binding->GetAnonymousNodes(); } else { // We are altering the explicit content list of a node to accommodate insertion points. nsCOMPtr node(do_QueryInterface(parent)); node->GetChildNodes(getter_AddRefs(nodeList)); } nsXBLInsertionPoint* pseudoPoint = nsnull; PRUint32 childCount; nodeList->GetLength(&childCount); PRInt32 j = 0; for (PRUint32 i = 0; i < childCount; i++) { nsCOMPtr node; nodeList->Item(i, getter_AddRefs(node)); nsCOMPtr child(do_QueryInterface(node)); if (((PRInt32)i) == currIndex) { // Add the currPoint to the insertion point list. contentList->AppendElement(currPoint); // Get the next real insertion point and update our currIndex. j++; if (j < count) { currPoint = aData->ElementAt(j); currIndex = currPoint->GetInsertionIndex(); } // Null out our current pseudo-point. pseudoPoint = nsnull; } if (!pseudoPoint) { pseudoPoint = new nsXBLInsertionPoint(parent, (PRUint32) -1, nsnull); if (pseudoPoint) { contentList->AppendElement(pseudoPoint); } } if (pseudoPoint) { pseudoPoint->AddChild(child); } } // Add in all the remaining insertion points. contentList->AppendElements(aData->Elements() + j, count - j); // Now set the content list using the binding manager, // If the bound element is the parent, then we alter the anonymous node list // instead. This allows us to always maintain two distinct lists should // insertion points be nested into an inner binding. if (parent == boundElement) bm->SetAnonymousNodesFor(parent, contentList); else bm->SetContentListFor(parent, contentList); return PL_DHASH_NEXT; } PR_STATIC_CALLBACK(PLDHashOperator) RealizeDefaultContent(nsISupports* aKey, nsAutoPtr& aData, void* aClosure) { ContentListData* data = (ContentListData*)aClosure; nsBindingManager* bm = data->mBindingManager; nsXBLBinding* binding = data->mBinding; PRInt32 count = aData->Length(); for (PRInt32 i = 0; i < count; i++) { nsXBLInsertionPoint* currPoint = aData->ElementAt(i); PRInt32 insCount = currPoint->ChildCount(); if (insCount == 0) { nsCOMPtr defContent = currPoint->GetDefaultContentTemplate(); if (defContent) { // We need to take this template and use it to realize the // actual default content (through cloning). // Clone this insertion point element. nsCOMPtr insParent = currPoint->GetInsertionParent(); if (!insParent) { data->mRv = NS_ERROR_FAILURE; return PL_DHASH_STOP; } nsIDocument *document = insParent->GetOwnerDoc(); if (!document) { data->mRv = NS_ERROR_FAILURE; return PL_DHASH_STOP; } nsCOMPtr clonedNode; nsCOMArray nodesWithProperties; nsNodeUtils::Clone(defContent, PR_TRUE, document->NodeInfoManager(), nodesWithProperties, getter_AddRefs(clonedNode)); // Now that we have the cloned content, install the default content as // if it were additional anonymous content. nsCOMPtr clonedContent(do_QueryInterface(clonedNode)); binding->InstallAnonymousContent(clonedContent, insParent); // Cache the clone so that it can be properly destroyed if/when our // other anonymous content is destroyed. currPoint->SetDefaultContent(clonedContent); // Now make sure the kids of the clone are added to the insertion point as // children. PRUint32 cloneKidCount = clonedContent->GetChildCount(); for (PRUint32 k = 0; k < cloneKidCount; k++) { nsIContent *cloneChild = clonedContent->GetChildAt(k); bm->SetInsertionParent(cloneChild, insParent); currPoint->AddChild(cloneChild); } } } } return PL_DHASH_NEXT; } PR_STATIC_CALLBACK(PLDHashOperator) ChangeDocumentForDefaultContent(nsISupports* aKey, nsAutoPtr& aData, void* aClosure) { PRInt32 count = aData->Length(); for (PRInt32 i = 0; i < count; i++) { nsXBLInsertionPoint* currPoint = aData->ElementAt(i); nsCOMPtr defContent = currPoint->GetDefaultContent(); if (defContent) defContent->UnbindFromTree(); } return PL_DHASH_NEXT; } void nsXBLBinding::GenerateAnonymousContent() { // Fetch the content element for this binding. nsIContent* content = mPrototypeBinding->GetImmediateChild(nsGkAtoms::content); if (!content) { // We have no anonymous content. if (mNextBinding) mNextBinding->GenerateAnonymousContent(); return; } // Find out if we're really building kids or if we're just // using the attribute-setting shorthand hack. PRUint32 contentCount = content->GetChildCount(); // Plan to build the content by default. PRBool hasContent = (contentCount > 0); PRBool hasInsertionPoints = mPrototypeBinding->HasInsertionPoints(); #ifdef DEBUG // See if there's an includes attribute. if (nsContentUtils::HasNonEmptyAttr(content, kNameSpaceID_None, nsGkAtoms::includes)) { nsCAutoString message("An XBL Binding with URI "); nsCAutoString uri; mPrototypeBinding->BindingURI()->GetSpec(uri); message += uri; message += " is still using the deprecated\n syntax! Use instead!\n"; NS_WARNING(message.get()); } #endif if (hasContent || hasInsertionPoints) { nsIDocument* doc = mBoundElement->GetOwnerDoc(); // XXX doc will be null if we're in the midst of paint suppression. if (! doc) return; nsBindingManager *bindingManager = doc->BindingManager(); nsCOMPtr children; bindingManager->GetContentListFor(mBoundElement, getter_AddRefs(children)); nsCOMPtr node; nsCOMPtr childContent; PRUint32 length; children->GetLength(&length); if (length > 0 && !hasInsertionPoints) { // There are children being placed underneath us, but we have no specified // insertion points, and therefore no place to put the kids. Don't generate // anonymous content. // Special case template and observes. for (PRUint32 i = 0; i < length; i++) { children->Item(i, getter_AddRefs(node)); childContent = do_QueryInterface(node); nsINodeInfo *ni = childContent->NodeInfo(); nsIAtom *localName = ni->NameAtom(); if (ni->NamespaceID() != kNameSpaceID_XUL || (localName != nsGkAtoms::observes && localName != nsGkAtoms::_template)) { hasContent = PR_FALSE; break; } } } if (hasContent || hasInsertionPoints) { nsIDocument *document = mBoundElement->GetOwnerDoc(); if (!document) { return; } nsCOMPtr clonedNode; nsCOMArray nodesWithProperties; nsNodeUtils::Clone(content, PR_TRUE, document->NodeInfoManager(), nodesWithProperties, getter_AddRefs(clonedNode)); mContent = do_QueryInterface(clonedNode); InstallAnonymousContent(mContent, mBoundElement); if (hasInsertionPoints) { // Now check and see if we have a single insertion point // or multiple insertion points. // Enumerate the prototype binding's insertion table to build // our table of instantiated insertion points. mPrototypeBinding->InstantiateInsertionPoints(this); // We now have our insertion point table constructed. We // enumerate this table. For each array of insertion points // bundled under the same content node, we generate a content // list. In the case of the bound element, we generate a new // anonymous node list that will be used in place of the binding's // cached anonymous node list. ContentListData data(this, bindingManager); mInsertionPointTable->Enumerate(BuildContentLists, &data); if (NS_FAILED(data.mRv)) { return; } // We need to place the children // at their respective insertion points. PRUint32 index = 0; PRBool multiplePoints = PR_FALSE; nsIContent *singlePoint = GetSingleInsertionPoint(&index, &multiplePoints); if (children) { if (multiplePoints) { // We must walk the entire content list in order to determine where // each child belongs. children->GetLength(&length); for (PRUint32 i = 0; i < length; i++) { children->Item(i, getter_AddRefs(node)); childContent = do_QueryInterface(node); // Now determine the insertion point in the prototype table. PRUint32 index; nsIContent *point = GetInsertionPoint(childContent, &index); bindingManager->SetInsertionParent(childContent, point); // Find the correct nsIXBLInsertion point in our table. nsInsertionPointList* arr = nsnull; GetInsertionPointsFor(point, &arr); nsXBLInsertionPoint* insertionPoint = nsnull; PRInt32 arrCount = arr->Length(); for (PRInt32 j = 0; j < arrCount; j++) { insertionPoint = arr->ElementAt(j); if (insertionPoint->Matches(point, index)) break; insertionPoint = nsnull; } if (insertionPoint) insertionPoint->AddChild(childContent); else { // We were unable to place this child. All anonymous content // should be thrown out. Special-case template and observes. nsINodeInfo *ni = childContent->NodeInfo(); nsIAtom *localName = ni->NameAtom(); if (ni->NamespaceID() != kNameSpaceID_XUL || (localName != nsGkAtoms::observes && localName != nsGkAtoms::_template)) { // Kill all anonymous content. mContent = nsnull; bindingManager->SetContentListFor(mBoundElement, nsnull); bindingManager->SetAnonymousNodesFor(mBoundElement, nsnull); return; } } } } else { // All of our children are shunted to this single insertion point. nsInsertionPointList* arr = nsnull; GetInsertionPointsFor(singlePoint, &arr); nsXBLInsertionPoint* insertionPoint = arr->ElementAt(0); nsCOMPtr node; nsCOMPtr content; PRUint32 length; children->GetLength(&length); for (PRUint32 i = 0; i < length; i++) { children->Item(i, getter_AddRefs(node)); content = do_QueryInterface(node); bindingManager->SetInsertionParent(content, singlePoint); insertionPoint->AddChild(content); } } } // Now that all of our children have been added, we need to walk all of our // nsIXBLInsertion points to see if any of them have default content that // needs to be built. mInsertionPointTable->Enumerate(RealizeDefaultContent, &data); if (NS_FAILED(data.mRv)) { return; } } } mPrototypeBinding->SetInitialAttributes(mBoundElement, mContent); } // Always check the content element for potential attributes. // This shorthand hack always happens, even when we didn't // build anonymous content. const nsAttrName* attrName; for (PRUint32 i = 0; (attrName = content->GetAttrNameAt(i)); ++i) { PRInt32 namespaceID = attrName->NamespaceID(); nsIAtom* name = attrName->LocalName(); if (name != nsGkAtoms::includes) { if (!nsContentUtils::HasNonEmptyAttr(mBoundElement, namespaceID, name)) { nsAutoString value2; content->GetAttr(namespaceID, name, value2); mBoundElement->SetAttr(namespaceID, name, attrName->GetPrefix(), value2, PR_FALSE); } } // Conserve space by wiping the attributes off the clone. if (mContent) mContent->UnsetAttr(namespaceID, name, PR_FALSE); } } void nsXBLBinding::InstallEventHandlers() { // Don't install handlers if scripts aren't allowed. if (AllowScripts()) { // Fetch the handlers prototypes for this binding. nsXBLPrototypeHandler* handlerChain = mPrototypeBinding->GetPrototypeHandlers(); if (handlerChain) { nsCOMPtr manager; mBoundElement->GetListenerManager(PR_TRUE, getter_AddRefs(manager)); if (!manager) return; nsCOMPtr systemEventGroup; PRBool isChromeDoc = nsContentUtils::IsChromeDoc(mBoundElement->GetOwnerDoc()); nsXBLPrototypeHandler* curr; for (curr = handlerChain; curr; curr = curr->GetNextHandler()) { // Fetch the event type. nsCOMPtr eventAtom = curr->GetEventName(); if (!eventAtom || eventAtom == nsGkAtoms::keyup || eventAtom == nsGkAtoms::keydown || eventAtom == nsGkAtoms::keypress) continue; nsAutoString type; eventAtom->ToString(type); // If this is a command, add it in the system event group, otherwise // add it to the standard event group. // This is a weak ref. systemEventGroup above is already a // strong ref, so we are guaranteed it will not go away. nsIDOMEventGroup* eventGroup = nsnull; if (curr->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND | NS_HANDLER_TYPE_SYSTEM)) { if (!systemEventGroup) manager->GetSystemEventGroupLM(getter_AddRefs(systemEventGroup)); eventGroup = systemEventGroup; } nsXBLEventHandler* handler = curr->GetEventHandler(); if (handler) { // Figure out if we're using capturing or not. PRInt32 flags = (curr->GetPhase() == NS_PHASE_CAPTURING) ? NS_EVENT_FLAG_CAPTURE : NS_EVENT_FLAG_BUBBLE; PRBool hasAllowUntrustedAttr = curr->HasAllowUntrustedAttr(); if ((hasAllowUntrustedAttr && curr->AllowUntrustedEvents()) || (!hasAllowUntrustedAttr && !isChromeDoc)) { flags |= NS_PRIV_EVENT_UNTRUSTED_PERMITTED; } manager->AddEventListenerByType(handler, type, flags, eventGroup); } } const nsCOMArray* keyHandlers = mPrototypeBinding->GetKeyEventHandlers(); PRInt32 i; for (i = 0; i < keyHandlers->Count(); ++i) { nsXBLKeyEventHandler* handler = keyHandlers->ObjectAt(i); handler->SetIsBoundToChrome(isChromeDoc); nsAutoString type; handler->GetEventName(type); // If this is a command, add it in the system event group, otherwise // add it to the standard event group. // This is a weak ref. systemEventGroup above is already a // strong ref, so we are guaranteed it will not go away. nsIDOMEventGroup* eventGroup = nsnull; if (handler->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND | NS_HANDLER_TYPE_SYSTEM)) { if (!systemEventGroup) manager->GetSystemEventGroupLM(getter_AddRefs(systemEventGroup)); eventGroup = systemEventGroup; } // Figure out if we're using capturing or not. PRInt32 flags = (handler->GetPhase() == NS_PHASE_CAPTURING) ? NS_EVENT_FLAG_CAPTURE : NS_EVENT_FLAG_BUBBLE; // For key handlers we have to set NS_PRIV_EVENT_UNTRUSTED_PERMITTED flag. // Whether the handling of the event is allowed or not is handled in // nsXBLKeyEventHandler::HandleEvent flags |= NS_PRIV_EVENT_UNTRUSTED_PERMITTED; manager->AddEventListenerByType(handler, type, flags, eventGroup); } } } if (mNextBinding) mNextBinding->InstallEventHandlers(); } nsresult nsXBLBinding::InstallImplementation() { // Always install the base class properties first, so that // derived classes can reference the base class properties. if (mNextBinding) { nsresult rv = mNextBinding->InstallImplementation(); NS_ENSURE_SUCCESS(rv, rv); } // iterate through each property in the prototype's list and install the property. if (AllowScripts()) return mPrototypeBinding->InstallImplementation(mBoundElement); return NS_OK; } nsIAtom* nsXBLBinding::GetBaseTag(PRInt32* aNameSpaceID) { nsIAtom *tag = mPrototypeBinding->GetBaseTag(aNameSpaceID); if (!tag && mNextBinding) return mNextBinding->GetBaseTag(aNameSpaceID); return tag; } void nsXBLBinding::AttributeChanged(nsIAtom* aAttribute, PRInt32 aNameSpaceID, PRBool aRemoveFlag, PRBool aNotify) { // XXX Change if we ever allow multiple bindings in a chain to contribute anonymous content if (!mContent) { if (mNextBinding) mNextBinding->AttributeChanged(aAttribute, aNameSpaceID, aRemoveFlag, aNotify); } else { mPrototypeBinding->AttributeChanged(aAttribute, aNameSpaceID, aRemoveFlag, mBoundElement, mContent, aNotify); } } void nsXBLBinding::ExecuteAttachedHandler() { if (mNextBinding) mNextBinding->ExecuteAttachedHandler(); if (AllowScripts()) mPrototypeBinding->BindingAttached(mBoundElement); } void nsXBLBinding::ExecuteDetachedHandler() { if (AllowScripts()) mPrototypeBinding->BindingDetached(mBoundElement); if (mNextBinding) mNextBinding->ExecuteDetachedHandler(); } void nsXBLBinding::UnhookEventHandlers() { nsXBLPrototypeHandler* handlerChain = mPrototypeBinding->GetPrototypeHandlers(); if (handlerChain) { nsCOMPtr piTarget = do_QueryInterface(mBoundElement); nsCOMPtr target = do_QueryInterface(piTarget); nsCOMPtr systemEventGroup; nsXBLPrototypeHandler* curr; for (curr = handlerChain; curr; curr = curr->GetNextHandler()) { nsXBLEventHandler* handler = curr->GetCachedEventHandler(); if (handler) { nsCOMPtr eventAtom = curr->GetEventName(); if (!eventAtom || eventAtom == nsGkAtoms::keyup || eventAtom == nsGkAtoms::keydown || eventAtom == nsGkAtoms::keypress) continue; nsAutoString type; eventAtom->ToString(type); // Figure out if we're using capturing or not. PRBool useCapture = (curr->GetPhase() == NS_PHASE_CAPTURING); // If this is a command, remove it from the system event group, otherwise // remove it from the standard event group. // This is a weak ref. systemEventGroup above is already a // strong ref, so we are guaranteed it will not go away. nsIDOMEventGroup* eventGroup = nsnull; if (curr->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND | NS_HANDLER_TYPE_SYSTEM)) { if (!systemEventGroup) piTarget->GetSystemEventGroup(getter_AddRefs(systemEventGroup)); eventGroup = systemEventGroup; } target->RemoveGroupedEventListener(type, handler, useCapture, eventGroup); } } const nsCOMArray* keyHandlers = mPrototypeBinding->GetKeyEventHandlers(); PRInt32 i; for (i = 0; i < keyHandlers->Count(); ++i) { nsXBLKeyEventHandler* handler = keyHandlers->ObjectAt(i); nsAutoString type; handler->GetEventName(type); // Figure out if we're using capturing or not. PRBool useCapture = (handler->GetPhase() == NS_PHASE_CAPTURING); // If this is a command, remove it from the system event group, otherwise // remove it from the standard event group. // This is a weak ref. systemEventGroup above is already a // strong ref, so we are guaranteed it will not go away. nsIDOMEventGroup* eventGroup = nsnull; if (handler->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND | NS_HANDLER_TYPE_SYSTEM)) { if (!systemEventGroup) piTarget->GetSystemEventGroup(getter_AddRefs(systemEventGroup)); eventGroup = systemEventGroup; } target->RemoveGroupedEventListener(type, handler, useCapture, eventGroup); } } } void nsXBLBinding::ChangeDocument(nsIDocument* aOldDocument, nsIDocument* aNewDocument) { if (aOldDocument != aNewDocument) { // Only style bindings get their prototypes unhooked. First do ourselves. if (mIsStyleBinding) { // Now the binding dies. Unhook our prototypes. if (mPrototypeBinding->HasImplementation()) { nsIScriptGlobalObject *global = aOldDocument->GetScopeObject(); if (global) { nsCOMPtr context = global->GetContext(); if (context) { JSContext *cx = (JSContext *)context->GetNativeContext(); nsCOMPtr wrapper; nsresult rv = nsContentUtils::XPConnect()-> WrapNative(cx, global->GetGlobalJSObject(), mBoundElement, NS_GET_IID(nsISupports), getter_AddRefs(wrapper)); if (NS_FAILED(rv)) return; JSObject* scriptObject = nsnull; rv = wrapper->GetJSObject(&scriptObject); if (NS_FAILED(rv)) return; // XXX Stay in sync! What if a layered binding has an // ?! // XXXbz what does that comment mean, really? It seems to date // back to when there was such a thing as an , whever // that was... // Find the right prototype. JSObject* base = scriptObject; JSObject* proto; JSAutoRequest ar(cx); for ( ; true; base = proto) { // Will break out on null proto proto = ::JS_GetPrototype(cx, base); if (!proto) { break; } JSClass* clazz = ::JS_GetClass(cx, proto); if (!clazz || (~clazz->flags & (JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS)) || JSCLASS_RESERVED_SLOTS(clazz) != 1) { // Clearly not the right class continue; } nsCOMPtr docInfo = do_QueryInterface(static_cast (::JS_GetPrivate(cx, proto))); if (!docInfo) { // Not the proto we seek continue; } jsval protoBinding; if (!::JS_GetReservedSlot(cx, proto, 0, &protoBinding)) { NS_ERROR("Really shouldn't happen"); continue; } if (JSVAL_TO_PRIVATE(protoBinding) != mPrototypeBinding) { // Not the right binding continue; } // Alright! This is the right prototype. Pull it out of the // proto chain. JSObject* grandProto = ::JS_GetPrototype(cx, proto); ::JS_SetPrototype(cx, base, grandProto); break; } // Do this after unhooking the proto to avoid extra walking along // the proto chain as the JS engine tries to resolve the properties // we're removing. mBoundElement->SetFlags(NODE_IS_IN_BINDING_TEARDOWN); mPrototypeBinding->UndefineFields(cx, scriptObject); mBoundElement->UnsetFlags(NODE_IS_IN_BINDING_TEARDOWN); // Don't remove the reference from the document to the // wrapper here since it'll be removed by the element // itself when that's taken out of the document. } } } } // Then do our ancestors. This reverses the construction order, so that at // all times things are consistent as far as everyone is concerned. if (mNextBinding) { mNextBinding->ChangeDocument(aOldDocument, aNewDocument); } // Update the anonymous content. // XXXbz why not only for style bindings? nsIContent *anonymous = mContent; if (anonymous) { // Also kill the default content within all our insertion points. if (mInsertionPointTable) mInsertionPointTable->Enumerate(ChangeDocumentForDefaultContent, nsnull); #ifdef MOZ_XUL nsCOMPtr xuldoc(do_QueryInterface(aOldDocument)); #endif anonymous->UnbindFromTree(); // Kill it. #ifdef MOZ_XUL // To make XUL templates work (and other XUL-specific stuff), // we'll need to notify it using its add & remove APIs. Grab the // interface now... if (xuldoc) xuldoc->RemoveSubtreeFromDocument(anonymous); #endif } // Make sure that henceforth we don't claim that mBoundElement's children // have insertion parents in the old document. nsBindingManager* bindingManager = aOldDocument->BindingManager(); for (PRUint32 i = mBoundElement->GetChildCount(); i > 0; --i) { NS_ASSERTION(mBoundElement->GetChildAt(i-1), "Must have child at i for 0 <= i < GetChildCount()!"); bindingManager->SetInsertionParent(mBoundElement->GetChildAt(i-1), nsnull); } } } PRBool nsXBLBinding::InheritsStyle() const { // XXX Will have to change if we ever allow multiple bindings to contribute anonymous content. // Most derived binding with anonymous content determines style inheritance for now. // XXX What about bindings with but no kids, e.g., my treecell-text binding? if (mContent) return mPrototypeBinding->InheritsStyle(); if (mNextBinding) return mNextBinding->InheritsStyle(); return PR_TRUE; } void nsXBLBinding::WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc, void* aData) { if (mNextBinding) mNextBinding->WalkRules(aFunc, aData); nsIStyleRuleProcessor *rules = mPrototypeBinding->GetRuleProcessor(); if (rules) (*aFunc)(rules, aData); } // Internal helper methods //////////////////////////////////////////////////////////////// // static nsresult nsXBLBinding::DoInitJSClass(JSContext *cx, JSObject *global, JSObject *obj, const nsAFlatCString& aClassName, nsXBLPrototypeBinding* aProtoBinding, void **aClassObject) { // First ensure our JS class is initialized. jsval val; JSObject* proto; nsCAutoString className(aClassName); JSObject* parent_proto = nsnull; // If we have an "obj" we can set this JSAutoRequest ar(cx); if (obj) { // Retrieve the current prototype of obj. parent_proto = ::JS_GetPrototype(cx, obj); if (parent_proto) { // We need to create a unique classname based on aClassName and // parent_proto. Append a space (an invalid URI character) to ensure that // we don't have accidental collisions with the case when parent_proto is // null and aClassName ends in some bizarre numbers (yeah, it's unlikely). jsid parent_proto_id; if (!::JS_GetObjectId(cx, parent_proto, &parent_proto_id)) { // Probably OOM return NS_ERROR_OUT_OF_MEMORY; } // One space, maybe "0x", at most 16 chars (on a 64-bit system) of long, // and a null-terminator (which PR_snprintf ensures is there even if the // string representation of what we're printing does not fit in the buffer // provided). char buf[20]; PR_snprintf(buf, sizeof(buf), " %lx", parent_proto_id); className.Append(buf); } } if ((!::JS_LookupPropertyWithFlags(cx, global, className.get(), JSRESOLVE_CLASSNAME, &val)) || JSVAL_IS_PRIMITIVE(val)) { // We need to initialize the class. nsXBLJSClass* c; void* classObject; nsCStringKey key(className); classObject = (nsXBLService::gClassTable)->Get(&key); if (classObject) { c = static_cast(classObject); // If c is on the LRU list (i.e., not linked to itself), remove it now! JSCList* link = static_cast(c); if (c->next != link) { JS_REMOVE_AND_INIT_LINK(link); nsXBLService::gClassLRUListLength--; } } else { if (JS_CLIST_IS_EMPTY(&nsXBLService::gClassLRUList)) { // We need to create a struct for this class. c = new nsXBLJSClass(className); if (!c) return NS_ERROR_OUT_OF_MEMORY; } else { // Pull the least recently used class struct off the list. JSCList* lru = (nsXBLService::gClassLRUList).next; JS_REMOVE_AND_INIT_LINK(lru); nsXBLService::gClassLRUListLength--; // Remove any mapping from the old name to the class struct. c = static_cast(lru); nsCStringKey oldKey(c->name); (nsXBLService::gClassTable)->Remove(&oldKey); // Change the class name and we're done. nsMemory::Free((void*) c->name); c->name = ToNewCString(className); } // Add c to our table. (nsXBLService::gClassTable)->Put(&key, (void*)c); } // The prototype holds a strong reference to its class struct. c->Hold(); // Make a new object prototyped by parent_proto and parented by global. proto = ::JS_InitClass(cx, // context global, // global object parent_proto, // parent proto c, // JSClass nsnull, // JSNative ctor 0, // ctor args nsnull, // proto props nsnull, // proto funcs nsnull, // ctor props (static) nsnull); // ctor funcs (static) if (!proto) { // This will happen if we're OOM or if the security manager // denies defining the new class... (nsXBLService::gClassTable)->Remove(&key); c->Drop(); return NS_ERROR_OUT_OF_MEMORY; } // Keep this proto binding alive while we're alive. Do this first so that // we can guarantee that in XBLFinalize this will be non-null. nsIXBLDocumentInfo* docInfo = aProtoBinding->XBLDocumentInfo(); ::JS_SetPrivate(cx, proto, docInfo); NS_ADDREF(docInfo); if (!::JS_SetReservedSlot(cx, proto, 0, PRIVATE_TO_JSVAL(aProtoBinding))) { (nsXBLService::gClassTable)->Remove(&key); // |c| will get dropped when |proto| is finalized return NS_ERROR_OUT_OF_MEMORY; } *aClassObject = (void*)proto; } else { proto = JSVAL_TO_OBJECT(val); } if (obj) { // Set the prototype of our object to be the new class. if (!::JS_SetPrototype(cx, obj, proto)) { return NS_ERROR_FAILURE; } } return NS_OK; } PRBool nsXBLBinding::AllowScripts() { PRBool result; mPrototypeBinding->GetAllowScripts(&result); if (!result) { return result; } // Nasty hack. Use the JSContext of the bound node, since the // security manager API expects to get the docshell type from // that. But use the nsIPrincipal of our document. nsIScriptSecurityManager* mgr = nsContentUtils::GetSecurityManager(); if (!mgr) { return PR_FALSE; } nsIDocument* doc = mBoundElement->GetOwnerDoc(); if (!doc) { return PR_FALSE; } nsIScriptGlobalObject* global = doc->GetScriptGlobalObject(); if (!global) { return PR_FALSE; } nsCOMPtr context = global->GetContext(); if (!context) { return PR_FALSE; } JSContext* cx = (JSContext*) context->GetNativeContext(); nsCOMPtr ourDocument; mPrototypeBinding->XBLDocumentInfo()->GetDocument(getter_AddRefs(ourDocument)); PRBool canExecute; nsresult rv = mgr->CanExecuteScripts(cx, ourDocument->NodePrincipal(), &canExecute); return NS_SUCCEEDED(rv) && canExecute; } void nsXBLBinding::RemoveInsertionParent(nsIContent* aParent) { if (mNextBinding) { mNextBinding->RemoveInsertionParent(aParent); } if (mInsertionPointTable) { nsInsertionPointList* list = nsnull; mInsertionPointTable->Get(aParent, &list); if (list) { PRInt32 count = list->Length(); for (PRInt32 i = 0; i < count; ++i) { nsRefPtr currPoint = list->ElementAt(i); nsCOMPtr defContent = currPoint->GetDefaultContent(); if (defContent) { defContent->UnbindFromTree(); } #ifdef DEBUG nsCOMPtr parent = currPoint->GetInsertionParent(); NS_ASSERTION(!parent || parent == aParent, "Wrong insertion parent!"); #endif currPoint->ClearInsertionParent(); } mInsertionPointTable->Remove(aParent); } } } PRBool nsXBLBinding::HasInsertionParent(nsIContent* aParent) { if (mInsertionPointTable) { nsInsertionPointList* list = nsnull; mInsertionPointTable->Get(aParent, &list); if (list) { return PR_TRUE; } } return mNextBinding ? mNextBinding->HasInsertionParent(aParent) : PR_FALSE; } nsresult nsXBLBinding::GetInsertionPointsFor(nsIContent* aParent, nsInsertionPointList** aResult) { if (!mInsertionPointTable) { mInsertionPointTable = new nsClassHashtable; if (!mInsertionPointTable || !mInsertionPointTable->Init(4)) { delete mInsertionPointTable; mInsertionPointTable = nsnull; return NS_ERROR_OUT_OF_MEMORY; } } mInsertionPointTable->Get(aParent, aResult); if (!*aResult) { *aResult = new nsInsertionPointList; if (!*aResult || !mInsertionPointTable->Put(aParent, *aResult)) { delete *aResult; *aResult = nsnull; return NS_ERROR_OUT_OF_MEMORY; } if (aParent) { aParent->SetFlags(NODE_IS_INSERTION_PARENT); } } return NS_OK; } nsInsertionPointList* nsXBLBinding::GetExistingInsertionPointsFor(nsIContent* aParent) { if (!mInsertionPointTable) { return nsnull; } nsInsertionPointList* result = nsnull; mInsertionPointTable->Get(aParent, &result); return result; } nsIContent* nsXBLBinding::GetInsertionPoint(nsIContent* aChild, PRUint32* aIndex) { if (mContent) { return mPrototypeBinding->GetInsertionPoint(mBoundElement, mContent, aChild, aIndex); } if (mNextBinding) return mNextBinding->GetInsertionPoint(aChild, aIndex); return nsnull; } nsIContent* nsXBLBinding::GetSingleInsertionPoint(PRUint32* aIndex, PRBool* aMultipleInsertionPoints) { *aMultipleInsertionPoints = PR_FALSE; if (mContent) { return mPrototypeBinding->GetSingleInsertionPoint(mBoundElement, mContent, aIndex, aMultipleInsertionPoints); } if (mNextBinding) return mNextBinding->GetSingleInsertionPoint(aIndex, aMultipleInsertionPoints); return nsnull; } nsXBLBinding* nsXBLBinding::RootBinding() { if (mNextBinding) return mNextBinding->RootBinding(); return this; } nsXBLBinding* nsXBLBinding::GetFirstStyleBinding() { if (mIsStyleBinding) return this; return mNextBinding ? mNextBinding->GetFirstStyleBinding() : nsnull; } PRBool nsXBLBinding::ResolveAllFields(JSContext *cx, JSObject *obj) const { if (!mPrototypeBinding->ResolveAllFields(cx, obj)) { return PR_FALSE; } if (mNextBinding) { return mNextBinding->ResolveAllFields(cx, obj); } return PR_TRUE; } void nsXBLBinding::MarkForDeath() { mMarkedForDeath = PR_TRUE; ExecuteDetachedHandler(); } PRBool nsXBLBinding::ImplementsInterface(REFNSIID aIID) const { return mPrototypeBinding->ImplementsInterface(aIID) || (mNextBinding && mNextBinding->ImplementsInterface(aIID)); } already_AddRefed nsXBLBinding::GetAnonymousNodes() { if (mContent) { nsCOMPtr elt(do_QueryInterface(mContent)); nsIDOMNodeList *nodeList = nsnull; elt->GetChildNodes(&nodeList); return nodeList; } if (mNextBinding) return mNextBinding->GetAnonymousNodes(); return nsnull; } PRBool nsXBLBinding::ShouldBuildChildFrames() const { if (mContent) return mPrototypeBinding->ShouldBuildChildFrames(); if (mNextBinding) return mNextBinding->ShouldBuildChildFrames(); return PR_TRUE; }