Bumping manifests a=b2g-bump
[gecko.git] / dom / xbl / nsXBLService.cpp
bloba3f69bf1d64b9b5bdcfca90f14f8eab25bf0d018
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 // Get the binding.
120 bool ready = false;
121 nsXBLService::GetInstance()->BindingReady(mBoundElement, mBindingURI, &ready);
122 if (!ready)
123 return;
125 // If |mBoundElement| is (in addition to having binding |mBinding|)
126 // also a descendant of another element with binding |mBinding|,
127 // then we might have just constructed it due to the
128 // notification of its parent. (We can know about both if the
129 // binding loads were triggered from the DOM rather than frame
130 // construction.) So we have to check both whether the element
131 // has a primary frame and whether it's in the undisplayed map
132 // before sending a ContentInserted notification, or bad things
133 // will happen.
134 nsIPresShell *shell = doc->GetShell();
135 if (shell) {
136 nsIFrame* childFrame = mBoundElement->GetPrimaryFrame();
137 if (!childFrame) {
138 // Check to see if it's in the undisplayed content map.
139 nsStyleContext* sc =
140 shell->FrameManager()->GetUndisplayedContent(mBoundElement);
142 if (!sc) {
143 shell->RecreateFramesFor(mBoundElement);
149 nsXBLBindingRequest(nsIURI* aURI, nsIContent* aBoundElement)
150 : mBindingURI(aURI),
151 mBoundElement(aBoundElement)
156 // nsXBLStreamListener, a helper class used for
157 // asynchronous parsing of URLs
158 /* Header file */
159 class nsXBLStreamListener MOZ_FINAL : public nsIStreamListener,
160 public nsIDOMEventListener
162 public:
163 NS_DECL_ISUPPORTS
164 NS_DECL_NSISTREAMLISTENER
165 NS_DECL_NSIREQUESTOBSERVER
166 NS_DECL_NSIDOMEVENTLISTENER
168 nsXBLStreamListener(nsIDocument* aBoundDocument,
169 nsIXMLContentSink* aSink,
170 nsIDocument* aBindingDocument);
172 void AddRequest(nsXBLBindingRequest* aRequest) { mBindingRequests.AppendElement(aRequest); }
173 bool HasRequest(nsIURI* aURI, nsIContent* aBoundElement);
175 private:
176 ~nsXBLStreamListener();
178 nsCOMPtr<nsIStreamListener> mInner;
179 nsAutoTArray<nsXBLBindingRequest*, 8> mBindingRequests;
181 nsCOMPtr<nsIWeakReference> mBoundDocument;
182 nsCOMPtr<nsIXMLContentSink> mSink; // Only set until OnStartRequest
183 nsCOMPtr<nsIDocument> mBindingDocument; // Only set until OnStartRequest
186 /* Implementation file */
187 NS_IMPL_ISUPPORTS(nsXBLStreamListener,
188 nsIStreamListener,
189 nsIRequestObserver,
190 nsIDOMEventListener)
192 nsXBLStreamListener::nsXBLStreamListener(nsIDocument* aBoundDocument,
193 nsIXMLContentSink* aSink,
194 nsIDocument* aBindingDocument)
195 : mSink(aSink), mBindingDocument(aBindingDocument)
197 /* member initializers and constructor code */
198 mBoundDocument = do_GetWeakReference(aBoundDocument);
201 nsXBLStreamListener::~nsXBLStreamListener()
203 for (uint32_t i = 0; i < mBindingRequests.Length(); i++) {
204 nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
205 delete req;
209 NS_IMETHODIMP
210 nsXBLStreamListener::OnDataAvailable(nsIRequest *request, nsISupports* aCtxt,
211 nsIInputStream* aInStr,
212 uint64_t aSourceOffset, uint32_t aCount)
214 if (mInner)
215 return mInner->OnDataAvailable(request, aCtxt, aInStr, aSourceOffset, aCount);
216 return NS_ERROR_FAILURE;
219 NS_IMETHODIMP
220 nsXBLStreamListener::OnStartRequest(nsIRequest* request, nsISupports* aCtxt)
222 // Make sure we don't hold on to the sink and binding document past this point
223 nsCOMPtr<nsIXMLContentSink> sink;
224 mSink.swap(sink);
225 nsCOMPtr<nsIDocument> doc;
226 mBindingDocument.swap(doc);
228 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
229 NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
231 nsCOMPtr<nsILoadGroup> group;
232 request->GetLoadGroup(getter_AddRefs(group));
234 nsresult rv = doc->StartDocumentLoad("loadAsInteractiveData",
235 channel,
236 group,
237 nullptr,
238 getter_AddRefs(mInner),
239 true,
240 sink);
241 NS_ENSURE_SUCCESS(rv, rv);
243 // Make sure to add ourselves as a listener after StartDocumentLoad,
244 // since that resets the event listners on the document.
245 doc->AddEventListener(NS_LITERAL_STRING("load"), this, false);
247 return mInner->OnStartRequest(request, aCtxt);
250 NS_IMETHODIMP
251 nsXBLStreamListener::OnStopRequest(nsIRequest* request, nsISupports* aCtxt, nsresult aStatus)
253 nsresult rv = NS_OK;
254 if (mInner) {
255 rv = mInner->OnStopRequest(request, aCtxt, aStatus);
258 // Don't hold onto the inner listener; holding onto it can create a cycle
259 // with the document
260 mInner = nullptr;
262 return rv;
265 bool
266 nsXBLStreamListener::HasRequest(nsIURI* aURI, nsIContent* aElt)
268 // XXX Could be more efficient.
269 uint32_t count = mBindingRequests.Length();
270 for (uint32_t i = 0; i < count; i++) {
271 nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
272 bool eq;
273 if (req->mBoundElement == aElt &&
274 NS_SUCCEEDED(req->mBindingURI->Equals(aURI, &eq)) && eq)
275 return true;
278 return false;
281 nsresult
282 nsXBLStreamListener::HandleEvent(nsIDOMEvent* aEvent)
284 nsresult rv = NS_OK;
285 uint32_t i;
286 uint32_t count = mBindingRequests.Length();
288 // Get the binding document; note that we don't hold onto it in this object
289 // to avoid creating a cycle
290 Event* event = aEvent->InternalDOMEvent();
291 EventTarget* target = event->GetCurrentTarget();
292 nsCOMPtr<nsIDocument> bindingDocument = do_QueryInterface(target);
293 NS_ASSERTION(bindingDocument, "Event not targeted at document?!");
295 // See if we're still alive.
296 nsCOMPtr<nsIDocument> doc(do_QueryReferent(mBoundDocument));
297 if (!doc) {
298 NS_WARNING("XBL load did not complete until after document went away! Modal dialog bug?\n");
300 else {
301 // We have to do a flush prior to notification of the document load.
302 // This has to happen since the HTML content sink can be holding on
303 // to notifications related to our children (e.g., if you bind to the
304 // <body> tag) that result in duplication of content.
305 // We need to get the sink's notifications flushed and then make the binding
306 // ready.
307 if (count > 0) {
308 nsXBLBindingRequest* req = mBindingRequests.ElementAt(0);
309 nsIDocument* document = req->mBoundElement->GetCurrentDoc();
310 if (document)
311 document->FlushPendingNotifications(Flush_ContentAndNotify);
314 // Remove ourselves from the set of pending docs.
315 nsBindingManager *bindingManager = doc->BindingManager();
316 nsIURI* documentURI = bindingDocument->GetDocumentURI();
317 bindingManager->RemoveLoadingDocListener(documentURI);
319 if (!bindingDocument->GetRootElement()) {
320 // FIXME: How about an error console warning?
321 NS_WARNING("XBL doc with no root element - this usually shouldn't happen");
322 return NS_ERROR_FAILURE;
325 // Put our doc info in the doc table.
326 nsBindingManager *xblDocBindingManager = bindingDocument->BindingManager();
327 nsRefPtr<nsXBLDocumentInfo> info =
328 xblDocBindingManager->GetXBLDocumentInfo(documentURI);
329 xblDocBindingManager->RemoveXBLDocumentInfo(info); // Break the self-imposed cycle.
330 if (!info) {
331 if (nsXBLService::IsChromeOrResourceURI(documentURI)) {
332 NS_WARNING("An XBL file is malformed. Did you forget the XBL namespace on the bindings tag?");
334 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
335 NS_LITERAL_CSTRING("XBL"), nullptr,
336 nsContentUtils::eXBL_PROPERTIES,
337 "MalformedXBL",
338 nullptr, 0, documentURI);
339 return NS_ERROR_FAILURE;
342 // If the doc is a chrome URI, then we put it into the XUL cache.
343 #ifdef MOZ_XUL
344 if (nsXBLService::IsChromeOrResourceURI(documentURI)) {
345 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
346 if (cache && cache->IsEnabled())
347 cache->PutXBLDocumentInfo(info);
349 #endif
351 bindingManager->PutXBLDocumentInfo(info);
353 // Notify all pending requests that their bindings are
354 // ready and can be installed.
355 for (i = 0; i < count; i++) {
356 nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
357 req->DocumentLoaded(bindingDocument);
361 target->RemoveEventListener(NS_LITERAL_STRING("load"), this, false);
363 return rv;
366 // Implementation /////////////////////////////////////////////////////////////////
368 // Static member variable initialization
369 bool nsXBLService::gAllowDataURIs = false;
371 // Implement our nsISupports methods
372 NS_IMPL_ISUPPORTS(nsXBLService, nsISupportsWeakReference)
374 void
375 nsXBLService::Init()
377 gInstance = new nsXBLService();
378 NS_ADDREF(gInstance);
381 // Constructors/Destructors
382 nsXBLService::nsXBLService(void)
384 Preferences::AddBoolVarCache(&gAllowDataURIs, "layout.debug.enable_data_xbl");
387 nsXBLService::~nsXBLService(void)
391 // static
392 bool
393 nsXBLService::IsChromeOrResourceURI(nsIURI* aURI)
395 bool isChrome = false;
396 bool isResource = false;
397 if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) &&
398 NS_SUCCEEDED(aURI->SchemeIs("resource", &isResource)))
399 return (isChrome || isResource);
400 return false;
404 // This function loads a particular XBL file and installs all of the bindings
405 // onto the element.
406 nsresult
407 nsXBLService::LoadBindings(nsIContent* aContent, nsIURI* aURL,
408 nsIPrincipal* aOriginPrincipal,
409 nsXBLBinding** aBinding, bool* aResolveStyle)
411 NS_PRECONDITION(aOriginPrincipal, "Must have an origin principal");
413 *aBinding = nullptr;
414 *aResolveStyle = false;
416 nsresult rv;
418 nsCOMPtr<nsIDocument> document = aContent->OwnerDoc();
420 nsAutoCString urlspec;
421 if (nsContentUtils::GetWrapperSafeScriptFilename(document, aURL, urlspec)) {
422 // Block an attempt to load a binding that has special wrapper
423 // automation needs.
425 return NS_OK;
428 nsXBLBinding *binding = aContent->GetXBLBinding();
429 if (binding) {
430 if (binding->MarkedForDeath()) {
431 FlushStyleBindings(aContent);
432 binding = nullptr;
434 else {
435 // See if the URIs match.
436 if (binding->PrototypeBinding()->CompareBindingURI(aURL))
437 return NS_OK;
438 FlushStyleBindings(aContent);
439 binding = nullptr;
443 bool ready;
444 nsRefPtr<nsXBLBinding> newBinding;
445 if (NS_FAILED(rv = GetBinding(aContent, aURL, false, aOriginPrincipal,
446 &ready, getter_AddRefs(newBinding)))) {
447 return rv;
450 if (!newBinding) {
451 #ifdef DEBUG
452 nsAutoCString spec;
453 aURL->GetSpec(spec);
454 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);
455 NS_ERROR(str.get());
456 #endif
457 return NS_OK;
460 if (::IsAncestorBinding(document, aURL, aContent)) {
461 return NS_ERROR_ILLEGAL_VALUE;
464 // We loaded a style binding. It goes on the end.
465 if (binding) {
466 // Get the last binding that is in the append layer.
467 binding->RootBinding()->SetBaseBinding(newBinding);
469 else {
470 // Install the binding on the content node.
471 aContent->SetXBLBinding(newBinding);
475 nsAutoScriptBlocker scriptBlocker;
477 // Set the binding's bound element.
478 newBinding->SetBoundElement(aContent);
480 // Tell the binding to build the anonymous content.
481 newBinding->GenerateAnonymousContent();
483 // Tell the binding to install event handlers
484 newBinding->InstallEventHandlers();
486 // Set up our properties
487 rv = newBinding->InstallImplementation();
488 NS_ENSURE_SUCCESS(rv, rv);
490 // Figure out if we have any scoped sheets. If so, we do a second resolve.
491 *aResolveStyle = newBinding->HasStyleSheets();
493 newBinding.swap(*aBinding);
496 return NS_OK;
499 nsresult
500 nsXBLService::FlushStyleBindings(nsIContent* aContent)
502 nsCOMPtr<nsIDocument> document = aContent->OwnerDoc();
504 nsXBLBinding *binding = aContent->GetXBLBinding();
505 if (binding) {
506 // Clear out the script references.
507 binding->ChangeDocument(document, nullptr);
509 aContent->SetXBLBinding(nullptr); // Flush old style bindings
512 return NS_OK;
516 // AttachGlobalKeyHandler
518 // Creates a new key handler and prepares to listen to key events on the given
519 // event receiver (either a document or an content node). If the receiver is content,
520 // then extra work needs to be done to hook it up to the document (XXX WHY??)
522 nsresult
523 nsXBLService::AttachGlobalKeyHandler(EventTarget* aTarget)
525 // check if the receiver is a content node (not a document), and hook
526 // it to the document if that is the case.
527 nsCOMPtr<EventTarget> piTarget = aTarget;
528 nsCOMPtr<nsIContent> contentNode(do_QueryInterface(aTarget));
529 if (contentNode) {
530 // Only attach if we're really in a document
531 nsCOMPtr<nsIDocument> doc = contentNode->GetCurrentDoc();
532 if (doc)
533 piTarget = doc; // We're a XUL keyset. Attach to our document.
536 EventListenerManager* manager = piTarget->GetOrCreateListenerManager();
538 if (!piTarget || !manager)
539 return NS_ERROR_FAILURE;
541 // the listener already exists, so skip this
542 if (contentNode && contentNode->GetProperty(nsGkAtoms::listener))
543 return NS_OK;
545 nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(contentNode));
547 // Create the key handler
548 nsRefPtr<nsXBLWindowKeyHandler> handler =
549 NS_NewXBLWindowKeyHandler(elt, piTarget);
551 // listen to these events
552 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keydown"),
553 TrustedEventsAtSystemGroupBubble());
554 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keyup"),
555 TrustedEventsAtSystemGroupBubble());
556 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keypress"),
557 TrustedEventsAtSystemGroupBubble());
559 // The capturing listener is only used for XUL keysets to properly handle
560 // shortcut keys in a multi-process environment.
561 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keydown"),
562 TrustedEventsAtSystemGroupCapture());
563 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keyup"),
564 TrustedEventsAtSystemGroupCapture());
565 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keypress"),
566 TrustedEventsAtSystemGroupCapture());
568 if (contentNode)
569 return contentNode->SetProperty(nsGkAtoms::listener,
570 handler.forget().take(),
571 nsPropertyTable::SupportsDtorFunc, true);
573 // The reference to the handler will be maintained by the event target,
574 // and, if there is a content node, the property.
575 return NS_OK;
579 // DetachGlobalKeyHandler
581 // Removes a key handler added by DeatchGlobalKeyHandler.
583 nsresult
584 nsXBLService::DetachGlobalKeyHandler(EventTarget* aTarget)
586 nsCOMPtr<EventTarget> piTarget = aTarget;
587 nsCOMPtr<nsIContent> contentNode(do_QueryInterface(aTarget));
588 if (!contentNode) // detaching is only supported for content nodes
589 return NS_ERROR_FAILURE;
591 // Only attach if we're really in a document
592 nsCOMPtr<nsIDocument> doc = contentNode->GetCurrentDoc();
593 if (doc)
594 piTarget = do_QueryInterface(doc);
596 EventListenerManager* manager = piTarget->GetOrCreateListenerManager();
598 if (!piTarget || !manager)
599 return NS_ERROR_FAILURE;
601 nsIDOMEventListener* handler =
602 static_cast<nsIDOMEventListener*>(contentNode->GetProperty(nsGkAtoms::listener));
603 if (!handler)
604 return NS_ERROR_FAILURE;
606 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keydown"),
607 TrustedEventsAtSystemGroupBubble());
608 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keyup"),
609 TrustedEventsAtSystemGroupBubble());
610 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keypress"),
611 TrustedEventsAtSystemGroupBubble());
613 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keydown"),
614 TrustedEventsAtSystemGroupCapture());
615 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keyup"),
616 TrustedEventsAtSystemGroupCapture());
617 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keypress"),
618 TrustedEventsAtSystemGroupCapture());
620 contentNode->DeleteProperty(nsGkAtoms::listener);
622 return NS_OK;
625 // Internal helper methods ////////////////////////////////////////////////////////////////
627 nsresult
628 nsXBLService::BindingReady(nsIContent* aBoundElement,
629 nsIURI* aURI,
630 bool* aIsReady)
632 // Don't do a security check here; we know this binding is set to go.
633 return GetBinding(aBoundElement, aURI, true, nullptr, aIsReady, nullptr);
636 nsresult
637 nsXBLService::GetBinding(nsIContent* aBoundElement, nsIURI* aURI,
638 bool aPeekOnly, nsIPrincipal* aOriginPrincipal,
639 bool* aIsReady, nsXBLBinding** aResult)
641 // More than 6 binding URIs are rare, see bug 55070 comment 18.
642 nsAutoTArray<nsIURI*, 6> uris;
643 return GetBinding(aBoundElement, aURI, aPeekOnly, aOriginPrincipal, aIsReady,
644 aResult, uris);
647 static bool
648 MayBindToContent(nsXBLPrototypeBinding* aProtoBinding, nsIContent* aBoundElement,
649 nsIURI* aURI)
651 // If this binding explicitly allows untrusted content, we're done.
652 if (aProtoBinding->BindToUntrustedContent()) {
653 return true;
656 // We let XUL content and content in XUL documents through, since XUL is
657 // restricted anyway and we want to minimize remote XUL breakage.
658 if (aBoundElement->IsXUL() || aBoundElement->OwnerDoc()->IsXUL()) {
659 return true;
662 // Similarly, we make an exception for anonymous content (which
663 // lives in the XBL scope), because it's already protected from content,
664 // and tends to use a lot of bindings that we wouldn't otherwise need to
665 // whitelist.
666 if (aBoundElement->IsInAnonymousSubtree()) {
667 return true;
670 // Allow if the bound content subsumes the binding.
671 nsCOMPtr<nsIDocument> bindingDoc = aProtoBinding->XBLDocumentInfo()->GetDocument();
672 NS_ENSURE_TRUE(bindingDoc, false);
673 if (aBoundElement->NodePrincipal()->Subsumes(bindingDoc->NodePrincipal())) {
674 return true;
677 // One last special case: we need to watch out for in-document data: URI
678 // bindings from remote-XUL-whitelisted domains (especially tests), because
679 // they end up with a null principal (rather than inheriting the document's
680 // principal), which causes them to fail the check above.
681 if (nsContentUtils::AllowXULXBLForPrincipal(aBoundElement->NodePrincipal())) {
682 bool isDataURI = false;
683 nsresult rv = aURI->SchemeIs("data", &isDataURI);
684 NS_ENSURE_SUCCESS(rv, false);
685 if (isDataURI) {
686 return true;
690 // Disallow.
691 return false;
694 nsresult
695 nsXBLService::GetBinding(nsIContent* aBoundElement, nsIURI* aURI,
696 bool aPeekOnly, nsIPrincipal* aOriginPrincipal,
697 bool* aIsReady, nsXBLBinding** aResult,
698 nsTArray<nsIURI*>& aDontExtendURIs)
700 NS_ASSERTION(aPeekOnly || aResult,
701 "Must have non-null out param if not just peeking to see "
702 "whether the binding is ready");
704 if (aResult)
705 *aResult = nullptr;
707 if (!aURI)
708 return NS_ERROR_FAILURE;
710 nsAutoCString ref;
711 aURI->GetRef(ref);
713 nsCOMPtr<nsIDocument> boundDocument = aBoundElement->OwnerDoc();
715 nsRefPtr<nsXBLDocumentInfo> docInfo;
716 nsresult rv = LoadBindingDocumentInfo(aBoundElement, boundDocument, aURI,
717 aOriginPrincipal,
718 false, getter_AddRefs(docInfo));
719 NS_ENSURE_SUCCESS(rv, rv);
721 if (!docInfo)
722 return NS_ERROR_FAILURE;
724 nsXBLPrototypeBinding* protoBinding = docInfo->GetPrototypeBinding(ref);
726 if (!protoBinding) {
727 #ifdef DEBUG
728 nsAutoCString uriSpec;
729 aURI->GetSpec(uriSpec);
730 nsAutoCString doc;
731 boundDocument->GetDocumentURI()->GetSpec(doc);
732 nsAutoCString message("Unable to locate an XBL binding for URI ");
733 message += uriSpec;
734 message += " in document ";
735 message += doc;
736 NS_WARNING(message.get());
737 #endif
738 return NS_ERROR_FAILURE;
741 // If the binding isn't whitelisted, refuse to apply it to content that
742 // doesn't subsume it (modulo a few exceptions).
743 if (!MayBindToContent(protoBinding, aBoundElement, aURI)) {
744 #ifdef DEBUG
745 nsAutoCString uriSpec;
746 aURI->GetSpec(uriSpec);
747 nsAutoCString message("Permission denied to apply binding ");
748 message += uriSpec;
749 message += " to unprivileged content. Set bindToUntrustedContent=true on "
750 "the binding to override this restriction.";
751 NS_WARNING(message.get());
752 #endif
753 return NS_ERROR_FAILURE;
756 NS_ENSURE_TRUE(aDontExtendURIs.AppendElement(protoBinding->BindingURI()),
757 NS_ERROR_OUT_OF_MEMORY);
758 nsCOMPtr<nsIURI> altBindingURI = protoBinding->AlternateBindingURI();
759 if (altBindingURI) {
760 NS_ENSURE_TRUE(aDontExtendURIs.AppendElement(altBindingURI),
761 NS_ERROR_OUT_OF_MEMORY);
764 // Our prototype binding must have all its resources loaded.
765 bool ready = protoBinding->LoadResources();
766 if (!ready) {
767 // Add our bound element to the protos list of elts that should
768 // be notified when the stylesheets and scripts finish loading.
769 protoBinding->AddResourceListener(aBoundElement);
770 return NS_ERROR_FAILURE; // The binding isn't ready yet.
773 rv = protoBinding->ResolveBaseBinding();
774 NS_ENSURE_SUCCESS(rv, rv);
776 nsIURI* baseBindingURI;
777 nsXBLPrototypeBinding* baseProto = protoBinding->GetBasePrototype();
778 if (baseProto) {
779 baseBindingURI = baseProto->BindingURI();
781 else {
782 baseBindingURI = protoBinding->GetBaseBindingURI();
783 if (baseBindingURI) {
784 uint32_t count = aDontExtendURIs.Length();
785 for (uint32_t index = 0; index < count; ++index) {
786 bool equal;
787 rv = aDontExtendURIs[index]->Equals(baseBindingURI, &equal);
788 NS_ENSURE_SUCCESS(rv, rv);
789 if (equal) {
790 nsAutoCString spec, basespec;
791 protoBinding->BindingURI()->GetSpec(spec);
792 NS_ConvertUTF8toUTF16 protoSpec(spec);
793 baseBindingURI->GetSpec(basespec);
794 NS_ConvertUTF8toUTF16 baseSpecUTF16(basespec);
795 const char16_t* params[] = { protoSpec.get(), baseSpecUTF16.get() };
796 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
797 NS_LITERAL_CSTRING("XBL"), nullptr,
798 nsContentUtils::eXBL_PROPERTIES,
799 "CircularExtendsBinding",
800 params, ArrayLength(params),
801 boundDocument->GetDocumentURI());
802 return NS_ERROR_ILLEGAL_VALUE;
808 nsRefPtr<nsXBLBinding> baseBinding;
809 if (baseBindingURI) {
810 nsIContent* child = protoBinding->GetBindingElement();
811 rv = GetBinding(aBoundElement, baseBindingURI, aPeekOnly,
812 child->NodePrincipal(), aIsReady,
813 getter_AddRefs(baseBinding), aDontExtendURIs);
814 if (NS_FAILED(rv))
815 return rv; // We aren't ready yet.
818 *aIsReady = true;
820 if (!aPeekOnly) {
821 // Make a new binding
822 nsXBLBinding *newBinding = new nsXBLBinding(protoBinding);
823 NS_ENSURE_TRUE(newBinding, NS_ERROR_OUT_OF_MEMORY);
825 if (baseBinding) {
826 if (!baseProto) {
827 protoBinding->SetBasePrototype(baseBinding->PrototypeBinding());
829 newBinding->SetBaseBinding(baseBinding);
832 NS_ADDREF(*aResult = newBinding);
835 return NS_OK;
838 static bool SchemeIs(nsIURI* aURI, const char* aScheme)
840 nsCOMPtr<nsIURI> baseURI = NS_GetInnermostURI(aURI);
841 NS_ENSURE_TRUE(baseURI, false);
843 bool isScheme = false;
844 return NS_SUCCEEDED(baseURI->SchemeIs(aScheme, &isScheme)) && isScheme;
847 static bool
848 IsSystemOrChromeURLPrincipal(nsIPrincipal* aPrincipal)
850 if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
851 return true;
854 nsCOMPtr<nsIURI> uri;
855 aPrincipal->GetURI(getter_AddRefs(uri));
856 NS_ENSURE_TRUE(uri, false);
858 bool isChrome = false;
859 return NS_SUCCEEDED(uri->SchemeIs("chrome", &isChrome)) && isChrome;
862 nsresult
863 nsXBLService::LoadBindingDocumentInfo(nsIContent* aBoundElement,
864 nsIDocument* aBoundDocument,
865 nsIURI* aBindingURI,
866 nsIPrincipal* aOriginPrincipal,
867 bool aForceSyncLoad,
868 nsXBLDocumentInfo** aResult)
870 NS_PRECONDITION(aBindingURI, "Must have a binding URI");
871 NS_PRECONDITION(!aOriginPrincipal || aBoundDocument,
872 "If we're doing a security check, we better have a document!");
874 nsresult rv;
875 if (aOriginPrincipal) {
876 // Security check - Enforce same-origin policy, except to chrome.
877 // We have to be careful to not pass aContent as the context here.
878 // Otherwise, if there is a JS-implemented content policy, we will attempt
879 // to wrap the content node, which will try to load XBL bindings for it, if
880 // any. Since we're not done loading this binding yet, that will reenter
881 // this method and we'll end up creating a binding and then immediately
882 // clobbering it in our table. That makes things very confused, leading to
883 // misbehavior and crashes.
884 rv = nsContentUtils::
885 CheckSecurityBeforeLoad(aBindingURI, aOriginPrincipal,
886 nsIScriptSecurityManager::ALLOW_CHROME,
887 gAllowDataURIs,
888 nsIContentPolicy::TYPE_XBL,
889 aBoundDocument);
890 NS_ENSURE_SUCCESS(rv, NS_ERROR_XBL_BLOCKED);
892 if (!IsSystemOrChromeURLPrincipal(aOriginPrincipal)) {
893 // Also make sure that we're same-origin with the bound document
894 // except if the stylesheet has the system principal.
895 if (!(gAllowDataURIs && SchemeIs(aBindingURI, "data")) &&
896 !SchemeIs(aBindingURI, "chrome")) {
897 rv = aBoundDocument->NodePrincipal()->CheckMayLoad(aBindingURI,
898 true, false);
899 NS_ENSURE_SUCCESS(rv, NS_ERROR_XBL_BLOCKED);
902 // Finally check if this document is allowed to use XBL at all.
903 NS_ENSURE_TRUE(aBoundDocument->AllowXULXBL(),
904 NS_ERROR_XBL_BLOCKED);
908 *aResult = nullptr;
909 nsRefPtr<nsXBLDocumentInfo> info;
911 nsCOMPtr<nsIURI> documentURI;
912 rv = aBindingURI->CloneIgnoringRef(getter_AddRefs(documentURI));
913 NS_ENSURE_SUCCESS(rv, rv);
915 #ifdef MOZ_XUL
916 // We've got a file. Check our XBL document cache.
917 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
918 bool useXULCache = cache && cache->IsEnabled();
920 if (useXULCache) {
921 // The first line of defense is the chrome cache.
922 // This cache crosses the entire product, so that any XBL bindings that are
923 // part of chrome will be reused across all XUL documents.
924 info = cache->GetXBLDocumentInfo(documentURI);
926 #endif
928 if (!info) {
929 // The second line of defense is the binding manager's document table.
930 nsBindingManager *bindingManager = nullptr;
932 if (aBoundDocument) {
933 bindingManager = aBoundDocument->BindingManager();
934 info = bindingManager->GetXBLDocumentInfo(documentURI);
935 if (aBoundDocument->IsStaticDocument() &&
936 IsChromeOrResourceURI(aBindingURI)) {
937 aForceSyncLoad = true;
941 NodeInfo *ni = nullptr;
942 if (aBoundElement)
943 ni = aBoundElement->NodeInfo();
945 if (!info && bindingManager &&
946 (!ni || !(ni->Equals(nsGkAtoms::scrollbar, kNameSpaceID_XUL) ||
947 ni->Equals(nsGkAtoms::thumb, kNameSpaceID_XUL) ||
948 ((ni->Equals(nsGkAtoms::input) ||
949 ni->Equals(nsGkAtoms::select)) &&
950 aBoundElement->IsHTML()))) && !aForceSyncLoad) {
951 // The third line of defense is to investigate whether or not the
952 // document is currently being loaded asynchronously. If so, there's no
953 // document yet, but we need to glom on our request so that it will be
954 // processed whenever the doc does finish loading.
955 nsCOMPtr<nsIStreamListener> listener;
956 if (bindingManager)
957 listener = bindingManager->GetLoadingDocListener(documentURI);
958 if (listener) {
959 nsXBLStreamListener* xblListener =
960 static_cast<nsXBLStreamListener*>(listener.get());
961 // Create a new load observer.
962 if (!xblListener->HasRequest(aBindingURI, aBoundElement)) {
963 nsXBLBindingRequest* req = new nsXBLBindingRequest(aBindingURI, aBoundElement);
964 xblListener->AddRequest(req);
966 return NS_OK;
970 #ifdef MOZ_XUL
971 // Next, look in the startup cache
972 bool useStartupCache = useXULCache && IsChromeOrResourceURI(documentURI);
973 if (!info && useStartupCache) {
974 rv = nsXBLDocumentInfo::ReadPrototypeBindings(documentURI, getter_AddRefs(info));
975 if (NS_SUCCEEDED(rv)) {
976 cache->PutXBLDocumentInfo(info);
978 if (bindingManager) {
979 // Cache it in our binding manager's document table.
980 bindingManager->PutXBLDocumentInfo(info);
984 #endif
986 if (!info) {
987 // Finally, if all lines of defense fail, we go and fetch the binding
988 // document.
990 // Always load chrome synchronously
991 bool chrome;
992 if (NS_SUCCEEDED(documentURI->SchemeIs("chrome", &chrome)) && chrome)
993 aForceSyncLoad = true;
995 nsCOMPtr<nsIDocument> document;
996 FetchBindingDocument(aBoundElement, aBoundDocument, documentURI,
997 aBindingURI, aForceSyncLoad, getter_AddRefs(document));
999 if (document) {
1000 nsBindingManager *xblDocBindingManager = document->BindingManager();
1001 info = xblDocBindingManager->GetXBLDocumentInfo(documentURI);
1002 if (!info) {
1003 NS_ERROR("An XBL file is malformed. Did you forget the XBL namespace on the bindings tag?");
1004 return NS_ERROR_FAILURE;
1006 xblDocBindingManager->RemoveXBLDocumentInfo(info); // Break the self-imposed cycle.
1008 // If the doc is a chrome URI, then we put it into the XUL cache.
1009 #ifdef MOZ_XUL
1010 if (useStartupCache) {
1011 cache->PutXBLDocumentInfo(info);
1013 // now write the bindings into the startup cache
1014 info->WritePrototypeBindings();
1016 #endif
1018 if (bindingManager) {
1019 // Also put it in our binding manager's document table.
1020 bindingManager->PutXBLDocumentInfo(info);
1026 info.forget(aResult);
1028 return NS_OK;
1031 nsresult
1032 nsXBLService::FetchBindingDocument(nsIContent* aBoundElement, nsIDocument* aBoundDocument,
1033 nsIURI* aDocumentURI, nsIURI* aBindingURI,
1034 bool aForceSyncLoad, nsIDocument** aResult)
1036 nsresult rv = NS_OK;
1037 // Initialize our out pointer to nullptr
1038 *aResult = nullptr;
1040 // Now we have to synchronously load the binding file.
1041 // Create an XML content sink and a parser.
1042 nsCOMPtr<nsILoadGroup> loadGroup;
1043 if (aBoundDocument)
1044 loadGroup = aBoundDocument->GetDocumentLoadGroup();
1046 // We really shouldn't have to force a sync load for anything here... could
1047 // we get away with not doing that? Not sure.
1048 if (IsChromeOrResourceURI(aDocumentURI))
1049 aForceSyncLoad = true;
1051 // Create document and contentsink and set them up.
1052 nsCOMPtr<nsIDocument> doc;
1053 rv = NS_NewXMLDocument(getter_AddRefs(doc));
1054 NS_ENSURE_SUCCESS(rv, rv);
1056 nsCOMPtr<nsIXMLContentSink> xblSink;
1057 rv = NS_NewXBLContentSink(getter_AddRefs(xblSink), doc, aDocumentURI, nullptr);
1058 NS_ENSURE_SUCCESS(rv, rv);
1060 // Open channel
1061 nsCOMPtr<nsIChannel> channel;
1062 rv = NS_NewChannel(getter_AddRefs(channel), aDocumentURI, nullptr, loadGroup);
1063 NS_ENSURE_SUCCESS(rv, rv);
1065 nsCOMPtr<nsIInterfaceRequestor> sameOriginChecker = nsContentUtils::GetSameOriginChecker();
1066 NS_ENSURE_TRUE(sameOriginChecker, NS_ERROR_OUT_OF_MEMORY);
1068 channel->SetNotificationCallbacks(sameOriginChecker);
1070 if (!aForceSyncLoad) {
1071 // We can be asynchronous
1072 nsXBLStreamListener* xblListener =
1073 new nsXBLStreamListener(aBoundDocument, xblSink, doc);
1074 NS_ENSURE_TRUE(xblListener,NS_ERROR_OUT_OF_MEMORY);
1076 // Add ourselves to the list of loading docs.
1077 nsBindingManager *bindingManager;
1078 if (aBoundDocument)
1079 bindingManager = aBoundDocument->BindingManager();
1080 else
1081 bindingManager = nullptr;
1083 if (bindingManager)
1084 bindingManager->PutLoadingDocListener(aDocumentURI, xblListener);
1086 // Add our request.
1087 nsXBLBindingRequest* req = new nsXBLBindingRequest(aBindingURI,
1088 aBoundElement);
1089 xblListener->AddRequest(req);
1091 // Now kick off the async read.
1092 rv = channel->AsyncOpen(xblListener, nullptr);
1093 if (NS_FAILED(rv)) {
1094 // Well, we won't be getting a load. Make sure to clean up our stuff!
1095 if (bindingManager) {
1096 bindingManager->RemoveLoadingDocListener(aDocumentURI);
1099 return NS_OK;
1102 nsCOMPtr<nsIStreamListener> listener;
1103 rv = doc->StartDocumentLoad("loadAsInteractiveData",
1104 channel,
1105 loadGroup,
1106 nullptr,
1107 getter_AddRefs(listener),
1108 true,
1109 xblSink);
1110 NS_ENSURE_SUCCESS(rv, rv);
1112 // Now do a blocking synchronous parse of the file.
1113 nsCOMPtr<nsIInputStream> in;
1114 rv = channel->Open(getter_AddRefs(in));
1115 NS_ENSURE_SUCCESS(rv, rv);
1117 rv = nsSyncLoadService::PushSyncStreamToListener(in, listener, channel);
1118 NS_ENSURE_SUCCESS(rv, rv);
1120 doc.swap(*aResult);
1122 return NS_OK;