Bumping manifests a=b2g-bump
[gecko.git] / dom / xbl / nsXBLService.cpp
blobfbe03eb244a9408b010619fd1e9d0be3b4365ab7
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/ArrayUtils.h"
8 #include "nsCOMPtr.h"
9 #include "nsNetUtil.h"
10 #include "nsXBLService.h"
11 #include "nsXBLWindowKeyHandler.h"
12 #include "nsIInputStream.h"
13 #include "nsNameSpaceManager.h"
14 #include "nsIURI.h"
15 #include "nsIDOMElement.h"
16 #include "nsIURL.h"
17 #include "nsIChannel.h"
18 #include "nsXPIDLString.h"
19 #include "plstr.h"
20 #include "nsIContent.h"
21 #include "nsIDocument.h"
22 #include "nsIXMLContentSink.h"
23 #include "nsContentCID.h"
24 #include "mozilla/dom/XMLDocument.h"
25 #include "nsGkAtoms.h"
26 #include "nsIMemory.h"
27 #include "nsIObserverService.h"
28 #include "nsIDOMNodeList.h"
29 #include "nsXBLContentSink.h"
30 #include "nsXBLBinding.h"
31 #include "nsXBLPrototypeBinding.h"
32 #include "nsXBLDocumentInfo.h"
33 #include "nsCRT.h"
34 #include "nsContentUtils.h"
35 #include "nsSyncLoadService.h"
36 #include "nsContentPolicyUtils.h"
37 #include "nsTArray.h"
38 #include "nsError.h"
40 #include "nsIPresShell.h"
41 #include "nsIDocumentObserver.h"
42 #include "nsFrameManager.h"
43 #include "nsStyleContext.h"
44 #include "nsIScriptSecurityManager.h"
45 #include "nsIScriptError.h"
46 #include "nsXBLSerialize.h"
48 #ifdef MOZ_XUL
49 #include "nsXULPrototypeCache.h"
50 #endif
51 #include "nsIDOMEventListener.h"
52 #include "mozilla/Attributes.h"
53 #include "mozilla/EventListenerManager.h"
54 #include "mozilla/Preferences.h"
55 #include "mozilla/dom/Event.h"
56 #include "mozilla/dom/Element.h"
58 using namespace mozilla;
59 using namespace mozilla::dom;
61 #define NS_MAX_XBL_BINDING_RECURSION 20
63 nsXBLService* nsXBLService::gInstance = nullptr;
65 static bool
66 IsAncestorBinding(nsIDocument* aDocument,
67 nsIURI* aChildBindingURI,
68 nsIContent* aChild)
70 NS_ASSERTION(aDocument, "expected a document");
71 NS_ASSERTION(aChildBindingURI, "expected a binding URI");
72 NS_ASSERTION(aChild, "expected a child content");
74 uint32_t bindingRecursion = 0;
75 for (nsIContent *bindingParent = aChild->GetBindingParent();
76 bindingParent;
77 bindingParent = bindingParent->GetBindingParent()) {
78 nsXBLBinding* binding = bindingParent->GetXBLBinding();
79 if (!binding) {
80 continue;
83 if (binding->PrototypeBinding()->CompareBindingURI(aChildBindingURI)) {
84 ++bindingRecursion;
85 if (bindingRecursion < NS_MAX_XBL_BINDING_RECURSION) {
86 continue;
88 nsAutoCString spec;
89 aChildBindingURI->GetSpec(spec);
90 NS_ConvertUTF8toUTF16 bindingURI(spec);
91 const char16_t* params[] = { bindingURI.get() };
92 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
93 NS_LITERAL_CSTRING("XBL"), aDocument,
94 nsContentUtils::eXBL_PROPERTIES,
95 "TooDeepBindingRecursion",
96 params, ArrayLength(params));
97 return true;
101 return false;
104 // Individual binding requests.
105 class nsXBLBindingRequest
107 public:
108 nsCOMPtr<nsIURI> mBindingURI;
109 nsCOMPtr<nsIContent> mBoundElement;
111 void DocumentLoaded(nsIDocument* aBindingDoc)
113 // We only need the document here to cause frame construction, so
114 // we need the current doc, not the owner doc.
115 nsIDocument* doc = mBoundElement->GetCurrentDoc();
116 if (!doc)
117 return;
119 // Destroy the frames for mBoundElement.
120 nsIContent* destroyedFramesFor = nullptr;
121 nsIPresShell* shell = doc->GetShell();
122 if (shell) {
123 shell->DestroyFramesFor(mBoundElement, &destroyedFramesFor);
125 MOZ_ASSERT(!mBoundElement->GetPrimaryFrame());
127 // Get the binding.
128 bool ready = false;
129 nsXBLService::GetInstance()->BindingReady(mBoundElement, mBindingURI, &ready);
130 if (!ready)
131 return;
133 // If |mBoundElement| is (in addition to having binding |mBinding|)
134 // also a descendant of another element with binding |mBinding|,
135 // then we might have just constructed it due to the
136 // notification of its parent. (We can know about both if the
137 // binding loads were triggered from the DOM rather than frame
138 // construction.) So we have to check both whether the element
139 // has a primary frame and whether it's in the frame manager maps
140 // before sending a ContentInserted notification, or bad things
141 // will happen.
142 MOZ_ASSERT(shell == doc->GetShell());
143 if (shell) {
144 nsIFrame* childFrame = mBoundElement->GetPrimaryFrame();
145 if (!childFrame) {
146 // Check to see if it's in the undisplayed content map...
147 nsFrameManager* fm = shell->FrameManager();
148 nsStyleContext* sc = fm->GetUndisplayedContent(mBoundElement);
149 if (!sc) {
150 // or in the display:contents map.
151 sc = fm->GetDisplayContentsStyleFor(mBoundElement);
153 if (!sc) {
154 shell->CreateFramesFor(destroyedFramesFor);
160 nsXBLBindingRequest(nsIURI* aURI, nsIContent* aBoundElement)
161 : mBindingURI(aURI),
162 mBoundElement(aBoundElement)
167 // nsXBLStreamListener, a helper class used for
168 // asynchronous parsing of URLs
169 /* Header file */
170 class nsXBLStreamListener MOZ_FINAL : public nsIStreamListener,
171 public nsIDOMEventListener
173 public:
174 NS_DECL_ISUPPORTS
175 NS_DECL_NSISTREAMLISTENER
176 NS_DECL_NSIREQUESTOBSERVER
177 NS_DECL_NSIDOMEVENTLISTENER
179 nsXBLStreamListener(nsIDocument* aBoundDocument,
180 nsIXMLContentSink* aSink,
181 nsIDocument* aBindingDocument);
183 void AddRequest(nsXBLBindingRequest* aRequest) { mBindingRequests.AppendElement(aRequest); }
184 bool HasRequest(nsIURI* aURI, nsIContent* aBoundElement);
186 private:
187 ~nsXBLStreamListener();
189 nsCOMPtr<nsIStreamListener> mInner;
190 nsAutoTArray<nsXBLBindingRequest*, 8> mBindingRequests;
192 nsCOMPtr<nsIWeakReference> mBoundDocument;
193 nsCOMPtr<nsIXMLContentSink> mSink; // Only set until OnStartRequest
194 nsCOMPtr<nsIDocument> mBindingDocument; // Only set until OnStartRequest
197 /* Implementation file */
198 NS_IMPL_ISUPPORTS(nsXBLStreamListener,
199 nsIStreamListener,
200 nsIRequestObserver,
201 nsIDOMEventListener)
203 nsXBLStreamListener::nsXBLStreamListener(nsIDocument* aBoundDocument,
204 nsIXMLContentSink* aSink,
205 nsIDocument* aBindingDocument)
206 : mSink(aSink), mBindingDocument(aBindingDocument)
208 /* member initializers and constructor code */
209 mBoundDocument = do_GetWeakReference(aBoundDocument);
212 nsXBLStreamListener::~nsXBLStreamListener()
214 for (uint32_t i = 0; i < mBindingRequests.Length(); i++) {
215 nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
216 delete req;
220 NS_IMETHODIMP
221 nsXBLStreamListener::OnDataAvailable(nsIRequest *request, nsISupports* aCtxt,
222 nsIInputStream* aInStr,
223 uint64_t aSourceOffset, uint32_t aCount)
225 if (mInner)
226 return mInner->OnDataAvailable(request, aCtxt, aInStr, aSourceOffset, aCount);
227 return NS_ERROR_FAILURE;
230 NS_IMETHODIMP
231 nsXBLStreamListener::OnStartRequest(nsIRequest* request, nsISupports* aCtxt)
233 // Make sure we don't hold on to the sink and binding document past this point
234 nsCOMPtr<nsIXMLContentSink> sink;
235 mSink.swap(sink);
236 nsCOMPtr<nsIDocument> doc;
237 mBindingDocument.swap(doc);
239 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
240 NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
242 nsCOMPtr<nsILoadGroup> group;
243 request->GetLoadGroup(getter_AddRefs(group));
245 nsresult rv = doc->StartDocumentLoad("loadAsInteractiveData",
246 channel,
247 group,
248 nullptr,
249 getter_AddRefs(mInner),
250 true,
251 sink);
252 NS_ENSURE_SUCCESS(rv, rv);
254 // Make sure to add ourselves as a listener after StartDocumentLoad,
255 // since that resets the event listners on the document.
256 doc->AddEventListener(NS_LITERAL_STRING("load"), this, false);
258 return mInner->OnStartRequest(request, aCtxt);
261 NS_IMETHODIMP
262 nsXBLStreamListener::OnStopRequest(nsIRequest* request, nsISupports* aCtxt, nsresult aStatus)
264 nsresult rv = NS_OK;
265 if (mInner) {
266 rv = mInner->OnStopRequest(request, aCtxt, aStatus);
269 // Don't hold onto the inner listener; holding onto it can create a cycle
270 // with the document
271 mInner = nullptr;
273 return rv;
276 bool
277 nsXBLStreamListener::HasRequest(nsIURI* aURI, nsIContent* aElt)
279 // XXX Could be more efficient.
280 uint32_t count = mBindingRequests.Length();
281 for (uint32_t i = 0; i < count; i++) {
282 nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
283 bool eq;
284 if (req->mBoundElement == aElt &&
285 NS_SUCCEEDED(req->mBindingURI->Equals(aURI, &eq)) && eq)
286 return true;
289 return false;
292 nsresult
293 nsXBLStreamListener::HandleEvent(nsIDOMEvent* aEvent)
295 nsresult rv = NS_OK;
296 uint32_t i;
297 uint32_t count = mBindingRequests.Length();
299 // Get the binding document; note that we don't hold onto it in this object
300 // to avoid creating a cycle
301 Event* event = aEvent->InternalDOMEvent();
302 EventTarget* target = event->GetCurrentTarget();
303 nsCOMPtr<nsIDocument> bindingDocument = do_QueryInterface(target);
304 NS_ASSERTION(bindingDocument, "Event not targeted at document?!");
306 // See if we're still alive.
307 nsCOMPtr<nsIDocument> doc(do_QueryReferent(mBoundDocument));
308 if (!doc) {
309 NS_WARNING("XBL load did not complete until after document went away! Modal dialog bug?\n");
311 else {
312 // We have to do a flush prior to notification of the document load.
313 // This has to happen since the HTML content sink can be holding on
314 // to notifications related to our children (e.g., if you bind to the
315 // <body> tag) that result in duplication of content.
316 // We need to get the sink's notifications flushed and then make the binding
317 // ready.
318 if (count > 0) {
319 nsXBLBindingRequest* req = mBindingRequests.ElementAt(0);
320 nsIDocument* document = req->mBoundElement->GetCurrentDoc();
321 if (document)
322 document->FlushPendingNotifications(Flush_ContentAndNotify);
325 // Remove ourselves from the set of pending docs.
326 nsBindingManager *bindingManager = doc->BindingManager();
327 nsIURI* documentURI = bindingDocument->GetDocumentURI();
328 bindingManager->RemoveLoadingDocListener(documentURI);
330 if (!bindingDocument->GetRootElement()) {
331 // FIXME: How about an error console warning?
332 NS_WARNING("XBL doc with no root element - this usually shouldn't happen");
333 return NS_ERROR_FAILURE;
336 // Put our doc info in the doc table.
337 nsBindingManager *xblDocBindingManager = bindingDocument->BindingManager();
338 nsRefPtr<nsXBLDocumentInfo> info =
339 xblDocBindingManager->GetXBLDocumentInfo(documentURI);
340 xblDocBindingManager->RemoveXBLDocumentInfo(info); // Break the self-imposed cycle.
341 if (!info) {
342 if (nsXBLService::IsChromeOrResourceURI(documentURI)) {
343 NS_WARNING("An XBL file is malformed. Did you forget the XBL namespace on the bindings tag?");
345 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
346 NS_LITERAL_CSTRING("XBL"), nullptr,
347 nsContentUtils::eXBL_PROPERTIES,
348 "MalformedXBL",
349 nullptr, 0, documentURI);
350 return NS_ERROR_FAILURE;
353 // If the doc is a chrome URI, then we put it into the XUL cache.
354 #ifdef MOZ_XUL
355 if (nsXBLService::IsChromeOrResourceURI(documentURI)) {
356 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
357 if (cache && cache->IsEnabled())
358 cache->PutXBLDocumentInfo(info);
360 #endif
362 bindingManager->PutXBLDocumentInfo(info);
364 // Notify all pending requests that their bindings are
365 // ready and can be installed.
366 for (i = 0; i < count; i++) {
367 nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
368 req->DocumentLoaded(bindingDocument);
372 target->RemoveEventListener(NS_LITERAL_STRING("load"), this, false);
374 return rv;
377 // Implementation /////////////////////////////////////////////////////////////////
379 // Static member variable initialization
380 bool nsXBLService::gAllowDataURIs = false;
382 // Implement our nsISupports methods
383 NS_IMPL_ISUPPORTS(nsXBLService, nsISupportsWeakReference)
385 void
386 nsXBLService::Init()
388 gInstance = new nsXBLService();
389 NS_ADDREF(gInstance);
392 // Constructors/Destructors
393 nsXBLService::nsXBLService(void)
395 Preferences::AddBoolVarCache(&gAllowDataURIs, "layout.debug.enable_data_xbl");
398 nsXBLService::~nsXBLService(void)
402 // static
403 bool
404 nsXBLService::IsChromeOrResourceURI(nsIURI* aURI)
406 bool isChrome = false;
407 bool isResource = false;
408 if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) &&
409 NS_SUCCEEDED(aURI->SchemeIs("resource", &isResource)))
410 return (isChrome || isResource);
411 return false;
415 // This function loads a particular XBL file and installs all of the bindings
416 // onto the element.
417 nsresult
418 nsXBLService::LoadBindings(nsIContent* aContent, nsIURI* aURL,
419 nsIPrincipal* aOriginPrincipal,
420 nsXBLBinding** aBinding, bool* aResolveStyle)
422 NS_PRECONDITION(aOriginPrincipal, "Must have an origin principal");
424 *aBinding = nullptr;
425 *aResolveStyle = false;
427 nsresult rv;
429 nsCOMPtr<nsIDocument> document = aContent->OwnerDoc();
431 nsAutoCString urlspec;
432 if (nsContentUtils::GetWrapperSafeScriptFilename(document, aURL, urlspec)) {
433 // Block an attempt to load a binding that has special wrapper
434 // automation needs.
436 return NS_OK;
439 nsXBLBinding *binding = aContent->GetXBLBinding();
440 if (binding) {
441 if (binding->MarkedForDeath()) {
442 FlushStyleBindings(aContent);
443 binding = nullptr;
445 else {
446 // See if the URIs match.
447 if (binding->PrototypeBinding()->CompareBindingURI(aURL))
448 return NS_OK;
449 FlushStyleBindings(aContent);
450 binding = nullptr;
454 bool ready;
455 nsRefPtr<nsXBLBinding> newBinding;
456 if (NS_FAILED(rv = GetBinding(aContent, aURL, false, aOriginPrincipal,
457 &ready, getter_AddRefs(newBinding)))) {
458 return rv;
461 if (!newBinding) {
462 #ifdef DEBUG
463 nsAutoCString spec;
464 aURL->GetSpec(spec);
465 nsAutoCString str(NS_LITERAL_CSTRING("Failed to locate XBL binding. XBL is now using id instead of name to reference bindings. Make sure you have switched over. The invalid binding name is: ") + spec);
466 NS_ERROR(str.get());
467 #endif
468 return NS_OK;
471 if (::IsAncestorBinding(document, aURL, aContent)) {
472 return NS_ERROR_ILLEGAL_VALUE;
475 // We loaded a style binding. It goes on the end.
476 if (binding) {
477 // Get the last binding that is in the append layer.
478 binding->RootBinding()->SetBaseBinding(newBinding);
480 else {
481 // Install the binding on the content node.
482 aContent->SetXBLBinding(newBinding);
486 nsAutoScriptBlocker scriptBlocker;
488 // Set the binding's bound element.
489 newBinding->SetBoundElement(aContent);
491 // Tell the binding to build the anonymous content.
492 newBinding->GenerateAnonymousContent();
494 // Tell the binding to install event handlers
495 newBinding->InstallEventHandlers();
497 // Set up our properties
498 rv = newBinding->InstallImplementation();
499 NS_ENSURE_SUCCESS(rv, rv);
501 // Figure out if we have any scoped sheets. If so, we do a second resolve.
502 *aResolveStyle = newBinding->HasStyleSheets();
504 newBinding.swap(*aBinding);
507 return NS_OK;
510 nsresult
511 nsXBLService::FlushStyleBindings(nsIContent* aContent)
513 nsCOMPtr<nsIDocument> document = aContent->OwnerDoc();
515 nsXBLBinding *binding = aContent->GetXBLBinding();
516 if (binding) {
517 // Clear out the script references.
518 binding->ChangeDocument(document, nullptr);
520 aContent->SetXBLBinding(nullptr); // Flush old style bindings
523 return NS_OK;
527 // AttachGlobalKeyHandler
529 // Creates a new key handler and prepares to listen to key events on the given
530 // event receiver (either a document or an content node). If the receiver is content,
531 // then extra work needs to be done to hook it up to the document (XXX WHY??)
533 nsresult
534 nsXBLService::AttachGlobalKeyHandler(EventTarget* aTarget)
536 // check if the receiver is a content node (not a document), and hook
537 // it to the document if that is the case.
538 nsCOMPtr<EventTarget> piTarget = aTarget;
539 nsCOMPtr<nsIContent> contentNode(do_QueryInterface(aTarget));
540 if (contentNode) {
541 // Only attach if we're really in a document
542 nsCOMPtr<nsIDocument> doc = contentNode->GetCurrentDoc();
543 if (doc)
544 piTarget = doc; // We're a XUL keyset. Attach to our document.
547 EventListenerManager* manager = piTarget->GetOrCreateListenerManager();
549 if (!piTarget || !manager)
550 return NS_ERROR_FAILURE;
552 // the listener already exists, so skip this
553 if (contentNode && contentNode->GetProperty(nsGkAtoms::listener))
554 return NS_OK;
556 nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(contentNode));
558 // Create the key handler
559 nsRefPtr<nsXBLWindowKeyHandler> handler =
560 NS_NewXBLWindowKeyHandler(elt, piTarget);
562 // listen to these events
563 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keydown"),
564 TrustedEventsAtSystemGroupBubble());
565 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keyup"),
566 TrustedEventsAtSystemGroupBubble());
567 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keypress"),
568 TrustedEventsAtSystemGroupBubble());
570 // The capturing listener is only used for XUL keysets to properly handle
571 // shortcut keys in a multi-process environment.
572 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keydown"),
573 TrustedEventsAtSystemGroupCapture());
574 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keyup"),
575 TrustedEventsAtSystemGroupCapture());
576 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keypress"),
577 TrustedEventsAtSystemGroupCapture());
579 if (contentNode)
580 return contentNode->SetProperty(nsGkAtoms::listener,
581 handler.forget().take(),
582 nsPropertyTable::SupportsDtorFunc, true);
584 // The reference to the handler will be maintained by the event target,
585 // and, if there is a content node, the property.
586 return NS_OK;
590 // DetachGlobalKeyHandler
592 // Removes a key handler added by DeatchGlobalKeyHandler.
594 nsresult
595 nsXBLService::DetachGlobalKeyHandler(EventTarget* aTarget)
597 nsCOMPtr<EventTarget> piTarget = aTarget;
598 nsCOMPtr<nsIContent> contentNode(do_QueryInterface(aTarget));
599 if (!contentNode) // detaching is only supported for content nodes
600 return NS_ERROR_FAILURE;
602 // Only attach if we're really in a document
603 nsCOMPtr<nsIDocument> doc = contentNode->GetCurrentDoc();
604 if (doc)
605 piTarget = do_QueryInterface(doc);
607 EventListenerManager* manager = piTarget->GetOrCreateListenerManager();
609 if (!piTarget || !manager)
610 return NS_ERROR_FAILURE;
612 nsIDOMEventListener* handler =
613 static_cast<nsIDOMEventListener*>(contentNode->GetProperty(nsGkAtoms::listener));
614 if (!handler)
615 return NS_ERROR_FAILURE;
617 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keydown"),
618 TrustedEventsAtSystemGroupBubble());
619 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keyup"),
620 TrustedEventsAtSystemGroupBubble());
621 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keypress"),
622 TrustedEventsAtSystemGroupBubble());
624 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keydown"),
625 TrustedEventsAtSystemGroupCapture());
626 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keyup"),
627 TrustedEventsAtSystemGroupCapture());
628 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keypress"),
629 TrustedEventsAtSystemGroupCapture());
631 contentNode->DeleteProperty(nsGkAtoms::listener);
633 return NS_OK;
636 // Internal helper methods ////////////////////////////////////////////////////////////////
638 nsresult
639 nsXBLService::BindingReady(nsIContent* aBoundElement,
640 nsIURI* aURI,
641 bool* aIsReady)
643 // Don't do a security check here; we know this binding is set to go.
644 return GetBinding(aBoundElement, aURI, true, nullptr, aIsReady, nullptr);
647 nsresult
648 nsXBLService::GetBinding(nsIContent* aBoundElement, nsIURI* aURI,
649 bool aPeekOnly, nsIPrincipal* aOriginPrincipal,
650 bool* aIsReady, nsXBLBinding** aResult)
652 // More than 6 binding URIs are rare, see bug 55070 comment 18.
653 nsAutoTArray<nsIURI*, 6> uris;
654 return GetBinding(aBoundElement, aURI, aPeekOnly, aOriginPrincipal, aIsReady,
655 aResult, uris);
658 static bool
659 MayBindToContent(nsXBLPrototypeBinding* aProtoBinding, nsIContent* aBoundElement,
660 nsIURI* aURI)
662 // If this binding explicitly allows untrusted content, we're done.
663 if (aProtoBinding->BindToUntrustedContent()) {
664 return true;
667 // We let XUL content and content in XUL documents through, since XUL is
668 // restricted anyway and we want to minimize remote XUL breakage.
669 if (aBoundElement->IsXUL() || aBoundElement->OwnerDoc()->IsXUL()) {
670 return true;
673 // Similarly, we make an exception for anonymous content (which
674 // lives in the XBL scope), because it's already protected from content,
675 // and tends to use a lot of bindings that we wouldn't otherwise need to
676 // whitelist.
677 if (aBoundElement->IsInAnonymousSubtree()) {
678 return true;
681 // Allow if the bound content subsumes the binding.
682 nsCOMPtr<nsIDocument> bindingDoc = aProtoBinding->XBLDocumentInfo()->GetDocument();
683 NS_ENSURE_TRUE(bindingDoc, false);
684 if (aBoundElement->NodePrincipal()->Subsumes(bindingDoc->NodePrincipal())) {
685 return true;
688 // One last special case: we need to watch out for in-document data: URI
689 // bindings from remote-XUL-whitelisted domains (especially tests), because
690 // they end up with a null principal (rather than inheriting the document's
691 // principal), which causes them to fail the check above.
692 if (nsContentUtils::AllowXULXBLForPrincipal(aBoundElement->NodePrincipal())) {
693 bool isDataURI = false;
694 nsresult rv = aURI->SchemeIs("data", &isDataURI);
695 NS_ENSURE_SUCCESS(rv, false);
696 if (isDataURI) {
697 return true;
701 // Disallow.
702 return false;
705 nsresult
706 nsXBLService::GetBinding(nsIContent* aBoundElement, nsIURI* aURI,
707 bool aPeekOnly, nsIPrincipal* aOriginPrincipal,
708 bool* aIsReady, nsXBLBinding** aResult,
709 nsTArray<nsIURI*>& aDontExtendURIs)
711 NS_ASSERTION(aPeekOnly || aResult,
712 "Must have non-null out param if not just peeking to see "
713 "whether the binding is ready");
715 if (aResult)
716 *aResult = nullptr;
718 if (!aURI)
719 return NS_ERROR_FAILURE;
721 nsAutoCString ref;
722 aURI->GetRef(ref);
724 nsCOMPtr<nsIDocument> boundDocument = aBoundElement->OwnerDoc();
726 nsRefPtr<nsXBLDocumentInfo> docInfo;
727 nsresult rv = LoadBindingDocumentInfo(aBoundElement, boundDocument, aURI,
728 aOriginPrincipal,
729 false, getter_AddRefs(docInfo));
730 NS_ENSURE_SUCCESS(rv, rv);
732 if (!docInfo)
733 return NS_ERROR_FAILURE;
735 nsXBLPrototypeBinding* protoBinding = docInfo->GetPrototypeBinding(ref);
737 if (!protoBinding) {
738 #ifdef DEBUG
739 nsAutoCString uriSpec;
740 aURI->GetSpec(uriSpec);
741 nsAutoCString doc;
742 boundDocument->GetDocumentURI()->GetSpec(doc);
743 nsAutoCString message("Unable to locate an XBL binding for URI ");
744 message += uriSpec;
745 message += " in document ";
746 message += doc;
747 NS_WARNING(message.get());
748 #endif
749 return NS_ERROR_FAILURE;
752 // If the binding isn't whitelisted, refuse to apply it to content that
753 // doesn't subsume it (modulo a few exceptions).
754 if (!MayBindToContent(protoBinding, aBoundElement, aURI)) {
755 #ifdef DEBUG
756 nsAutoCString uriSpec;
757 aURI->GetSpec(uriSpec);
758 nsAutoCString message("Permission denied to apply binding ");
759 message += uriSpec;
760 message += " to unprivileged content. Set bindToUntrustedContent=true on "
761 "the binding to override this restriction.";
762 NS_WARNING(message.get());
763 #endif
764 return NS_ERROR_FAILURE;
767 NS_ENSURE_TRUE(aDontExtendURIs.AppendElement(protoBinding->BindingURI()),
768 NS_ERROR_OUT_OF_MEMORY);
769 nsCOMPtr<nsIURI> altBindingURI = protoBinding->AlternateBindingURI();
770 if (altBindingURI) {
771 NS_ENSURE_TRUE(aDontExtendURIs.AppendElement(altBindingURI),
772 NS_ERROR_OUT_OF_MEMORY);
775 // Our prototype binding must have all its resources loaded.
776 bool ready = protoBinding->LoadResources();
777 if (!ready) {
778 // Add our bound element to the protos list of elts that should
779 // be notified when the stylesheets and scripts finish loading.
780 protoBinding->AddResourceListener(aBoundElement);
781 return NS_ERROR_FAILURE; // The binding isn't ready yet.
784 rv = protoBinding->ResolveBaseBinding();
785 NS_ENSURE_SUCCESS(rv, rv);
787 nsIURI* baseBindingURI;
788 nsXBLPrototypeBinding* baseProto = protoBinding->GetBasePrototype();
789 if (baseProto) {
790 baseBindingURI = baseProto->BindingURI();
792 else {
793 baseBindingURI = protoBinding->GetBaseBindingURI();
794 if (baseBindingURI) {
795 uint32_t count = aDontExtendURIs.Length();
796 for (uint32_t index = 0; index < count; ++index) {
797 bool equal;
798 rv = aDontExtendURIs[index]->Equals(baseBindingURI, &equal);
799 NS_ENSURE_SUCCESS(rv, rv);
800 if (equal) {
801 nsAutoCString spec, basespec;
802 protoBinding->BindingURI()->GetSpec(spec);
803 NS_ConvertUTF8toUTF16 protoSpec(spec);
804 baseBindingURI->GetSpec(basespec);
805 NS_ConvertUTF8toUTF16 baseSpecUTF16(basespec);
806 const char16_t* params[] = { protoSpec.get(), baseSpecUTF16.get() };
807 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
808 NS_LITERAL_CSTRING("XBL"), nullptr,
809 nsContentUtils::eXBL_PROPERTIES,
810 "CircularExtendsBinding",
811 params, ArrayLength(params),
812 boundDocument->GetDocumentURI());
813 return NS_ERROR_ILLEGAL_VALUE;
819 nsRefPtr<nsXBLBinding> baseBinding;
820 if (baseBindingURI) {
821 nsIContent* child = protoBinding->GetBindingElement();
822 rv = GetBinding(aBoundElement, baseBindingURI, aPeekOnly,
823 child->NodePrincipal(), aIsReady,
824 getter_AddRefs(baseBinding), aDontExtendURIs);
825 if (NS_FAILED(rv))
826 return rv; // We aren't ready yet.
829 *aIsReady = true;
831 if (!aPeekOnly) {
832 // Make a new binding
833 nsXBLBinding *newBinding = new nsXBLBinding(protoBinding);
834 NS_ENSURE_TRUE(newBinding, NS_ERROR_OUT_OF_MEMORY);
836 if (baseBinding) {
837 if (!baseProto) {
838 protoBinding->SetBasePrototype(baseBinding->PrototypeBinding());
840 newBinding->SetBaseBinding(baseBinding);
843 NS_ADDREF(*aResult = newBinding);
846 return NS_OK;
849 static bool SchemeIs(nsIURI* aURI, const char* aScheme)
851 nsCOMPtr<nsIURI> baseURI = NS_GetInnermostURI(aURI);
852 NS_ENSURE_TRUE(baseURI, false);
854 bool isScheme = false;
855 return NS_SUCCEEDED(baseURI->SchemeIs(aScheme, &isScheme)) && isScheme;
858 static bool
859 IsSystemOrChromeURLPrincipal(nsIPrincipal* aPrincipal)
861 if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
862 return true;
865 nsCOMPtr<nsIURI> uri;
866 aPrincipal->GetURI(getter_AddRefs(uri));
867 NS_ENSURE_TRUE(uri, false);
869 bool isChrome = false;
870 return NS_SUCCEEDED(uri->SchemeIs("chrome", &isChrome)) && isChrome;
873 nsresult
874 nsXBLService::LoadBindingDocumentInfo(nsIContent* aBoundElement,
875 nsIDocument* aBoundDocument,
876 nsIURI* aBindingURI,
877 nsIPrincipal* aOriginPrincipal,
878 bool aForceSyncLoad,
879 nsXBLDocumentInfo** aResult)
881 NS_PRECONDITION(aBindingURI, "Must have a binding URI");
882 NS_PRECONDITION(!aOriginPrincipal || aBoundDocument,
883 "If we're doing a security check, we better have a document!");
885 nsresult rv;
886 if (aOriginPrincipal) {
887 // Security check - Enforce same-origin policy, except to chrome.
888 // We have to be careful to not pass aContent as the context here.
889 // Otherwise, if there is a JS-implemented content policy, we will attempt
890 // to wrap the content node, which will try to load XBL bindings for it, if
891 // any. Since we're not done loading this binding yet, that will reenter
892 // this method and we'll end up creating a binding and then immediately
893 // clobbering it in our table. That makes things very confused, leading to
894 // misbehavior and crashes.
895 rv = nsContentUtils::
896 CheckSecurityBeforeLoad(aBindingURI, aOriginPrincipal,
897 nsIScriptSecurityManager::ALLOW_CHROME,
898 gAllowDataURIs,
899 nsIContentPolicy::TYPE_XBL,
900 aBoundDocument);
901 NS_ENSURE_SUCCESS(rv, NS_ERROR_XBL_BLOCKED);
903 if (!IsSystemOrChromeURLPrincipal(aOriginPrincipal)) {
904 // Also make sure that we're same-origin with the bound document
905 // except if the stylesheet has the system principal.
906 if (!(gAllowDataURIs && SchemeIs(aBindingURI, "data")) &&
907 !SchemeIs(aBindingURI, "chrome")) {
908 rv = aBoundDocument->NodePrincipal()->CheckMayLoad(aBindingURI,
909 true, false);
910 NS_ENSURE_SUCCESS(rv, NS_ERROR_XBL_BLOCKED);
913 // Finally check if this document is allowed to use XBL at all.
914 NS_ENSURE_TRUE(aBoundDocument->AllowXULXBL(),
915 NS_ERROR_XBL_BLOCKED);
919 *aResult = nullptr;
920 nsRefPtr<nsXBLDocumentInfo> info;
922 nsCOMPtr<nsIURI> documentURI;
923 rv = aBindingURI->CloneIgnoringRef(getter_AddRefs(documentURI));
924 NS_ENSURE_SUCCESS(rv, rv);
926 #ifdef MOZ_XUL
927 // We've got a file. Check our XBL document cache.
928 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
929 bool useXULCache = cache && cache->IsEnabled();
931 if (useXULCache) {
932 // The first line of defense is the chrome cache.
933 // This cache crosses the entire product, so that any XBL bindings that are
934 // part of chrome will be reused across all XUL documents.
935 info = cache->GetXBLDocumentInfo(documentURI);
937 #endif
939 if (!info) {
940 // The second line of defense is the binding manager's document table.
941 nsBindingManager *bindingManager = nullptr;
943 if (aBoundDocument) {
944 bindingManager = aBoundDocument->BindingManager();
945 info = bindingManager->GetXBLDocumentInfo(documentURI);
946 if (aBoundDocument->IsStaticDocument() &&
947 IsChromeOrResourceURI(aBindingURI)) {
948 aForceSyncLoad = true;
952 NodeInfo *ni = nullptr;
953 if (aBoundElement)
954 ni = aBoundElement->NodeInfo();
956 if (!info && bindingManager &&
957 (!ni || !(ni->Equals(nsGkAtoms::scrollbar, kNameSpaceID_XUL) ||
958 ni->Equals(nsGkAtoms::thumb, kNameSpaceID_XUL) ||
959 ((ni->Equals(nsGkAtoms::input) ||
960 ni->Equals(nsGkAtoms::select)) &&
961 aBoundElement->IsHTML()))) && !aForceSyncLoad) {
962 // The third line of defense is to investigate whether or not the
963 // document is currently being loaded asynchronously. If so, there's no
964 // document yet, but we need to glom on our request so that it will be
965 // processed whenever the doc does finish loading.
966 nsCOMPtr<nsIStreamListener> listener;
967 if (bindingManager)
968 listener = bindingManager->GetLoadingDocListener(documentURI);
969 if (listener) {
970 nsXBLStreamListener* xblListener =
971 static_cast<nsXBLStreamListener*>(listener.get());
972 // Create a new load observer.
973 if (!xblListener->HasRequest(aBindingURI, aBoundElement)) {
974 nsXBLBindingRequest* req = new nsXBLBindingRequest(aBindingURI, aBoundElement);
975 xblListener->AddRequest(req);
977 return NS_OK;
981 #ifdef MOZ_XUL
982 // Next, look in the startup cache
983 bool useStartupCache = useXULCache && IsChromeOrResourceURI(documentURI);
984 if (!info && useStartupCache) {
985 rv = nsXBLDocumentInfo::ReadPrototypeBindings(documentURI, getter_AddRefs(info));
986 if (NS_SUCCEEDED(rv)) {
987 cache->PutXBLDocumentInfo(info);
989 if (bindingManager) {
990 // Cache it in our binding manager's document table.
991 bindingManager->PutXBLDocumentInfo(info);
995 #endif
997 if (!info) {
998 // Finally, if all lines of defense fail, we go and fetch the binding
999 // document.
1001 // Always load chrome synchronously
1002 bool chrome;
1003 if (NS_SUCCEEDED(documentURI->SchemeIs("chrome", &chrome)) && chrome)
1004 aForceSyncLoad = true;
1006 nsCOMPtr<nsIDocument> document;
1007 FetchBindingDocument(aBoundElement, aBoundDocument, documentURI,
1008 aBindingURI, aOriginPrincipal, aForceSyncLoad,
1009 getter_AddRefs(document));
1011 if (document) {
1012 nsBindingManager *xblDocBindingManager = document->BindingManager();
1013 info = xblDocBindingManager->GetXBLDocumentInfo(documentURI);
1014 if (!info) {
1015 NS_ERROR("An XBL file is malformed. Did you forget the XBL namespace on the bindings tag?");
1016 return NS_ERROR_FAILURE;
1018 xblDocBindingManager->RemoveXBLDocumentInfo(info); // Break the self-imposed cycle.
1020 // If the doc is a chrome URI, then we put it into the XUL cache.
1021 #ifdef MOZ_XUL
1022 if (useStartupCache) {
1023 cache->PutXBLDocumentInfo(info);
1025 // now write the bindings into the startup cache
1026 info->WritePrototypeBindings();
1028 #endif
1030 if (bindingManager) {
1031 // Also put it in our binding manager's document table.
1032 bindingManager->PutXBLDocumentInfo(info);
1038 info.forget(aResult);
1040 return NS_OK;
1043 nsresult
1044 nsXBLService::FetchBindingDocument(nsIContent* aBoundElement, nsIDocument* aBoundDocument,
1045 nsIURI* aDocumentURI, nsIURI* aBindingURI,
1046 nsIPrincipal* aOriginPrincipal, bool aForceSyncLoad,
1047 nsIDocument** aResult)
1049 nsresult rv = NS_OK;
1050 // Initialize our out pointer to nullptr
1051 *aResult = nullptr;
1053 // Now we have to synchronously load the binding file.
1054 // Create an XML content sink and a parser.
1055 nsCOMPtr<nsILoadGroup> loadGroup;
1056 if (aBoundDocument)
1057 loadGroup = aBoundDocument->GetDocumentLoadGroup();
1059 // We really shouldn't have to force a sync load for anything here... could
1060 // we get away with not doing that? Not sure.
1061 if (IsChromeOrResourceURI(aDocumentURI))
1062 aForceSyncLoad = true;
1064 // Create document and contentsink and set them up.
1065 nsCOMPtr<nsIDocument> doc;
1066 rv = NS_NewXMLDocument(getter_AddRefs(doc));
1067 NS_ENSURE_SUCCESS(rv, rv);
1069 nsCOMPtr<nsIXMLContentSink> xblSink;
1070 rv = NS_NewXBLContentSink(getter_AddRefs(xblSink), doc, aDocumentURI, nullptr);
1071 NS_ENSURE_SUCCESS(rv, rv);
1073 // Open channel
1074 // Note: There are some cases where aOriginPrincipal and aBoundDocument are purposely
1075 // set to null (to bypass security checks) when calling LoadBindingDocumentInfo() which calls
1076 // FetchBindingDocument(). LoadInfo will end up with no principal or node in those cases,
1077 // so we use systemPrincipal. This achieves the same result of bypassing security checks,
1078 // but it gives the wrong information to potential future consumers of loadInfo.
1079 nsCOMPtr<nsIChannel> channel;
1081 if (aOriginPrincipal) {
1082 // if there is an originPrincipal we should also have aBoundDocument
1083 NS_ASSERTION(aBoundDocument, "can not create a channel without aBoundDocument");
1084 rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(channel),
1085 aDocumentURI,
1086 aBoundDocument,
1087 aOriginPrincipal,
1088 nsILoadInfo::SEC_NORMAL,
1089 nsIContentPolicy::TYPE_OTHER,
1090 loadGroup);
1092 else {
1093 rv = NS_NewChannel(getter_AddRefs(channel),
1094 aDocumentURI,
1095 nsContentUtils::GetSystemPrincipal(),
1096 nsILoadInfo::SEC_NORMAL,
1097 nsIContentPolicy::TYPE_OTHER,
1098 loadGroup);
1101 NS_ENSURE_SUCCESS(rv, rv);
1103 nsCOMPtr<nsIInterfaceRequestor> sameOriginChecker = nsContentUtils::GetSameOriginChecker();
1104 NS_ENSURE_TRUE(sameOriginChecker, NS_ERROR_OUT_OF_MEMORY);
1106 channel->SetNotificationCallbacks(sameOriginChecker);
1108 if (!aForceSyncLoad) {
1109 // We can be asynchronous
1110 nsXBLStreamListener* xblListener =
1111 new nsXBLStreamListener(aBoundDocument, xblSink, doc);
1112 NS_ENSURE_TRUE(xblListener,NS_ERROR_OUT_OF_MEMORY);
1114 // Add ourselves to the list of loading docs.
1115 nsBindingManager *bindingManager;
1116 if (aBoundDocument)
1117 bindingManager = aBoundDocument->BindingManager();
1118 else
1119 bindingManager = nullptr;
1121 if (bindingManager)
1122 bindingManager->PutLoadingDocListener(aDocumentURI, xblListener);
1124 // Add our request.
1125 nsXBLBindingRequest* req = new nsXBLBindingRequest(aBindingURI,
1126 aBoundElement);
1127 xblListener->AddRequest(req);
1129 // Now kick off the async read.
1130 rv = channel->AsyncOpen(xblListener, nullptr);
1131 if (NS_FAILED(rv)) {
1132 // Well, we won't be getting a load. Make sure to clean up our stuff!
1133 if (bindingManager) {
1134 bindingManager->RemoveLoadingDocListener(aDocumentURI);
1137 return NS_OK;
1140 nsCOMPtr<nsIStreamListener> listener;
1141 rv = doc->StartDocumentLoad("loadAsInteractiveData",
1142 channel,
1143 loadGroup,
1144 nullptr,
1145 getter_AddRefs(listener),
1146 true,
1147 xblSink);
1148 NS_ENSURE_SUCCESS(rv, rv);
1150 // Now do a blocking synchronous parse of the file.
1151 nsCOMPtr<nsIInputStream> in;
1152 rv = channel->Open(getter_AddRefs(in));
1153 NS_ENSURE_SUCCESS(rv, rv);
1155 rv = nsSyncLoadService::PushSyncStreamToListener(in, listener, channel);
1156 NS_ENSURE_SUCCESS(rv, rv);
1158 doc.swap(*aResult);
1160 return NS_OK;