Bumping gaia.json for 2 gaia revision(s) a=gaia-bump
[gecko.git] / dom / xbl / nsXBLBinding.cpp
blob84da40cef2985d9d2ed5bafc1239099fd3a8d9af
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=79: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsCOMPtr.h"
8 #include "nsIAtom.h"
9 #include "nsXBLDocumentInfo.h"
10 #include "nsIInputStream.h"
11 #include "nsNameSpaceManager.h"
12 #include "nsIURI.h"
13 #include "nsIURL.h"
14 #include "nsIChannel.h"
15 #include "nsXPIDLString.h"
16 #include "nsReadableUtils.h"
17 #include "nsNetUtil.h"
18 #include "plstr.h"
19 #include "nsIContent.h"
20 #include "nsIDocument.h"
21 #include "nsContentUtils.h"
22 #include "ChildIterator.h"
23 #ifdef MOZ_XUL
24 #include "nsIXULDocument.h"
25 #endif
26 #include "nsIXMLContentSink.h"
27 #include "nsContentCID.h"
28 #include "mozilla/dom/XMLDocument.h"
29 #include "jsapi.h"
30 #include "nsXBLService.h"
31 #include "nsIXPConnect.h"
32 #include "nsIScriptContext.h"
33 #include "nsCRT.h"
35 // Event listeners
36 #include "mozilla/EventListenerManager.h"
37 #include "nsIDOMEventListener.h"
38 #include "nsAttrName.h"
40 #include "nsGkAtoms.h"
42 #include "nsXBLPrototypeHandler.h"
44 #include "nsXBLPrototypeBinding.h"
45 #include "nsXBLBinding.h"
46 #include "nsIPrincipal.h"
47 #include "nsIScriptSecurityManager.h"
48 #include "mozilla/dom/XBLChildrenElement.h"
50 #include "prprf.h"
51 #include "nsNodeUtils.h"
52 #include "nsJSUtils.h"
53 #include "nsCycleCollector.h"
55 // Nasty hack. Maybe we could move some of the classinfo utility methods
56 // (e.g. WrapNative) over to nsContentUtils?
57 #include "nsDOMClassInfo.h"
59 #include "mozilla/dom/Element.h"
60 #include "mozilla/dom/ScriptSettings.h"
61 #include "mozilla/dom/ShadowRoot.h"
63 using namespace mozilla;
64 using namespace mozilla::dom;
66 // Helper classes
68 /***********************************************************************/
70 // The JS class for XBLBinding
72 static void
73 XBLFinalize(JSFreeOp *fop, JSObject *obj)
75 nsXBLDocumentInfo* docInfo =
76 static_cast<nsXBLDocumentInfo*>(::JS_GetPrivate(obj));
77 cyclecollector::DeferredFinalize(docInfo);
80 static bool
81 XBLEnumerate(JSContext *cx, JS::Handle<JSObject*> obj)
83 nsXBLPrototypeBinding* protoBinding =
84 static_cast<nsXBLPrototypeBinding*>(::JS_GetReservedSlot(obj, 0).toPrivate());
85 MOZ_ASSERT(protoBinding);
87 return protoBinding->ResolveAllFields(cx, obj);
90 static const JSClass gPrototypeJSClass = {
91 "XBL prototype JSClass",
92 JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS |
93 JSCLASS_NEW_RESOLVE |
94 // Our one reserved slot holds the relevant nsXBLPrototypeBinding
95 JSCLASS_HAS_RESERVED_SLOTS(1),
96 JS_PropertyStub, JS_DeletePropertyStub,
97 JS_PropertyStub, JS_StrictPropertyStub,
98 XBLEnumerate, JS_ResolveStub,
99 JS_ConvertStub, XBLFinalize,
100 nullptr, nullptr, nullptr, nullptr
103 // Implementation /////////////////////////////////////////////////////////////////
105 // Constructors/Destructors
106 nsXBLBinding::nsXBLBinding(nsXBLPrototypeBinding* aBinding)
107 : mMarkedForDeath(false)
108 , mUsingContentXBLScope(false)
109 , mPrototypeBinding(aBinding)
111 NS_ASSERTION(mPrototypeBinding, "Must have a prototype binding!");
112 // Grab a ref to the document info so the prototype binding won't die
113 NS_ADDREF(mPrototypeBinding->XBLDocumentInfo());
116 // Constructor used by web components.
117 nsXBLBinding::nsXBLBinding(ShadowRoot* aShadowRoot, nsXBLPrototypeBinding* aBinding)
118 : mMarkedForDeath(false),
119 mUsingContentXBLScope(false),
120 mPrototypeBinding(aBinding),
121 mContent(aShadowRoot)
123 NS_ASSERTION(mPrototypeBinding, "Must have a prototype binding!");
124 // Grab a ref to the document info so the prototype binding won't die
125 NS_ADDREF(mPrototypeBinding->XBLDocumentInfo());
128 nsXBLBinding::~nsXBLBinding(void)
130 if (mContent) {
131 nsXBLBinding::UninstallAnonymousContent(mContent->OwnerDoc(), mContent);
133 nsXBLDocumentInfo* info = mPrototypeBinding->XBLDocumentInfo();
134 NS_RELEASE(info);
137 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXBLBinding)
139 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXBLBinding)
140 // XXX Probably can't unlink mPrototypeBinding->XBLDocumentInfo(), because
141 // mPrototypeBinding is weak.
142 if (tmp->mContent) {
143 nsXBLBinding::UninstallAnonymousContent(tmp->mContent->OwnerDoc(),
144 tmp->mContent);
146 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent)
147 NS_IMPL_CYCLE_COLLECTION_UNLINK(mNextBinding)
148 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDefaultInsertionPoint)
149 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInsertionPoints)
150 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContentList)
151 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
152 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXBLBinding)
153 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
154 "mPrototypeBinding->XBLDocumentInfo()");
155 cb.NoteXPCOMChild(tmp->mPrototypeBinding->XBLDocumentInfo());
156 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent)
157 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNextBinding)
158 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDefaultInsertionPoint)
159 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInsertionPoints)
160 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContentList)
161 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
162 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsXBLBinding, AddRef)
163 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsXBLBinding, Release)
165 void
166 nsXBLBinding::SetBaseBinding(nsXBLBinding* aBinding)
168 if (mNextBinding) {
169 NS_ERROR("Base XBL binding is already defined!");
170 return;
173 mNextBinding = aBinding; // Comptr handles rel/add
176 nsXBLBinding*
177 nsXBLBinding::GetBindingWithContent()
179 if (mContent) {
180 return this;
183 return mNextBinding ? mNextBinding->GetBindingWithContent() : nullptr;
186 void
187 nsXBLBinding::InstallAnonymousContent(nsIContent* aAnonParent, nsIContent* aElement,
188 bool aChromeOnlyContent)
190 // We need to ensure two things.
191 // (1) The anonymous content should be fooled into thinking it's in the bound
192 // element's document, assuming that the bound element is in a document
193 // Note that we don't change the current doc of aAnonParent here, since that
194 // quite simply does not matter. aAnonParent is just a way of keeping refs
195 // to all its kids, which are anonymous content from the point of view of
196 // aElement.
197 // (2) The children's parent back pointer should not be to this synthetic root
198 // but should instead point to the enclosing parent element.
199 nsIDocument* doc = aElement->GetCurrentDoc();
200 bool allowScripts = AllowScripts();
202 nsAutoScriptBlocker scriptBlocker;
203 for (nsIContent* child = aAnonParent->GetFirstChild();
204 child;
205 child = child->GetNextSibling()) {
206 child->UnbindFromTree();
207 if (aChromeOnlyContent) {
208 child->SetFlags(NODE_CHROME_ONLY_ACCESS |
209 NODE_IS_ROOT_OF_CHROME_ONLY_ACCESS);
211 nsresult rv =
212 child->BindToTree(doc, aElement, mBoundElement, allowScripts);
213 if (NS_FAILED(rv)) {
214 // Oh, well... Just give up.
215 // XXXbz This really shouldn't be a void method!
216 child->UnbindFromTree();
217 return;
220 child->SetFlags(NODE_IS_ANONYMOUS_ROOT);
222 #ifdef MOZ_XUL
223 // To make XUL templates work (and other goodies that happen when
224 // an element is added to a XUL document), we need to notify the
225 // XUL document using its special API.
226 nsCOMPtr<nsIXULDocument> xuldoc(do_QueryInterface(doc));
227 if (xuldoc)
228 xuldoc->AddSubtreeToDocument(child);
229 #endif
233 void
234 nsXBLBinding::UninstallAnonymousContent(nsIDocument* aDocument,
235 nsIContent* aAnonParent)
237 if (aAnonParent->HasFlag(NODE_IS_IN_SHADOW_TREE)) {
238 // It is unnecessary to uninstall anonymous content in a shadow tree
239 // because the ShadowRoot itself is a DocumentFragment and does not
240 // need any additional cleanup.
241 return;
244 nsAutoScriptBlocker scriptBlocker;
245 // Hold a strong ref while doing this, just in case.
246 nsCOMPtr<nsIContent> anonParent = aAnonParent;
247 #ifdef MOZ_XUL
248 nsCOMPtr<nsIXULDocument> xuldoc =
249 do_QueryInterface(aDocument);
250 #endif
251 for (nsIContent* child = aAnonParent->GetFirstChild();
252 child;
253 child = child->GetNextSibling()) {
254 child->UnbindFromTree();
255 #ifdef MOZ_XUL
256 if (xuldoc) {
257 xuldoc->RemoveSubtreeFromDocument(child);
259 #endif
263 void
264 nsXBLBinding::SetBoundElement(nsIContent* aElement)
266 mBoundElement = aElement;
267 if (mNextBinding)
268 mNextBinding->SetBoundElement(aElement);
270 if (!mBoundElement) {
271 return;
274 // Compute whether we're using an XBL scope.
276 // We disable XBL scopes for remote XUL, where we care about compat more
277 // than security. So we need to know whether we're using an XBL scope so that
278 // we can decide what to do about untrusted events when "allowuntrusted"
279 // is not given in the handler declaration.
280 nsCOMPtr<nsIGlobalObject> go = mBoundElement->OwnerDoc()->GetScopeObject();
281 NS_ENSURE_TRUE_VOID(go && go->GetGlobalJSObject());
282 mUsingContentXBLScope = xpc::UseContentXBLScope(js::GetObjectCompartment(go->GetGlobalJSObject()));
285 bool
286 nsXBLBinding::HasStyleSheets() const
288 // Find out if we need to re-resolve style. We'll need to do this
289 // if we have additional stylesheets in our binding document.
290 if (mPrototypeBinding->HasStyleSheets())
291 return true;
293 return mNextBinding ? mNextBinding->HasStyleSheets() : false;
296 void
297 nsXBLBinding::GenerateAnonymousContent()
299 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
300 "Someone forgot a script blocker");
302 // Fetch the content element for this binding.
303 nsIContent* content =
304 mPrototypeBinding->GetImmediateChild(nsGkAtoms::content);
306 if (!content) {
307 // We have no anonymous content.
308 if (mNextBinding)
309 mNextBinding->GenerateAnonymousContent();
311 return;
314 // Find out if we're really building kids or if we're just
315 // using the attribute-setting shorthand hack.
316 uint32_t contentCount = content->GetChildCount();
318 // Plan to build the content by default.
319 bool hasContent = (contentCount > 0);
320 if (hasContent) {
321 nsIDocument* doc = mBoundElement->OwnerDoc();
323 nsCOMPtr<nsINode> clonedNode;
324 nsCOMArray<nsINode> nodesWithProperties;
325 nsNodeUtils::Clone(content, true, doc->NodeInfoManager(),
326 nodesWithProperties, getter_AddRefs(clonedNode));
327 mContent = clonedNode->AsElement();
329 // Search for <xbl:children> elements in the XBL content. In the presence
330 // of multiple default insertion points, we use the last one in document
331 // order.
332 for (nsIContent* child = mContent; child; child = child->GetNextNode(mContent)) {
333 if (child->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
334 XBLChildrenElement* point = static_cast<XBLChildrenElement*>(child);
335 if (point->IsDefaultInsertion()) {
336 mDefaultInsertionPoint = point;
337 } else {
338 mInsertionPoints.AppendElement(point);
343 // Do this after looking for <children> as this messes up the parent
344 // pointer which would make the GetNextNode call above fail
345 InstallAnonymousContent(mContent, mBoundElement,
346 mPrototypeBinding->ChromeOnlyContent());
348 // Insert explicit children into insertion points
349 if (mDefaultInsertionPoint && mInsertionPoints.IsEmpty()) {
350 ExplicitChildIterator iter(mBoundElement);
351 for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
352 mDefaultInsertionPoint->AppendInsertedChild(child);
354 } else {
355 // It is odd to come into this code if mInsertionPoints is not empty, but
356 // we need to make sure to do the compatibility hack below if the bound
357 // node has any non <xul:template> or <xul:observes> children.
358 ExplicitChildIterator iter(mBoundElement);
359 for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
360 XBLChildrenElement* point = FindInsertionPointForInternal(child);
361 if (point) {
362 point->AppendInsertedChild(child);
363 } else {
364 NodeInfo *ni = child->NodeInfo();
365 if (ni->NamespaceID() != kNameSpaceID_XUL ||
366 (!ni->Equals(nsGkAtoms::_template) &&
367 !ni->Equals(nsGkAtoms::observes))) {
368 // Compatibility hack. For some reason the original XBL
369 // implementation dropped the content of a binding if any child of
370 // the bound element didn't match any of the <children> in the
371 // binding. This became a pseudo-API that we have to maintain.
373 // Undo InstallAnonymousContent
374 UninstallAnonymousContent(doc, mContent);
376 // Clear out our children elements to avoid dangling references.
377 ClearInsertionPoints();
379 // Pretend as though there was no content in the binding.
380 mContent = nullptr;
381 return;
387 // Set binding parent on default content if need
388 if (mDefaultInsertionPoint) {
389 mDefaultInsertionPoint->MaybeSetupDefaultContent();
391 for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) {
392 mInsertionPoints[i]->MaybeSetupDefaultContent();
395 mPrototypeBinding->SetInitialAttributes(mBoundElement, mContent);
398 // Always check the content element for potential attributes.
399 // This shorthand hack always happens, even when we didn't
400 // build anonymous content.
401 const nsAttrName* attrName;
402 for (uint32_t i = 0; (attrName = content->GetAttrNameAt(i)); ++i) {
403 int32_t namespaceID = attrName->NamespaceID();
404 // Hold a strong reference here so that the atom doesn't go away during
405 // UnsetAttr.
406 nsCOMPtr<nsIAtom> name = attrName->LocalName();
408 if (name != nsGkAtoms::includes) {
409 if (!nsContentUtils::HasNonEmptyAttr(mBoundElement, namespaceID, name)) {
410 nsAutoString value2;
411 content->GetAttr(namespaceID, name, value2);
412 mBoundElement->SetAttr(namespaceID, name, attrName->GetPrefix(),
413 value2, false);
417 // Conserve space by wiping the attributes off the clone.
418 if (mContent)
419 mContent->UnsetAttr(namespaceID, name, false);
423 XBLChildrenElement*
424 nsXBLBinding::FindInsertionPointFor(nsIContent* aChild)
426 // XXX We should get rid of this function as it causes us to traverse the
427 // binding chain multiple times
428 if (mContent) {
429 return FindInsertionPointForInternal(aChild);
432 return mNextBinding ? mNextBinding->FindInsertionPointFor(aChild)
433 : nullptr;
436 XBLChildrenElement*
437 nsXBLBinding::FindInsertionPointForInternal(nsIContent* aChild)
439 for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) {
440 XBLChildrenElement* point = mInsertionPoints[i];
441 if (point->Includes(aChild)) {
442 return point;
446 return mDefaultInsertionPoint;
449 void
450 nsXBLBinding::ClearInsertionPoints()
452 if (mDefaultInsertionPoint) {
453 mDefaultInsertionPoint->ClearInsertedChildren();
456 for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) {
457 mInsertionPoints[i]->ClearInsertedChildren();
461 nsAnonymousContentList*
462 nsXBLBinding::GetAnonymousNodeList()
464 if (!mContent) {
465 return mNextBinding ? mNextBinding->GetAnonymousNodeList() : nullptr;
468 if (!mAnonymousContentList) {
469 mAnonymousContentList = new nsAnonymousContentList(mContent);
472 return mAnonymousContentList;
475 void
476 nsXBLBinding::InstallEventHandlers()
478 // Don't install handlers if scripts aren't allowed.
479 if (AllowScripts()) {
480 // Fetch the handlers prototypes for this binding.
481 nsXBLPrototypeHandler* handlerChain = mPrototypeBinding->GetPrototypeHandlers();
483 if (handlerChain) {
484 EventListenerManager* manager = mBoundElement->GetOrCreateListenerManager();
485 if (!manager)
486 return;
488 bool isChromeDoc =
489 nsContentUtils::IsChromeDoc(mBoundElement->OwnerDoc());
490 bool isChromeBinding = mPrototypeBinding->IsChrome();
491 nsXBLPrototypeHandler* curr;
492 for (curr = handlerChain; curr; curr = curr->GetNextHandler()) {
493 // Fetch the event type.
494 nsCOMPtr<nsIAtom> eventAtom = curr->GetEventName();
495 if (!eventAtom ||
496 eventAtom == nsGkAtoms::keyup ||
497 eventAtom == nsGkAtoms::keydown ||
498 eventAtom == nsGkAtoms::keypress)
499 continue;
501 nsXBLEventHandler* handler = curr->GetEventHandler();
502 if (handler) {
503 // Figure out if we're using capturing or not.
504 EventListenerFlags flags;
505 flags.mCapture = (curr->GetPhase() == NS_PHASE_CAPTURING);
507 // If this is a command, add it in the system event group
508 if ((curr->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND |
509 NS_HANDLER_TYPE_SYSTEM)) &&
510 (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
511 flags.mInSystemGroup = true;
514 bool hasAllowUntrustedAttr = curr->HasAllowUntrustedAttr();
515 if ((hasAllowUntrustedAttr && curr->AllowUntrustedEvents()) ||
516 (!hasAllowUntrustedAttr && !isChromeDoc && !mUsingContentXBLScope)) {
517 flags.mAllowUntrustedEvents = true;
520 manager->AddEventListenerByType(handler,
521 nsDependentAtomString(eventAtom),
522 flags);
526 const nsCOMArray<nsXBLKeyEventHandler>* keyHandlers =
527 mPrototypeBinding->GetKeyEventHandlers();
528 int32_t i;
529 for (i = 0; i < keyHandlers->Count(); ++i) {
530 nsXBLKeyEventHandler* handler = keyHandlers->ObjectAt(i);
531 handler->SetIsBoundToChrome(isChromeDoc);
532 handler->SetUsingContentXBLScope(mUsingContentXBLScope);
534 nsAutoString type;
535 handler->GetEventName(type);
537 // If this is a command, add it in the system event group, otherwise
538 // add it to the standard event group.
540 // Figure out if we're using capturing or not.
541 EventListenerFlags flags;
542 flags.mCapture = (handler->GetPhase() == NS_PHASE_CAPTURING);
544 if ((handler->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND |
545 NS_HANDLER_TYPE_SYSTEM)) &&
546 (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
547 flags.mInSystemGroup = true;
550 // For key handlers we have to set mAllowUntrustedEvents flag.
551 // Whether the handling of the event is allowed or not is handled in
552 // nsXBLKeyEventHandler::HandleEvent
553 flags.mAllowUntrustedEvents = true;
555 manager->AddEventListenerByType(handler, type, flags);
560 if (mNextBinding)
561 mNextBinding->InstallEventHandlers();
564 nsresult
565 nsXBLBinding::InstallImplementation()
567 // Always install the base class properties first, so that
568 // derived classes can reference the base class properties.
570 if (mNextBinding) {
571 nsresult rv = mNextBinding->InstallImplementation();
572 NS_ENSURE_SUCCESS(rv, rv);
575 // iterate through each property in the prototype's list and install the property.
576 if (AllowScripts())
577 return mPrototypeBinding->InstallImplementation(this);
579 return NS_OK;
582 nsIAtom*
583 nsXBLBinding::GetBaseTag(int32_t* aNameSpaceID)
585 nsIAtom *tag = mPrototypeBinding->GetBaseTag(aNameSpaceID);
586 if (!tag && mNextBinding)
587 return mNextBinding->GetBaseTag(aNameSpaceID);
589 return tag;
592 void
593 nsXBLBinding::AttributeChanged(nsIAtom* aAttribute, int32_t aNameSpaceID,
594 bool aRemoveFlag, bool aNotify)
596 // XXX Change if we ever allow multiple bindings in a chain to contribute anonymous content
597 if (!mContent) {
598 if (mNextBinding)
599 mNextBinding->AttributeChanged(aAttribute, aNameSpaceID,
600 aRemoveFlag, aNotify);
601 } else {
602 mPrototypeBinding->AttributeChanged(aAttribute, aNameSpaceID, aRemoveFlag,
603 mBoundElement, mContent, aNotify);
607 void
608 nsXBLBinding::ExecuteAttachedHandler()
610 if (mNextBinding)
611 mNextBinding->ExecuteAttachedHandler();
613 if (AllowScripts())
614 mPrototypeBinding->BindingAttached(mBoundElement);
617 void
618 nsXBLBinding::ExecuteDetachedHandler()
620 if (AllowScripts())
621 mPrototypeBinding->BindingDetached(mBoundElement);
623 if (mNextBinding)
624 mNextBinding->ExecuteDetachedHandler();
627 void
628 nsXBLBinding::UnhookEventHandlers()
630 nsXBLPrototypeHandler* handlerChain = mPrototypeBinding->GetPrototypeHandlers();
632 if (handlerChain) {
633 EventListenerManager* manager = mBoundElement->GetExistingListenerManager();
634 if (!manager) {
635 return;
638 bool isChromeBinding = mPrototypeBinding->IsChrome();
639 nsXBLPrototypeHandler* curr;
640 for (curr = handlerChain; curr; curr = curr->GetNextHandler()) {
641 nsXBLEventHandler* handler = curr->GetCachedEventHandler();
642 if (!handler) {
643 continue;
646 nsCOMPtr<nsIAtom> eventAtom = curr->GetEventName();
647 if (!eventAtom ||
648 eventAtom == nsGkAtoms::keyup ||
649 eventAtom == nsGkAtoms::keydown ||
650 eventAtom == nsGkAtoms::keypress)
651 continue;
653 // Figure out if we're using capturing or not.
654 EventListenerFlags flags;
655 flags.mCapture = (curr->GetPhase() == NS_PHASE_CAPTURING);
657 // If this is a command, remove it from the system event group,
658 // otherwise remove it from the standard event group.
660 if ((curr->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND |
661 NS_HANDLER_TYPE_SYSTEM)) &&
662 (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
663 flags.mInSystemGroup = true;
666 manager->RemoveEventListenerByType(handler,
667 nsDependentAtomString(eventAtom),
668 flags);
671 const nsCOMArray<nsXBLKeyEventHandler>* keyHandlers =
672 mPrototypeBinding->GetKeyEventHandlers();
673 int32_t i;
674 for (i = 0; i < keyHandlers->Count(); ++i) {
675 nsXBLKeyEventHandler* handler = keyHandlers->ObjectAt(i);
677 nsAutoString type;
678 handler->GetEventName(type);
680 // Figure out if we're using capturing or not.
681 EventListenerFlags flags;
682 flags.mCapture = (handler->GetPhase() == NS_PHASE_CAPTURING);
684 // If this is a command, remove it from the system event group, otherwise
685 // remove it from the standard event group.
687 if ((handler->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND | NS_HANDLER_TYPE_SYSTEM)) &&
688 (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
689 flags.mInSystemGroup = true;
692 manager->RemoveEventListenerByType(handler, type, flags);
697 static void
698 UpdateInsertionParent(XBLChildrenElement* aPoint,
699 nsIContent* aOldBoundElement)
701 if (aPoint->IsDefaultInsertion()) {
702 return;
705 for (size_t i = 0; i < aPoint->InsertedChildrenLength(); ++i) {
706 nsIContent* child = aPoint->InsertedChild(i);
708 MOZ_ASSERT(child->GetParentNode());
710 // Here, we're iterating children that we inserted. There are two cases:
711 // either |child| is an explicit child of |aOldBoundElement| and is no
712 // longer inserted anywhere or it's a child of a <children> element
713 // parented to |aOldBoundElement|. In the former case, the child is no
714 // longer inserted anywhere, so we set its insertion parent to null. In the
715 // latter case, the child is now inserted into |aOldBoundElement| from some
716 // binding above us, so we set its insertion parent to aOldBoundElement.
717 if (child->GetParentNode() == aOldBoundElement) {
718 child->SetXBLInsertionParent(nullptr);
719 } else {
720 child->SetXBLInsertionParent(aOldBoundElement);
725 void
726 nsXBLBinding::ChangeDocument(nsIDocument* aOldDocument, nsIDocument* aNewDocument)
728 if (aOldDocument == aNewDocument)
729 return;
731 // Now the binding dies. Unhook our prototypes.
732 if (mPrototypeBinding->HasImplementation()) {
733 AutoJSAPI jsapi;
734 // Init might fail here if we've cycle-collected the global object, since
735 // the Unlink phase of cycle collection happens after JS GC finalization.
736 // But in that case, we don't care about fixing the prototype chain, since
737 // everything's going away immediately.
738 if (jsapi.Init(aOldDocument->GetScopeObject())) {
739 JSContext* cx = jsapi.cx();
741 JS::Rooted<JSObject*> scriptObject(cx, mBoundElement->GetWrapper());
742 if (scriptObject) {
743 // XXX Stay in sync! What if a layered binding has an
744 // <interface>?!
745 // XXXbz what does that comment mean, really? It seems to date
746 // back to when there was such a thing as an <interface>, whever
747 // that was...
749 // Find the right prototype.
750 JSAutoCompartment ac(cx, scriptObject);
752 JS::Rooted<JSObject*> base(cx, scriptObject);
753 JS::Rooted<JSObject*> proto(cx);
754 for ( ; true; base = proto) { // Will break out on null proto
755 if (!JS_GetPrototype(cx, base, &proto)) {
756 return;
758 if (!proto) {
759 break;
762 if (JS_GetClass(proto) != &gPrototypeJSClass) {
763 // Clearly not the right class
764 continue;
767 nsRefPtr<nsXBLDocumentInfo> docInfo =
768 static_cast<nsXBLDocumentInfo*>(::JS_GetPrivate(proto));
769 if (!docInfo) {
770 // Not the proto we seek
771 continue;
774 JS::Value protoBinding = ::JS_GetReservedSlot(proto, 0);
776 if (protoBinding.toPrivate() != mPrototypeBinding) {
777 // Not the right binding
778 continue;
781 // Alright! This is the right prototype. Pull it out of the
782 // proto chain.
783 JS::Rooted<JSObject*> grandProto(cx);
784 if (!JS_GetPrototype(cx, proto, &grandProto)) {
785 return;
787 ::JS_SetPrototype(cx, base, grandProto);
788 break;
791 mPrototypeBinding->UndefineFields(cx, scriptObject);
793 // Don't remove the reference from the document to the
794 // wrapper here since it'll be removed by the element
795 // itself when that's taken out of the document.
800 // Remove our event handlers
801 UnhookEventHandlers();
804 nsAutoScriptBlocker scriptBlocker;
806 // Then do our ancestors. This reverses the construction order, so that at
807 // all times things are consistent as far as everyone is concerned.
808 if (mNextBinding) {
809 mNextBinding->ChangeDocument(aOldDocument, aNewDocument);
812 // Update the anonymous content.
813 // XXXbz why not only for style bindings?
814 if (mContent) {
815 nsXBLBinding::UninstallAnonymousContent(aOldDocument, mContent);
818 // Now that we've unbound our anonymous content from the tree and updated
819 // its binding parent, update the insertion parent for content inserted
820 // into our <children> elements.
821 if (mDefaultInsertionPoint) {
822 UpdateInsertionParent(mDefaultInsertionPoint, mBoundElement);
825 for (size_t i = 0; i < mInsertionPoints.Length(); ++i) {
826 UpdateInsertionParent(mInsertionPoints[i], mBoundElement);
829 // Now that our inserted children no longer think they're inserted
830 // anywhere, make sure our internal state reflects that as well.
831 ClearInsertionPoints();
835 bool
836 nsXBLBinding::InheritsStyle() const
838 // XXX Will have to change if we ever allow multiple bindings to contribute anonymous content.
839 // Most derived binding with anonymous content determines style inheritance for now.
841 // XXX What about bindings with <content> but no kids, e.g., my treecell-text binding?
842 if (mContent)
843 return mPrototypeBinding->InheritsStyle();
845 if (mNextBinding)
846 return mNextBinding->InheritsStyle();
848 return true;
851 void
852 nsXBLBinding::WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc, void* aData)
854 if (mNextBinding)
855 mNextBinding->WalkRules(aFunc, aData);
857 nsIStyleRuleProcessor *rules = mPrototypeBinding->GetRuleProcessor();
858 if (rules)
859 (*aFunc)(rules, aData);
862 // Internal helper methods ////////////////////////////////////////////////////////////////
864 // Get or create a WeakMap object on a given XBL-hosting global.
866 // The scheme is as follows. XBL-hosting globals (either privileged content
867 // Windows or XBL scopes) get two lazily-defined WeakMap properties. Each
868 // WeakMap is keyed by the grand-proto - i.e. the original prototype of the
869 // content before it was bound, and the prototype of the class object that we
870 // splice in. The values in the WeakMap are simple dictionary-style objects,
871 // mapping from XBL class names to class objects.
872 static JSObject*
873 GetOrCreateClassObjectMap(JSContext *cx, JS::Handle<JSObject*> scope, const char *mapName)
875 AssertSameCompartment(cx, scope);
876 MOZ_ASSERT(JS_IsGlobalObject(scope));
877 MOZ_ASSERT(scope == xpc::GetXBLScopeOrGlobal(cx, scope));
879 // First, see if the map is already defined.
880 JS::Rooted<JSPropertyDescriptor> desc(cx);
881 if (!JS_GetOwnPropertyDescriptor(cx, scope, mapName, &desc)) {
882 return nullptr;
884 if (desc.object() && desc.value().isObject() &&
885 JS::IsWeakMapObject(&desc.value().toObject())) {
886 return &desc.value().toObject();
889 // It's not there. Create and define it.
890 JS::Rooted<JSObject*> map(cx, JS::NewWeakMapObject(cx));
891 if (!map || !JS_DefineProperty(cx, scope, mapName, map,
892 JSPROP_PERMANENT | JSPROP_READONLY,
893 JS_PropertyStub, JS_StrictPropertyStub))
895 return nullptr;
897 return map;
900 static JSObject*
901 GetOrCreateMapEntryForPrototype(JSContext *cx, JS::Handle<JSObject*> proto)
903 AssertSameCompartment(cx, proto);
904 // We want to hang our class objects off the XBL scope. But since we also
905 // hoist anonymous content into the XBL scope, this creates the potential for
906 // tricky collisions, since we can simultaneously have a bound in-content
907 // node with grand-proto HTMLDivElement and a bound anonymous node whose
908 // grand-proto is the XBL scope's cross-compartment wrapper to HTMLDivElement.
909 // Since we have to wrap the WeakMap keys into its scope, this distinction
910 // would be lost if we don't do something about it.
912 // So we define two maps - one class objects that live in content (prototyped
913 // to content prototypes), and the other for class objects that live in the
914 // XBL scope (prototyped to cross-compartment-wrapped content prototypes).
915 const char* name = xpc::IsInContentXBLScope(proto) ? "__ContentClassObjectMap__"
916 : "__XBLClassObjectMap__";
918 // Now, enter the XBL scope, since that's where we need to operate, and wrap
919 // the proto accordingly. We hang the map off of the content XBL scope for
920 // content, and the Window for chrome (whether add-ons are involved or not).
921 JS::Rooted<JSObject*> scope(cx, xpc::GetXBLScopeOrGlobal(cx, proto));
922 NS_ENSURE_TRUE(scope, nullptr);
923 JS::Rooted<JSObject*> wrappedProto(cx, proto);
924 JSAutoCompartment ac(cx, scope);
925 if (!JS_WrapObject(cx, &wrappedProto)) {
926 return nullptr;
929 // Grab the appropriate WeakMap.
930 JS::Rooted<JSObject*> map(cx, GetOrCreateClassObjectMap(cx, scope, name));
931 if (!map) {
932 return nullptr;
935 // See if we already have a map entry for that prototype.
936 JS::Rooted<JS::Value> val(cx);
937 if (!JS::GetWeakMapEntry(cx, map, wrappedProto, &val)) {
938 return nullptr;
940 if (val.isObject()) {
941 return &val.toObject();
944 // We don't have an entry. Create one and stick it in the map.
945 JS::Rooted<JSObject*> entry(cx);
946 entry = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), scope);
947 if (!entry) {
948 return nullptr;
950 JS::Rooted<JS::Value> entryVal(cx, JS::ObjectValue(*entry));
951 if (!JS::SetWeakMapEntry(cx, map, wrappedProto, entryVal)) {
952 NS_WARNING("SetWeakMapEntry failed, probably due to non-preservable WeakMap "
953 "key. XBL binding will fail for this element.");
954 return nullptr;
956 return entry;
959 // static
960 nsresult
961 nsXBLBinding::DoInitJSClass(JSContext *cx,
962 JS::Handle<JSObject*> obj,
963 const nsAFlatCString& aClassName,
964 nsXBLPrototypeBinding* aProtoBinding,
965 JS::MutableHandle<JSObject*> aClassObject,
966 bool* aNew)
968 MOZ_ASSERT(obj);
970 // Note that, now that NAC reflectors are created in the XBL scope, the
971 // reflector is not necessarily same-compartment with the document. So we'll
972 // end up creating a separate instance of the oddly-named XBL class object
973 // and defining it as a property on the XBL scope's global. This works fine,
974 // but we need to make sure never to assume that the the reflector and
975 // prototype are same-compartment with the bound document.
976 JS::Rooted<JSObject*> global(cx, js::GetGlobalForObjectCrossCompartment(obj));
978 // We never store class objects in add-on scopes.
979 JS::Rooted<JSObject*> xblScope(cx, xpc::GetXBLScopeOrGlobal(cx, global));
980 NS_ENSURE_TRUE(xblScope, NS_ERROR_UNEXPECTED);
982 JS::Rooted<JSObject*> parent_proto(cx);
983 if (!JS_GetPrototype(cx, obj, &parent_proto)) {
984 return NS_ERROR_FAILURE;
987 // Get the map entry for the parent prototype. In the one-off case that the
988 // parent prototype is null, we somewhat hackily just use the WeakMap itself
989 // as a property holder.
990 JS::Rooted<JSObject*> holder(cx);
991 if (parent_proto) {
992 holder = GetOrCreateMapEntryForPrototype(cx, parent_proto);
993 } else {
994 JSAutoCompartment innerAC(cx, xblScope);
995 holder = GetOrCreateClassObjectMap(cx, xblScope, "__ContentClassObjectMap__");
997 if (NS_WARN_IF(!holder)) {
998 return NS_ERROR_FAILURE;
1000 js::AssertSameCompartment(holder, xblScope);
1001 JSAutoCompartment ac(cx, holder);
1003 // Look up the class on the property holder. The only properties on the
1004 // holder should be class objects. If we don't find the class object, we need
1005 // to create and define it.
1006 JS::Rooted<JSObject*> proto(cx);
1007 JS::Rooted<JSPropertyDescriptor> desc(cx);
1008 if (!JS_GetOwnPropertyDescriptor(cx, holder, aClassName.get(), &desc)) {
1009 return NS_ERROR_OUT_OF_MEMORY;
1011 *aNew = !desc.object();
1012 if (desc.object()) {
1013 proto = &desc.value().toObject();
1014 MOZ_ASSERT(JS_GetClass(js::UncheckedUnwrap(proto)) == &gPrototypeJSClass);
1015 } else {
1017 // We need to create the prototype. First, enter the compartment where it's
1018 // going to live, and create it.
1019 JSAutoCompartment ac2(cx, global);
1020 proto = JS_NewObjectWithGivenProto(cx, &gPrototypeJSClass, parent_proto, global);
1021 if (!proto) {
1022 return NS_ERROR_OUT_OF_MEMORY;
1025 // Keep this proto binding alive while we're alive. Do this first so that
1026 // we can guarantee that in XBLFinalize this will be non-null.
1027 // Note that we can't just store aProtoBinding in the private and
1028 // addref/release the nsXBLDocumentInfo through it, because cycle
1029 // collection doesn't seem to work right if the private is not an
1030 // nsISupports.
1031 nsXBLDocumentInfo* docInfo = aProtoBinding->XBLDocumentInfo();
1032 ::JS_SetPrivate(proto, docInfo);
1033 NS_ADDREF(docInfo);
1034 JS_SetReservedSlot(proto, 0, PRIVATE_TO_JSVAL(aProtoBinding));
1036 // Next, enter the compartment of the property holder, wrap the proto, and
1037 // stick it on.
1038 JSAutoCompartment ac3(cx, holder);
1039 if (!JS_WrapObject(cx, &proto) ||
1040 !JS_DefineProperty(cx, holder, aClassName.get(), proto,
1041 JSPROP_READONLY | JSPROP_PERMANENT,
1042 JS_PropertyStub, JS_StrictPropertyStub))
1044 return NS_ERROR_OUT_OF_MEMORY;
1048 // Whew. We have the proto. Wrap it back into the compartment of |obj|,
1049 // splice it in, and return it.
1050 JSAutoCompartment ac4(cx, obj);
1051 if (!JS_WrapObject(cx, &proto) || !JS_SetPrototype(cx, obj, proto)) {
1052 return NS_ERROR_FAILURE;
1054 aClassObject.set(proto);
1055 return NS_OK;
1058 bool
1059 nsXBLBinding::AllowScripts()
1061 return mBoundElement && mPrototypeBinding->GetAllowScripts();
1064 nsXBLBinding*
1065 nsXBLBinding::RootBinding()
1067 if (mNextBinding)
1068 return mNextBinding->RootBinding();
1070 return this;
1073 bool
1074 nsXBLBinding::ResolveAllFields(JSContext *cx, JS::Handle<JSObject*> obj) const
1076 if (!mPrototypeBinding->ResolveAllFields(cx, obj)) {
1077 return false;
1080 if (mNextBinding) {
1081 return mNextBinding->ResolveAllFields(cx, obj);
1084 return true;
1087 bool
1088 nsXBLBinding::LookupMember(JSContext* aCx, JS::Handle<jsid> aId,
1089 JS::MutableHandle<JSPropertyDescriptor> aDesc)
1091 // We should never enter this function with a pre-filled property descriptor.
1092 MOZ_ASSERT(!aDesc.object());
1094 // Get the string as an nsString before doing anything, so we can make
1095 // convenient comparisons during our search.
1096 if (!JSID_IS_STRING(aId)) {
1097 return true;
1099 nsAutoJSString name;
1100 if (!name.init(aCx, JSID_TO_STRING(aId))) {
1101 return false;
1104 // We have a weak reference to our bound element, so make sure it's alive.
1105 if (!mBoundElement || !mBoundElement->GetWrapper()) {
1106 return false;
1109 // Get the scope of mBoundElement and the associated XBL scope. We should only
1110 // be calling into this machinery if we're running in a separate XBL scope.
1112 // Note that we only end up in LookupMember for XrayWrappers from XBL scopes
1113 // into content. So for NAC reflectors that live in the XBL scope, we should
1114 // never get here. But on the off-chance that someone adds new callsites to
1115 // LookupMember, we do a release-mode assertion as belt-and-braces.
1116 // We do a release-mode assertion here to be extra safe.
1118 // This code is only called for content XBL, so we don't have to worry about
1119 // add-on scopes here.
1120 JS::Rooted<JSObject*> boundScope(aCx,
1121 js::GetGlobalForObjectCrossCompartment(mBoundElement->GetWrapper()));
1122 MOZ_RELEASE_ASSERT(!xpc::IsInAddonScope(boundScope));
1123 MOZ_RELEASE_ASSERT(!xpc::IsInContentXBLScope(boundScope));
1124 JS::Rooted<JSObject*> xblScope(aCx, xpc::GetXBLScope(aCx, boundScope));
1125 NS_ENSURE_TRUE(xblScope, false);
1126 MOZ_ASSERT(boundScope != xblScope);
1128 // Enter the xbl scope and invoke the internal version.
1130 JSAutoCompartment ac(aCx, xblScope);
1131 JS::Rooted<jsid> id(aCx, aId);
1132 if (!LookupMemberInternal(aCx, name, id, aDesc, xblScope)) {
1133 return false;
1137 // Wrap into the caller's scope.
1138 return JS_WrapPropertyDescriptor(aCx, aDesc);
1141 bool
1142 nsXBLBinding::LookupMemberInternal(JSContext* aCx, nsString& aName,
1143 JS::Handle<jsid> aNameAsId,
1144 JS::MutableHandle<JSPropertyDescriptor> aDesc,
1145 JS::Handle<JSObject*> aXBLScope)
1147 // First, see if we have an implementation. If we don't, it means that this
1148 // binding doesn't have a class object, and thus doesn't have any members.
1149 // Skip it.
1150 if (!PrototypeBinding()->HasImplementation()) {
1151 if (!mNextBinding) {
1152 return true;
1154 return mNextBinding->LookupMemberInternal(aCx, aName, aNameAsId,
1155 aDesc, aXBLScope);
1158 // Find our class object. It's in a protected scope and permanent just in case,
1159 // so should be there no matter what.
1160 JS::Rooted<JS::Value> classObject(aCx);
1161 if (!JS_GetProperty(aCx, aXBLScope, PrototypeBinding()->ClassName().get(),
1162 &classObject)) {
1163 return false;
1166 // The bound element may have been adoped by a document and have a different
1167 // wrapper (and different xbl scope) than when the binding was applied, in
1168 // this case getting the class object will fail. Behave as if the class
1169 // object did not exist.
1170 if (classObject.isUndefined()) {
1171 return true;
1174 MOZ_ASSERT(classObject.isObject());
1176 // Look for the property on this binding. If it's not there, try the next
1177 // binding on the chain.
1178 nsXBLProtoImpl* impl = mPrototypeBinding->GetImplementation();
1179 JS::Rooted<JSObject*> object(aCx, &classObject.toObject());
1180 if (impl && !impl->LookupMember(aCx, aName, aNameAsId, aDesc, object)) {
1181 return false;
1183 if (aDesc.object() || !mNextBinding) {
1184 return true;
1187 return mNextBinding->LookupMemberInternal(aCx, aName, aNameAsId, aDesc,
1188 aXBLScope);
1191 bool
1192 nsXBLBinding::HasField(nsString& aName)
1194 // See if this binding has such a field.
1195 return mPrototypeBinding->FindField(aName) ||
1196 (mNextBinding && mNextBinding->HasField(aName));
1199 void
1200 nsXBLBinding::MarkForDeath()
1202 mMarkedForDeath = true;
1203 ExecuteDetachedHandler();
1206 bool
1207 nsXBLBinding::ImplementsInterface(REFNSIID aIID) const
1209 return mPrototypeBinding->ImplementsInterface(aIID) ||
1210 (mNextBinding && mNextBinding->ImplementsInterface(aIID));