Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
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 #include "nsCxPusher.h"
24 #ifdef MOZ_XUL
25 #include "nsIXULDocument.h"
26 #endif
27 #include "nsIXMLContentSink.h"
28 #include "nsContentCID.h"
29 #include "mozilla/dom/XMLDocument.h"
30 #include "jsapi.h"
31 #include "nsXBLService.h"
32 #include "nsIXPConnect.h"
33 #include "nsIScriptContext.h"
34 #include "nsCRT.h"
36 // Event listeners
37 #include "mozilla/EventListenerManager.h"
38 #include "nsIDOMEventListener.h"
39 #include "nsAttrName.h"
41 #include "nsGkAtoms.h"
43 #include "nsXBLPrototypeHandler.h"
45 #include "nsXBLPrototypeBinding.h"
46 #include "nsXBLBinding.h"
47 #include "nsIPrincipal.h"
48 #include "nsIScriptSecurityManager.h"
49 #include "mozilla/dom/XBLChildrenElement.h"
51 #include "prprf.h"
52 #include "nsNodeUtils.h"
53 #include "nsJSUtils.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/ShadowRoot.h"
62 using namespace mozilla;
63 using namespace mozilla::dom;
65 // Helper classes
67 /***********************************************************************/
68 //
69 // The JS class for XBLBinding
70 //
71 static void
72 XBLFinalize(JSFreeOp *fop, JSObject *obj)
73 {
74 nsXBLDocumentInfo* docInfo =
75 static_cast<nsXBLDocumentInfo*>(::JS_GetPrivate(obj));
76 nsContentUtils::DeferredFinalize(docInfo);
77 }
79 static bool
80 XBLEnumerate(JSContext *cx, JS::Handle<JSObject*> obj)
81 {
82 nsXBLPrototypeBinding* protoBinding =
83 static_cast<nsXBLPrototypeBinding*>(::JS_GetReservedSlot(obj, 0).toPrivate());
84 MOZ_ASSERT(protoBinding);
86 return protoBinding->ResolveAllFields(cx, obj);
87 }
89 static const JSClass gPrototypeJSClass = {
90 "XBL prototype JSClass",
91 JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS |
92 JSCLASS_NEW_RESOLVE |
93 // Our one reserved slot holds the relevant nsXBLPrototypeBinding
94 JSCLASS_HAS_RESERVED_SLOTS(1),
95 JS_PropertyStub, JS_DeletePropertyStub,
96 JS_PropertyStub, JS_StrictPropertyStub,
97 XBLEnumerate, JS_ResolveStub,
98 JS_ConvertStub, XBLFinalize,
99 nullptr, nullptr, nullptr, nullptr
100 };
102 // Implementation /////////////////////////////////////////////////////////////////
104 // Constructors/Destructors
105 nsXBLBinding::nsXBLBinding(nsXBLPrototypeBinding* aBinding)
106 : mMarkedForDeath(false)
107 , mUsingXBLScope(false)
108 , mPrototypeBinding(aBinding)
109 {
110 NS_ASSERTION(mPrototypeBinding, "Must have a prototype binding!");
111 // Grab a ref to the document info so the prototype binding won't die
112 NS_ADDREF(mPrototypeBinding->XBLDocumentInfo());
113 }
115 // Constructor used by web components.
116 nsXBLBinding::nsXBLBinding(ShadowRoot* aShadowRoot, nsXBLPrototypeBinding* aBinding)
117 : mMarkedForDeath(false),
118 mUsingXBLScope(false),
119 mPrototypeBinding(aBinding),
120 mContent(aShadowRoot)
121 {
122 NS_ASSERTION(mPrototypeBinding, "Must have a prototype binding!");
123 // Grab a ref to the document info so the prototype binding won't die
124 NS_ADDREF(mPrototypeBinding->XBLDocumentInfo());
125 }
127 nsXBLBinding::~nsXBLBinding(void)
128 {
129 if (mContent) {
130 nsXBLBinding::UninstallAnonymousContent(mContent->OwnerDoc(), mContent);
131 }
132 nsXBLDocumentInfo* info = mPrototypeBinding->XBLDocumentInfo();
133 NS_RELEASE(info);
134 }
136 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXBLBinding)
138 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXBLBinding)
139 // XXX Probably can't unlink mPrototypeBinding->XBLDocumentInfo(), because
140 // mPrototypeBinding is weak.
141 if (tmp->mContent) {
142 nsXBLBinding::UninstallAnonymousContent(tmp->mContent->OwnerDoc(),
143 tmp->mContent);
144 }
145 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent)
146 NS_IMPL_CYCLE_COLLECTION_UNLINK(mNextBinding)
147 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDefaultInsertionPoint)
148 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInsertionPoints)
149 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContentList)
150 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
151 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXBLBinding)
152 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
153 "mPrototypeBinding->XBLDocumentInfo()");
154 cb.NoteXPCOMChild(tmp->mPrototypeBinding->XBLDocumentInfo());
155 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent)
156 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNextBinding)
157 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDefaultInsertionPoint)
158 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInsertionPoints)
159 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContentList)
160 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
161 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsXBLBinding, AddRef)
162 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsXBLBinding, Release)
164 void
165 nsXBLBinding::SetBaseBinding(nsXBLBinding* aBinding)
166 {
167 if (mNextBinding) {
168 NS_ERROR("Base XBL binding is already defined!");
169 return;
170 }
172 mNextBinding = aBinding; // Comptr handles rel/add
173 }
175 nsXBLBinding*
176 nsXBLBinding::GetBindingWithContent()
177 {
178 if (mContent) {
179 return this;
180 }
182 return mNextBinding ? mNextBinding->GetBindingWithContent() : nullptr;
183 }
185 void
186 nsXBLBinding::InstallAnonymousContent(nsIContent* aAnonParent, nsIContent* aElement,
187 bool aChromeOnlyContent)
188 {
189 // We need to ensure two things.
190 // (1) The anonymous content should be fooled into thinking it's in the bound
191 // element's document, assuming that the bound element is in a document
192 // Note that we don't change the current doc of aAnonParent here, since that
193 // quite simply does not matter. aAnonParent is just a way of keeping refs
194 // to all its kids, which are anonymous content from the point of view of
195 // aElement.
196 // (2) The children's parent back pointer should not be to this synthetic root
197 // but should instead point to the enclosing parent element.
198 nsIDocument* doc = aElement->GetCurrentDoc();
199 bool allowScripts = AllowScripts();
201 nsAutoScriptBlocker scriptBlocker;
202 for (nsIContent* child = aAnonParent->GetFirstChild();
203 child;
204 child = child->GetNextSibling()) {
205 child->UnbindFromTree();
206 if (aChromeOnlyContent) {
207 child->SetFlags(NODE_CHROME_ONLY_ACCESS |
208 NODE_IS_ROOT_OF_CHROME_ONLY_ACCESS);
209 }
210 nsresult rv =
211 child->BindToTree(doc, aElement, mBoundElement, allowScripts);
212 if (NS_FAILED(rv)) {
213 // Oh, well... Just give up.
214 // XXXbz This really shouldn't be a void method!
215 child->UnbindFromTree();
216 return;
217 }
219 child->SetFlags(NODE_IS_ANONYMOUS_ROOT);
221 #ifdef MOZ_XUL
222 // To make XUL templates work (and other goodies that happen when
223 // an element is added to a XUL document), we need to notify the
224 // XUL document using its special API.
225 nsCOMPtr<nsIXULDocument> xuldoc(do_QueryInterface(doc));
226 if (xuldoc)
227 xuldoc->AddSubtreeToDocument(child);
228 #endif
229 }
230 }
232 void
233 nsXBLBinding::UninstallAnonymousContent(nsIDocument* aDocument,
234 nsIContent* aAnonParent)
235 {
236 if (aAnonParent->HasFlag(NODE_IS_IN_SHADOW_TREE)) {
237 // It is unnecessary to uninstall anonymous content in a shadow tree
238 // because the ShadowRoot itself is a DocumentFragment and does not
239 // need any additional cleanup.
240 return;
241 }
243 nsAutoScriptBlocker scriptBlocker;
244 // Hold a strong ref while doing this, just in case.
245 nsCOMPtr<nsIContent> anonParent = aAnonParent;
246 #ifdef MOZ_XUL
247 nsCOMPtr<nsIXULDocument> xuldoc =
248 do_QueryInterface(aDocument);
249 #endif
250 for (nsIContent* child = aAnonParent->GetFirstChild();
251 child;
252 child = child->GetNextSibling()) {
253 child->UnbindFromTree();
254 #ifdef MOZ_XUL
255 if (xuldoc) {
256 xuldoc->RemoveSubtreeFromDocument(child);
257 }
258 #endif
259 }
260 }
262 void
263 nsXBLBinding::SetBoundElement(nsIContent* aElement)
264 {
265 mBoundElement = aElement;
266 if (mNextBinding)
267 mNextBinding->SetBoundElement(aElement);
269 if (!mBoundElement) {
270 return;
271 }
273 // Compute whether we're using an XBL scope.
274 //
275 // We disable XBL scopes for remote XUL, where we care about compat more
276 // than security. So we need to know whether we're using an XBL scope so that
277 // we can decide what to do about untrusted events when "allowuntrusted"
278 // is not given in the handler declaration.
279 nsCOMPtr<nsIGlobalObject> go = mBoundElement->OwnerDoc()->GetScopeObject();
280 NS_ENSURE_TRUE_VOID(go && go->GetGlobalJSObject());
281 mUsingXBLScope = xpc::UseXBLScope(js::GetObjectCompartment(go->GetGlobalJSObject()));
282 }
284 bool
285 nsXBLBinding::HasStyleSheets() const
286 {
287 // Find out if we need to re-resolve style. We'll need to do this
288 // if we have additional stylesheets in our binding document.
289 if (mPrototypeBinding->HasStyleSheets())
290 return true;
292 return mNextBinding ? mNextBinding->HasStyleSheets() : false;
293 }
295 void
296 nsXBLBinding::GenerateAnonymousContent()
297 {
298 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
299 "Someone forgot a script blocker");
301 // Fetch the content element for this binding.
302 nsIContent* content =
303 mPrototypeBinding->GetImmediateChild(nsGkAtoms::content);
305 if (!content) {
306 // We have no anonymous content.
307 if (mNextBinding)
308 mNextBinding->GenerateAnonymousContent();
310 return;
311 }
313 // Find out if we're really building kids or if we're just
314 // using the attribute-setting shorthand hack.
315 uint32_t contentCount = content->GetChildCount();
317 // Plan to build the content by default.
318 bool hasContent = (contentCount > 0);
319 if (hasContent) {
320 nsIDocument* doc = mBoundElement->OwnerDoc();
322 nsCOMPtr<nsINode> clonedNode;
323 nsCOMArray<nsINode> nodesWithProperties;
324 nsNodeUtils::Clone(content, true, doc->NodeInfoManager(),
325 nodesWithProperties, getter_AddRefs(clonedNode));
326 mContent = clonedNode->AsElement();
328 // Search for <xbl:children> elements in the XBL content. In the presence
329 // of multiple default insertion points, we use the last one in document
330 // order.
331 for (nsIContent* child = mContent; child; child = child->GetNextNode(mContent)) {
332 if (child->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
333 XBLChildrenElement* point = static_cast<XBLChildrenElement*>(child);
334 if (point->IsDefaultInsertion()) {
335 mDefaultInsertionPoint = point;
336 } else {
337 mInsertionPoints.AppendElement(point);
338 }
339 }
340 }
342 // Do this after looking for <children> as this messes up the parent
343 // pointer which would make the GetNextNode call above fail
344 InstallAnonymousContent(mContent, mBoundElement,
345 mPrototypeBinding->ChromeOnlyContent());
347 // Insert explicit children into insertion points
348 if (mDefaultInsertionPoint && mInsertionPoints.IsEmpty()) {
349 ExplicitChildIterator iter(mBoundElement);
350 for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
351 mDefaultInsertionPoint->AppendInsertedChild(child);
352 }
353 } else {
354 // It is odd to come into this code if mInsertionPoints is not empty, but
355 // we need to make sure to do the compatibility hack below if the bound
356 // node has any non <xul:template> or <xul:observes> children.
357 ExplicitChildIterator iter(mBoundElement);
358 for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
359 XBLChildrenElement* point = FindInsertionPointForInternal(child);
360 if (point) {
361 point->AppendInsertedChild(child);
362 } else {
363 nsINodeInfo *ni = child->NodeInfo();
364 if (ni->NamespaceID() != kNameSpaceID_XUL ||
365 (!ni->Equals(nsGkAtoms::_template) &&
366 !ni->Equals(nsGkAtoms::observes))) {
367 // Compatibility hack. For some reason the original XBL
368 // implementation dropped the content of a binding if any child of
369 // the bound element didn't match any of the <children> in the
370 // binding. This became a pseudo-API that we have to maintain.
372 // Undo InstallAnonymousContent
373 UninstallAnonymousContent(doc, mContent);
375 // Clear out our children elements to avoid dangling references.
376 ClearInsertionPoints();
378 // Pretend as though there was no content in the binding.
379 mContent = nullptr;
380 return;
381 }
382 }
383 }
384 }
386 // Set binding parent on default content if need
387 if (mDefaultInsertionPoint) {
388 mDefaultInsertionPoint->MaybeSetupDefaultContent();
389 }
390 for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) {
391 mInsertionPoints[i]->MaybeSetupDefaultContent();
392 }
394 mPrototypeBinding->SetInitialAttributes(mBoundElement, mContent);
395 }
397 // Always check the content element for potential attributes.
398 // This shorthand hack always happens, even when we didn't
399 // build anonymous content.
400 const nsAttrName* attrName;
401 for (uint32_t i = 0; (attrName = content->GetAttrNameAt(i)); ++i) {
402 int32_t namespaceID = attrName->NamespaceID();
403 // Hold a strong reference here so that the atom doesn't go away during
404 // UnsetAttr.
405 nsCOMPtr<nsIAtom> name = attrName->LocalName();
407 if (name != nsGkAtoms::includes) {
408 if (!nsContentUtils::HasNonEmptyAttr(mBoundElement, namespaceID, name)) {
409 nsAutoString value2;
410 content->GetAttr(namespaceID, name, value2);
411 mBoundElement->SetAttr(namespaceID, name, attrName->GetPrefix(),
412 value2, false);
413 }
414 }
416 // Conserve space by wiping the attributes off the clone.
417 if (mContent)
418 mContent->UnsetAttr(namespaceID, name, false);
419 }
420 }
422 XBLChildrenElement*
423 nsXBLBinding::FindInsertionPointFor(nsIContent* aChild)
424 {
425 // XXX We should get rid of this function as it causes us to traverse the
426 // binding chain multiple times
427 if (mContent) {
428 return FindInsertionPointForInternal(aChild);
429 }
431 return mNextBinding ? mNextBinding->FindInsertionPointFor(aChild)
432 : nullptr;
433 }
435 XBLChildrenElement*
436 nsXBLBinding::FindInsertionPointForInternal(nsIContent* aChild)
437 {
438 for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) {
439 XBLChildrenElement* point = mInsertionPoints[i];
440 if (point->Includes(aChild)) {
441 return point;
442 }
443 }
445 return mDefaultInsertionPoint;
446 }
448 void
449 nsXBLBinding::ClearInsertionPoints()
450 {
451 if (mDefaultInsertionPoint) {
452 mDefaultInsertionPoint->ClearInsertedChildren();
453 }
455 for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) {
456 mInsertionPoints[i]->ClearInsertedChildren();
457 }
458 }
460 nsAnonymousContentList*
461 nsXBLBinding::GetAnonymousNodeList()
462 {
463 if (!mContent) {
464 return mNextBinding ? mNextBinding->GetAnonymousNodeList() : nullptr;
465 }
467 if (!mAnonymousContentList) {
468 mAnonymousContentList = new nsAnonymousContentList(mContent);
469 }
471 return mAnonymousContentList;
472 }
474 void
475 nsXBLBinding::InstallEventHandlers()
476 {
477 // Don't install handlers if scripts aren't allowed.
478 if (AllowScripts()) {
479 // Fetch the handlers prototypes for this binding.
480 nsXBLPrototypeHandler* handlerChain = mPrototypeBinding->GetPrototypeHandlers();
482 if (handlerChain) {
483 EventListenerManager* manager = mBoundElement->GetOrCreateListenerManager();
484 if (!manager)
485 return;
487 bool isChromeDoc =
488 nsContentUtils::IsChromeDoc(mBoundElement->OwnerDoc());
489 bool isChromeBinding = mPrototypeBinding->IsChrome();
490 nsXBLPrototypeHandler* curr;
491 for (curr = handlerChain; curr; curr = curr->GetNextHandler()) {
492 // Fetch the event type.
493 nsCOMPtr<nsIAtom> eventAtom = curr->GetEventName();
494 if (!eventAtom ||
495 eventAtom == nsGkAtoms::keyup ||
496 eventAtom == nsGkAtoms::keydown ||
497 eventAtom == nsGkAtoms::keypress)
498 continue;
500 nsXBLEventHandler* handler = curr->GetEventHandler();
501 if (handler) {
502 // Figure out if we're using capturing or not.
503 EventListenerFlags flags;
504 flags.mCapture = (curr->GetPhase() == NS_PHASE_CAPTURING);
506 // If this is a command, add it in the system event group
507 if ((curr->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND |
508 NS_HANDLER_TYPE_SYSTEM)) &&
509 (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
510 flags.mInSystemGroup = true;
511 }
513 bool hasAllowUntrustedAttr = curr->HasAllowUntrustedAttr();
514 if ((hasAllowUntrustedAttr && curr->AllowUntrustedEvents()) ||
515 (!hasAllowUntrustedAttr && !isChromeDoc && !mUsingXBLScope)) {
516 flags.mAllowUntrustedEvents = true;
517 }
519 manager->AddEventListenerByType(handler,
520 nsDependentAtomString(eventAtom),
521 flags);
522 }
523 }
525 const nsCOMArray<nsXBLKeyEventHandler>* keyHandlers =
526 mPrototypeBinding->GetKeyEventHandlers();
527 int32_t i;
528 for (i = 0; i < keyHandlers->Count(); ++i) {
529 nsXBLKeyEventHandler* handler = keyHandlers->ObjectAt(i);
530 handler->SetIsBoundToChrome(isChromeDoc);
531 handler->SetUsingXBLScope(mUsingXBLScope);
533 nsAutoString type;
534 handler->GetEventName(type);
536 // If this is a command, add it in the system event group, otherwise
537 // add it to the standard event group.
539 // Figure out if we're using capturing or not.
540 EventListenerFlags flags;
541 flags.mCapture = (handler->GetPhase() == NS_PHASE_CAPTURING);
543 if ((handler->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND |
544 NS_HANDLER_TYPE_SYSTEM)) &&
545 (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
546 flags.mInSystemGroup = true;
547 }
549 // For key handlers we have to set mAllowUntrustedEvents flag.
550 // Whether the handling of the event is allowed or not is handled in
551 // nsXBLKeyEventHandler::HandleEvent
552 flags.mAllowUntrustedEvents = true;
554 manager->AddEventListenerByType(handler, type, flags);
555 }
556 }
557 }
559 if (mNextBinding)
560 mNextBinding->InstallEventHandlers();
561 }
563 nsresult
564 nsXBLBinding::InstallImplementation()
565 {
566 // Always install the base class properties first, so that
567 // derived classes can reference the base class properties.
569 if (mNextBinding) {
570 nsresult rv = mNextBinding->InstallImplementation();
571 NS_ENSURE_SUCCESS(rv, rv);
572 }
574 // iterate through each property in the prototype's list and install the property.
575 if (AllowScripts())
576 return mPrototypeBinding->InstallImplementation(this);
578 return NS_OK;
579 }
581 nsIAtom*
582 nsXBLBinding::GetBaseTag(int32_t* aNameSpaceID)
583 {
584 nsIAtom *tag = mPrototypeBinding->GetBaseTag(aNameSpaceID);
585 if (!tag && mNextBinding)
586 return mNextBinding->GetBaseTag(aNameSpaceID);
588 return tag;
589 }
591 void
592 nsXBLBinding::AttributeChanged(nsIAtom* aAttribute, int32_t aNameSpaceID,
593 bool aRemoveFlag, bool aNotify)
594 {
595 // XXX Change if we ever allow multiple bindings in a chain to contribute anonymous content
596 if (!mContent) {
597 if (mNextBinding)
598 mNextBinding->AttributeChanged(aAttribute, aNameSpaceID,
599 aRemoveFlag, aNotify);
600 } else {
601 mPrototypeBinding->AttributeChanged(aAttribute, aNameSpaceID, aRemoveFlag,
602 mBoundElement, mContent, aNotify);
603 }
604 }
606 void
607 nsXBLBinding::ExecuteAttachedHandler()
608 {
609 if (mNextBinding)
610 mNextBinding->ExecuteAttachedHandler();
612 if (AllowScripts())
613 mPrototypeBinding->BindingAttached(mBoundElement);
614 }
616 void
617 nsXBLBinding::ExecuteDetachedHandler()
618 {
619 if (AllowScripts())
620 mPrototypeBinding->BindingDetached(mBoundElement);
622 if (mNextBinding)
623 mNextBinding->ExecuteDetachedHandler();
624 }
626 void
627 nsXBLBinding::UnhookEventHandlers()
628 {
629 nsXBLPrototypeHandler* handlerChain = mPrototypeBinding->GetPrototypeHandlers();
631 if (handlerChain) {
632 EventListenerManager* manager = mBoundElement->GetExistingListenerManager();
633 if (!manager) {
634 return;
635 }
637 bool isChromeBinding = mPrototypeBinding->IsChrome();
638 nsXBLPrototypeHandler* curr;
639 for (curr = handlerChain; curr; curr = curr->GetNextHandler()) {
640 nsXBLEventHandler* handler = curr->GetCachedEventHandler();
641 if (!handler) {
642 continue;
643 }
645 nsCOMPtr<nsIAtom> eventAtom = curr->GetEventName();
646 if (!eventAtom ||
647 eventAtom == nsGkAtoms::keyup ||
648 eventAtom == nsGkAtoms::keydown ||
649 eventAtom == nsGkAtoms::keypress)
650 continue;
652 // Figure out if we're using capturing or not.
653 EventListenerFlags flags;
654 flags.mCapture = (curr->GetPhase() == NS_PHASE_CAPTURING);
656 // If this is a command, remove it from the system event group,
657 // otherwise remove it from the standard event group.
659 if ((curr->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND |
660 NS_HANDLER_TYPE_SYSTEM)) &&
661 (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
662 flags.mInSystemGroup = true;
663 }
665 manager->RemoveEventListenerByType(handler,
666 nsDependentAtomString(eventAtom),
667 flags);
668 }
670 const nsCOMArray<nsXBLKeyEventHandler>* keyHandlers =
671 mPrototypeBinding->GetKeyEventHandlers();
672 int32_t i;
673 for (i = 0; i < keyHandlers->Count(); ++i) {
674 nsXBLKeyEventHandler* handler = keyHandlers->ObjectAt(i);
676 nsAutoString type;
677 handler->GetEventName(type);
679 // Figure out if we're using capturing or not.
680 EventListenerFlags flags;
681 flags.mCapture = (handler->GetPhase() == NS_PHASE_CAPTURING);
683 // If this is a command, remove it from the system event group, otherwise
684 // remove it from the standard event group.
686 if ((handler->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND | NS_HANDLER_TYPE_SYSTEM)) &&
687 (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
688 flags.mInSystemGroup = true;
689 }
691 manager->RemoveEventListenerByType(handler, type, flags);
692 }
693 }
694 }
696 static void
697 UpdateInsertionParent(XBLChildrenElement* aPoint,
698 nsIContent* aOldBoundElement)
699 {
700 if (aPoint->IsDefaultInsertion()) {
701 return;
702 }
704 for (size_t i = 0; i < aPoint->InsertedChildrenLength(); ++i) {
705 nsIContent* child = aPoint->mInsertedChildren[i];
707 MOZ_ASSERT(child->GetParentNode());
709 // Here, we're iterating children that we inserted. There are two cases:
710 // either |child| is an explicit child of |aOldBoundElement| and is no
711 // longer inserted anywhere or it's a child of a <children> element
712 // parented to |aOldBoundElement|. In the former case, the child is no
713 // longer inserted anywhere, so we set its insertion parent to null. In the
714 // latter case, the child is now inserted into |aOldBoundElement| from some
715 // binding above us, so we set its insertion parent to aOldBoundElement.
716 if (child->GetParentNode() == aOldBoundElement) {
717 child->SetXBLInsertionParent(nullptr);
718 } else {
719 child->SetXBLInsertionParent(aOldBoundElement);
720 }
721 }
722 }
724 void
725 nsXBLBinding::ChangeDocument(nsIDocument* aOldDocument, nsIDocument* aNewDocument)
726 {
727 if (aOldDocument == aNewDocument)
728 return;
730 // Now the binding dies. Unhook our prototypes.
731 if (mPrototypeBinding->HasImplementation()) {
732 nsCOMPtr<nsIScriptGlobalObject> global = do_QueryInterface(
733 aOldDocument->GetScopeObject());
734 if (global) {
735 nsCOMPtr<nsIScriptContext> context = global->GetContext();
736 if (context) {
737 JSContext *cx = context->GetNativeContext();
739 nsCxPusher pusher;
740 pusher.Push(cx);
742 // scope might be null if we've cycle-collected the global
743 // object, since the Unlink phase of cycle collection happens
744 // after JS GC finalization. But in that case, we don't care
745 // about fixing the prototype chain, since everything's going
746 // away immediately.
747 JS::Rooted<JSObject*> scope(cx, global->GetGlobalJSObject());
748 JS::Rooted<JSObject*> scriptObject(cx, mBoundElement->GetWrapper());
749 if (scope && scriptObject) {
750 // XXX Stay in sync! What if a layered binding has an
751 // <interface>?!
752 // XXXbz what does that comment mean, really? It seems to date
753 // back to when there was such a thing as an <interface>, whever
754 // that was...
756 // Find the right prototype.
757 JSAutoCompartment ac(cx, scriptObject);
759 JS::Rooted<JSObject*> base(cx, scriptObject);
760 JS::Rooted<JSObject*> proto(cx);
761 for ( ; true; base = proto) { // Will break out on null proto
762 if (!JS_GetPrototype(cx, base, &proto)) {
763 return;
764 }
765 if (!proto) {
766 break;
767 }
769 if (JS_GetClass(proto) != &gPrototypeJSClass) {
770 // Clearly not the right class
771 continue;
772 }
774 nsRefPtr<nsXBLDocumentInfo> docInfo =
775 static_cast<nsXBLDocumentInfo*>(::JS_GetPrivate(proto));
776 if (!docInfo) {
777 // Not the proto we seek
778 continue;
779 }
781 JS::Value protoBinding = ::JS_GetReservedSlot(proto, 0);
783 if (JSVAL_TO_PRIVATE(protoBinding) != mPrototypeBinding) {
784 // Not the right binding
785 continue;
786 }
788 // Alright! This is the right prototype. Pull it out of the
789 // proto chain.
790 JS::Rooted<JSObject*> grandProto(cx);
791 if (!JS_GetPrototype(cx, proto, &grandProto)) {
792 return;
793 }
794 ::JS_SetPrototype(cx, base, grandProto);
795 break;
796 }
798 mPrototypeBinding->UndefineFields(cx, scriptObject);
800 // Don't remove the reference from the document to the
801 // wrapper here since it'll be removed by the element
802 // itself when that's taken out of the document.
803 }
804 }
805 }
806 }
808 // Remove our event handlers
809 UnhookEventHandlers();
811 {
812 nsAutoScriptBlocker scriptBlocker;
814 // Then do our ancestors. This reverses the construction order, so that at
815 // all times things are consistent as far as everyone is concerned.
816 if (mNextBinding) {
817 mNextBinding->ChangeDocument(aOldDocument, aNewDocument);
818 }
820 // Update the anonymous content.
821 // XXXbz why not only for style bindings?
822 if (mContent) {
823 nsXBLBinding::UninstallAnonymousContent(aOldDocument, mContent);
824 }
826 // Now that we've unbound our anonymous content from the tree and updated
827 // its binding parent, update the insertion parent for content inserted
828 // into our <children> elements.
829 if (mDefaultInsertionPoint) {
830 UpdateInsertionParent(mDefaultInsertionPoint, mBoundElement);
831 }
833 for (size_t i = 0; i < mInsertionPoints.Length(); ++i) {
834 UpdateInsertionParent(mInsertionPoints[i], mBoundElement);
835 }
837 // Now that our inserted children no longer think they're inserted
838 // anywhere, make sure our internal state reflects that as well.
839 ClearInsertionPoints();
840 }
841 }
843 bool
844 nsXBLBinding::InheritsStyle() const
845 {
846 // XXX Will have to change if we ever allow multiple bindings to contribute anonymous content.
847 // Most derived binding with anonymous content determines style inheritance for now.
849 // XXX What about bindings with <content> but no kids, e.g., my treecell-text binding?
850 if (mContent)
851 return mPrototypeBinding->InheritsStyle();
853 if (mNextBinding)
854 return mNextBinding->InheritsStyle();
856 return true;
857 }
859 void
860 nsXBLBinding::WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc, void* aData)
861 {
862 if (mNextBinding)
863 mNextBinding->WalkRules(aFunc, aData);
865 nsIStyleRuleProcessor *rules = mPrototypeBinding->GetRuleProcessor();
866 if (rules)
867 (*aFunc)(rules, aData);
868 }
870 // Internal helper methods ////////////////////////////////////////////////////////////////
872 // Get or create a WeakMap object on a given XBL-hosting global.
873 //
874 // The scheme is as follows. XBL-hosting globals (either privileged content
875 // Windows or XBL scopes) get two lazily-defined WeakMap properties. Each
876 // WeakMap is keyed by the grand-proto - i.e. the original prototype of the
877 // content before it was bound, and the prototype of the class object that we
878 // splice in. The values in the WeakMap are simple dictionary-style objects,
879 // mapping from XBL class names to class objects.
880 static JSObject*
881 GetOrCreateClassObjectMap(JSContext *cx, JS::Handle<JSObject*> scope, const char *mapName)
882 {
883 AssertSameCompartment(cx, scope);
884 MOZ_ASSERT(JS_IsGlobalObject(scope));
885 MOZ_ASSERT(scope == xpc::GetXBLScopeOrGlobal(cx, scope));
887 // First, see if the map is already defined.
888 JS::Rooted<JSPropertyDescriptor> desc(cx);
889 if (!JS_GetOwnPropertyDescriptor(cx, scope, mapName, &desc)) {
890 return nullptr;
891 }
892 if (desc.object() && desc.value().isObject() &&
893 JS::IsWeakMapObject(&desc.value().toObject())) {
894 return &desc.value().toObject();
895 }
897 // It's not there. Create and define it.
898 JS::Rooted<JSObject*> map(cx, JS::NewWeakMapObject(cx));
899 if (!map || !JS_DefineProperty(cx, scope, mapName, map,
900 JSPROP_PERMANENT | JSPROP_READONLY,
901 JS_PropertyStub, JS_StrictPropertyStub))
902 {
903 return nullptr;
904 }
905 return map;
906 }
908 static JSObject*
909 GetOrCreateMapEntryForPrototype(JSContext *cx, JS::Handle<JSObject*> proto)
910 {
911 AssertSameCompartment(cx, proto);
912 // We want to hang our class objects off the XBL scope. But since we also
913 // hoist anonymous content into the XBL scope, this creates the potential for
914 // tricky collisions, since we can simultaneously have a bound in-content
915 // node with grand-proto HTMLDivElement and a bound anonymous node whose
916 // grand-proto is the XBL scope's cross-compartment wrapper to HTMLDivElement.
917 // Since we have to wrap the WeakMap keys into its scope, this distinction
918 // would be lost if we don't do something about it.
919 //
920 // So we define two maps - one class objects that live in content (prototyped
921 // to content prototypes), and the other for class objects that live in the
922 // XBL scope (prototyped to cross-compartment-wrapped content prototypes).
923 const char* name = xpc::IsInXBLScope(proto) ? "__ContentClassObjectMap__"
924 : "__XBLClassObjectMap__";
926 // Now, enter the XBL scope, since that's where we need to operate, and wrap
927 // the proto accordingly.
928 JS::Rooted<JSObject*> scope(cx, xpc::GetXBLScopeOrGlobal(cx, proto));
929 NS_ENSURE_TRUE(scope, nullptr);
930 JS::Rooted<JSObject*> wrappedProto(cx, proto);
931 JSAutoCompartment ac(cx, scope);
932 if (!JS_WrapObject(cx, &wrappedProto)) {
933 return nullptr;
934 }
936 // Grab the appropriate WeakMap.
937 JS::Rooted<JSObject*> map(cx, GetOrCreateClassObjectMap(cx, scope, name));
938 if (!map) {
939 return nullptr;
940 }
942 // See if we already have a map entry for that prototype.
943 JS::Rooted<JS::Value> val(cx);
944 if (!JS::GetWeakMapEntry(cx, map, wrappedProto, &val)) {
945 return nullptr;
946 }
947 if (val.isObject()) {
948 return &val.toObject();
949 }
951 // We don't have an entry. Create one and stick it in the map.
952 JS::Rooted<JSObject*> entry(cx);
953 entry = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), scope);
954 if (!entry) {
955 return nullptr;
956 }
957 JS::Rooted<JS::Value> entryVal(cx, JS::ObjectValue(*entry));
958 if (!JS::SetWeakMapEntry(cx, map, wrappedProto, entryVal)) {
959 NS_WARNING("SetWeakMapEntry failed, probably due to non-preservable WeakMap "
960 "key. XBL binding will fail for this element.");
961 return nullptr;
962 }
963 return entry;
964 }
966 // static
967 nsresult
968 nsXBLBinding::DoInitJSClass(JSContext *cx,
969 JS::Handle<JSObject*> obj,
970 const nsAFlatCString& aClassName,
971 nsXBLPrototypeBinding* aProtoBinding,
972 JS::MutableHandle<JSObject*> aClassObject,
973 bool* aNew)
974 {
975 MOZ_ASSERT(obj);
977 // Note that, now that NAC reflectors are created in the XBL scope, the
978 // reflector is not necessarily same-compartment with the document. So we'll
979 // end up creating a separate instance of the oddly-named XBL class object
980 // and defining it as a property on the XBL scope's global. This works fine,
981 // but we need to make sure never to assume that the the reflector and
982 // prototype are same-compartment with the bound document.
983 JS::Rooted<JSObject*> global(cx, js::GetGlobalForObjectCrossCompartment(obj));
984 JS::Rooted<JSObject*> xblScope(cx, xpc::GetXBLScopeOrGlobal(cx, global));
985 NS_ENSURE_TRUE(xblScope, NS_ERROR_UNEXPECTED);
987 JS::Rooted<JSObject*> parent_proto(cx);
988 if (!JS_GetPrototype(cx, obj, &parent_proto)) {
989 return NS_ERROR_FAILURE;
990 }
992 // Get the map entry for the parent prototype. In the one-off case that the
993 // parent prototype is null, we somewhat hackily just use the WeakMap itself
994 // as a property holder.
995 JS::Rooted<JSObject*> holder(cx);
996 if (parent_proto) {
997 holder = GetOrCreateMapEntryForPrototype(cx, parent_proto);
998 } else {
999 JSAutoCompartment innerAC(cx, xblScope);
1000 holder = GetOrCreateClassObjectMap(cx, xblScope, "__ContentClassObjectMap__");
1001 }
1002 if (NS_WARN_IF(!holder)) {
1003 return NS_ERROR_FAILURE;
1004 }
1005 js::AssertSameCompartment(holder, xblScope);
1006 JSAutoCompartment ac(cx, holder);
1008 // Look up the class on the property holder. The only properties on the
1009 // holder should be class objects. If we don't find the class object, we need
1010 // to create and define it.
1011 JS::Rooted<JSObject*> proto(cx);
1012 JS::Rooted<JSPropertyDescriptor> desc(cx);
1013 if (!JS_GetOwnPropertyDescriptor(cx, holder, aClassName.get(), &desc)) {
1014 return NS_ERROR_OUT_OF_MEMORY;
1015 }
1016 *aNew = !desc.object();
1017 if (desc.object()) {
1018 proto = &desc.value().toObject();
1019 MOZ_ASSERT(JS_GetClass(js::UncheckedUnwrap(proto)) == &gPrototypeJSClass);
1020 } else {
1022 // We need to create the prototype. First, enter the compartment where it's
1023 // going to live, and create it.
1024 JSAutoCompartment ac2(cx, global);
1025 proto = JS_NewObjectWithGivenProto(cx, &gPrototypeJSClass, parent_proto, global);
1026 if (!proto) {
1027 return NS_ERROR_OUT_OF_MEMORY;
1028 }
1030 // Keep this proto binding alive while we're alive. Do this first so that
1031 // we can guarantee that in XBLFinalize this will be non-null.
1032 // Note that we can't just store aProtoBinding in the private and
1033 // addref/release the nsXBLDocumentInfo through it, because cycle
1034 // collection doesn't seem to work right if the private is not an
1035 // nsISupports.
1036 nsXBLDocumentInfo* docInfo = aProtoBinding->XBLDocumentInfo();
1037 ::JS_SetPrivate(proto, docInfo);
1038 NS_ADDREF(docInfo);
1039 JS_SetReservedSlot(proto, 0, PRIVATE_TO_JSVAL(aProtoBinding));
1041 // Next, enter the compartment of the property holder, wrap the proto, and
1042 // stick it on.
1043 JSAutoCompartment ac3(cx, holder);
1044 if (!JS_WrapObject(cx, &proto) ||
1045 !JS_DefineProperty(cx, holder, aClassName.get(), proto,
1046 JSPROP_READONLY | JSPROP_PERMANENT,
1047 JS_PropertyStub, JS_StrictPropertyStub))
1048 {
1049 return NS_ERROR_OUT_OF_MEMORY;
1050 }
1051 }
1053 // Whew. We have the proto. Wrap it back into the compartment of |obj|,
1054 // splice it in, and return it.
1055 JSAutoCompartment ac4(cx, obj);
1056 if (!JS_WrapObject(cx, &proto) || !JS_SetPrototype(cx, obj, proto)) {
1057 return NS_ERROR_FAILURE;
1058 }
1059 aClassObject.set(proto);
1060 return NS_OK;
1061 }
1063 bool
1064 nsXBLBinding::AllowScripts()
1065 {
1066 return mBoundElement && mPrototypeBinding->GetAllowScripts();
1067 }
1069 nsXBLBinding*
1070 nsXBLBinding::RootBinding()
1071 {
1072 if (mNextBinding)
1073 return mNextBinding->RootBinding();
1075 return this;
1076 }
1078 bool
1079 nsXBLBinding::ResolveAllFields(JSContext *cx, JS::Handle<JSObject*> obj) const
1080 {
1081 if (!mPrototypeBinding->ResolveAllFields(cx, obj)) {
1082 return false;
1083 }
1085 if (mNextBinding) {
1086 return mNextBinding->ResolveAllFields(cx, obj);
1087 }
1089 return true;
1090 }
1092 bool
1093 nsXBLBinding::LookupMember(JSContext* aCx, JS::Handle<jsid> aId,
1094 JS::MutableHandle<JSPropertyDescriptor> aDesc)
1095 {
1096 // We should never enter this function with a pre-filled property descriptor.
1097 MOZ_ASSERT(!aDesc.object());
1099 // Get the string as an nsString before doing anything, so we can make
1100 // convenient comparisons during our search.
1101 if (!JSID_IS_STRING(aId)) {
1102 return true;
1103 }
1104 nsDependentJSString name(aId);
1106 // We have a weak reference to our bound element, so make sure it's alive.
1107 if (!mBoundElement || !mBoundElement->GetWrapper()) {
1108 return false;
1109 }
1111 // Get the scope of mBoundElement and the associated XBL scope. We should only
1112 // be calling into this machinery if we're running in a separate XBL scope.
1113 //
1114 // Note that we only end up in LookupMember for XrayWrappers from XBL scopes
1115 // into content. So for NAC reflectors that live in the XBL scope, we should
1116 // never get here. But on the off-chance that someone adds new callsites to
1117 // LookupMember, we do a release-mode assertion as belt-and-braces.
1118 // We do a release-mode assertion here to be extra safe.
1119 JS::Rooted<JSObject*> boundScope(aCx,
1120 js::GetGlobalForObjectCrossCompartment(mBoundElement->GetWrapper()));
1121 MOZ_RELEASE_ASSERT(!xpc::IsInXBLScope(boundScope));
1122 JS::Rooted<JSObject*> xblScope(aCx, xpc::GetXBLScope(aCx, boundScope));
1123 NS_ENSURE_TRUE(xblScope, false);
1124 MOZ_ASSERT(boundScope != xblScope);
1126 // Enter the xbl scope and invoke the internal version.
1127 {
1128 JSAutoCompartment ac(aCx, xblScope);
1129 JS::Rooted<jsid> id(aCx, aId);
1130 if (!JS_WrapId(aCx, &id) ||
1131 !LookupMemberInternal(aCx, name, id, aDesc, xblScope))
1132 {
1133 return false;
1134 }
1135 }
1137 // Wrap into the caller's scope.
1138 return JS_WrapPropertyDescriptor(aCx, aDesc);
1139 }
1141 bool
1142 nsXBLBinding::LookupMemberInternal(JSContext* aCx, nsString& aName,
1143 JS::Handle<jsid> aNameAsId,
1144 JS::MutableHandle<JSPropertyDescriptor> aDesc,
1145 JS::Handle<JSObject*> aXBLScope)
1146 {
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;
1153 }
1154 return mNextBinding->LookupMemberInternal(aCx, aName, aNameAsId,
1155 aDesc, aXBLScope);
1156 }
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;
1164 }
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;
1172 }
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;
1182 }
1183 if (aDesc.object() || !mNextBinding) {
1184 return true;
1185 }
1187 return mNextBinding->LookupMemberInternal(aCx, aName, aNameAsId, aDesc,
1188 aXBLScope);
1189 }
1191 bool
1192 nsXBLBinding::HasField(nsString& aName)
1193 {
1194 // See if this binding has such a field.
1195 return mPrototypeBinding->FindField(aName) ||
1196 (mNextBinding && mNextBinding->HasField(aName));
1197 }
1199 void
1200 nsXBLBinding::MarkForDeath()
1201 {
1202 mMarkedForDeath = true;
1203 ExecuteDetachedHandler();
1204 }
1206 bool
1207 nsXBLBinding::ImplementsInterface(REFNSIID aIID) const
1208 {
1209 return mPrototypeBinding->ImplementsInterface(aIID) ||
1210 (mNextBinding && mNextBinding->ImplementsInterface(aIID));
1211 }