Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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)
69 {
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;
81 }
83 if (binding->PrototypeBinding()->CompareBindingURI(aChildBindingURI)) {
84 ++bindingRecursion;
85 if (bindingRecursion < NS_MAX_XBL_BINDING_RECURSION) {
86 continue;
87 }
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;
98 }
99 }
101 return false;
102 }
104 // Individual binding requests.
105 class nsXBLBindingRequest
106 {
107 public:
108 nsCOMPtr<nsIURI> mBindingURI;
109 nsCOMPtr<nsIContent> mBoundElement;
111 void DocumentLoaded(nsIDocument* aBindingDoc)
112 {
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);
144 }
145 }
146 }
147 }
149 nsXBLBindingRequest(nsIURI* aURI, nsIContent* aBoundElement)
150 : mBindingURI(aURI),
151 mBoundElement(aBoundElement)
152 {
153 }
154 };
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
161 {
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);
171 ~nsXBLStreamListener();
173 void AddRequest(nsXBLBindingRequest* aRequest) { mBindingRequests.AppendElement(aRequest); }
174 bool HasRequest(nsIURI* aURI, nsIContent* aBoundElement);
176 private:
177 nsCOMPtr<nsIStreamListener> mInner;
178 nsAutoTArray<nsXBLBindingRequest*, 8> mBindingRequests;
180 nsCOMPtr<nsIWeakReference> mBoundDocument;
181 nsCOMPtr<nsIXMLContentSink> mSink; // Only set until OnStartRequest
182 nsCOMPtr<nsIDocument> mBindingDocument; // Only set until OnStartRequest
183 };
185 /* Implementation file */
186 NS_IMPL_ISUPPORTS(nsXBLStreamListener,
187 nsIStreamListener,
188 nsIRequestObserver,
189 nsIDOMEventListener)
191 nsXBLStreamListener::nsXBLStreamListener(nsIDocument* aBoundDocument,
192 nsIXMLContentSink* aSink,
193 nsIDocument* aBindingDocument)
194 : mSink(aSink), mBindingDocument(aBindingDocument)
195 {
196 /* member initializers and constructor code */
197 mBoundDocument = do_GetWeakReference(aBoundDocument);
198 }
200 nsXBLStreamListener::~nsXBLStreamListener()
201 {
202 for (uint32_t i = 0; i < mBindingRequests.Length(); i++) {
203 nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
204 delete req;
205 }
206 }
208 NS_IMETHODIMP
209 nsXBLStreamListener::OnDataAvailable(nsIRequest *request, nsISupports* aCtxt,
210 nsIInputStream* aInStr,
211 uint64_t aSourceOffset, uint32_t aCount)
212 {
213 if (mInner)
214 return mInner->OnDataAvailable(request, aCtxt, aInStr, aSourceOffset, aCount);
215 return NS_ERROR_FAILURE;
216 }
218 NS_IMETHODIMP
219 nsXBLStreamListener::OnStartRequest(nsIRequest* request, nsISupports* aCtxt)
220 {
221 // Make sure we don't hold on to the sink and binding document past this point
222 nsCOMPtr<nsIXMLContentSink> sink;
223 mSink.swap(sink);
224 nsCOMPtr<nsIDocument> doc;
225 mBindingDocument.swap(doc);
227 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
228 NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
230 nsCOMPtr<nsILoadGroup> group;
231 request->GetLoadGroup(getter_AddRefs(group));
233 nsresult rv = doc->StartDocumentLoad("loadAsInteractiveData",
234 channel,
235 group,
236 nullptr,
237 getter_AddRefs(mInner),
238 true,
239 sink);
240 NS_ENSURE_SUCCESS(rv, rv);
242 // Make sure to add ourselves as a listener after StartDocumentLoad,
243 // since that resets the event listners on the document.
244 doc->AddEventListener(NS_LITERAL_STRING("load"), this, false);
246 return mInner->OnStartRequest(request, aCtxt);
247 }
249 NS_IMETHODIMP
250 nsXBLStreamListener::OnStopRequest(nsIRequest* request, nsISupports* aCtxt, nsresult aStatus)
251 {
252 nsresult rv = NS_OK;
253 if (mInner) {
254 rv = mInner->OnStopRequest(request, aCtxt, aStatus);
255 }
257 // Don't hold onto the inner listener; holding onto it can create a cycle
258 // with the document
259 mInner = nullptr;
261 return rv;
262 }
264 bool
265 nsXBLStreamListener::HasRequest(nsIURI* aURI, nsIContent* aElt)
266 {
267 // XXX Could be more efficient.
268 uint32_t count = mBindingRequests.Length();
269 for (uint32_t i = 0; i < count; i++) {
270 nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
271 bool eq;
272 if (req->mBoundElement == aElt &&
273 NS_SUCCEEDED(req->mBindingURI->Equals(aURI, &eq)) && eq)
274 return true;
275 }
277 return false;
278 }
280 nsresult
281 nsXBLStreamListener::HandleEvent(nsIDOMEvent* aEvent)
282 {
283 nsresult rv = NS_OK;
284 uint32_t i;
285 uint32_t count = mBindingRequests.Length();
287 // Get the binding document; note that we don't hold onto it in this object
288 // to avoid creating a cycle
289 Event* event = aEvent->InternalDOMEvent();
290 EventTarget* target = event->GetCurrentTarget();
291 nsCOMPtr<nsIDocument> bindingDocument = do_QueryInterface(target);
292 NS_ASSERTION(bindingDocument, "Event not targeted at document?!");
294 // See if we're still alive.
295 nsCOMPtr<nsIDocument> doc(do_QueryReferent(mBoundDocument));
296 if (!doc) {
297 NS_WARNING("XBL load did not complete until after document went away! Modal dialog bug?\n");
298 }
299 else {
300 // We have to do a flush prior to notification of the document load.
301 // This has to happen since the HTML content sink can be holding on
302 // to notifications related to our children (e.g., if you bind to the
303 // <body> tag) that result in duplication of content.
304 // We need to get the sink's notifications flushed and then make the binding
305 // ready.
306 if (count > 0) {
307 nsXBLBindingRequest* req = mBindingRequests.ElementAt(0);
308 nsIDocument* document = req->mBoundElement->GetCurrentDoc();
309 if (document)
310 document->FlushPendingNotifications(Flush_ContentAndNotify);
311 }
313 // Remove ourselves from the set of pending docs.
314 nsBindingManager *bindingManager = doc->BindingManager();
315 nsIURI* documentURI = bindingDocument->GetDocumentURI();
316 bindingManager->RemoveLoadingDocListener(documentURI);
318 if (!bindingDocument->GetRootElement()) {
319 // FIXME: How about an error console warning?
320 NS_WARNING("XBL doc with no root element - this usually shouldn't happen");
321 return NS_ERROR_FAILURE;
322 }
324 // Put our doc info in the doc table.
325 nsBindingManager *xblDocBindingManager = bindingDocument->BindingManager();
326 nsRefPtr<nsXBLDocumentInfo> info =
327 xblDocBindingManager->GetXBLDocumentInfo(documentURI);
328 xblDocBindingManager->RemoveXBLDocumentInfo(info); // Break the self-imposed cycle.
329 if (!info) {
330 if (nsXBLService::IsChromeOrResourceURI(documentURI)) {
331 NS_WARNING("An XBL file is malformed. Did you forget the XBL namespace on the bindings tag?");
332 }
333 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
334 NS_LITERAL_CSTRING("XBL"), nullptr,
335 nsContentUtils::eXBL_PROPERTIES,
336 "MalformedXBL",
337 nullptr, 0, documentURI);
338 return NS_ERROR_FAILURE;
339 }
341 // If the doc is a chrome URI, then we put it into the XUL cache.
342 #ifdef MOZ_XUL
343 if (nsXBLService::IsChromeOrResourceURI(documentURI)) {
344 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
345 if (cache && cache->IsEnabled())
346 cache->PutXBLDocumentInfo(info);
347 }
348 #endif
350 bindingManager->PutXBLDocumentInfo(info);
352 // Notify all pending requests that their bindings are
353 // ready and can be installed.
354 for (i = 0; i < count; i++) {
355 nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
356 req->DocumentLoaded(bindingDocument);
357 }
358 }
360 target->RemoveEventListener(NS_LITERAL_STRING("load"), this, false);
362 return rv;
363 }
365 // Implementation /////////////////////////////////////////////////////////////////
367 // Static member variable initialization
368 bool nsXBLService::gAllowDataURIs = false;
370 // Implement our nsISupports methods
371 NS_IMPL_ISUPPORTS(nsXBLService, nsISupportsWeakReference)
373 void
374 nsXBLService::Init()
375 {
376 gInstance = new nsXBLService();
377 NS_ADDREF(gInstance);
378 }
380 // Constructors/Destructors
381 nsXBLService::nsXBLService(void)
382 {
383 Preferences::AddBoolVarCache(&gAllowDataURIs, "layout.debug.enable_data_xbl");
384 }
386 nsXBLService::~nsXBLService(void)
387 {
388 }
390 // static
391 bool
392 nsXBLService::IsChromeOrResourceURI(nsIURI* aURI)
393 {
394 bool isChrome = false;
395 bool isResource = false;
396 if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) &&
397 NS_SUCCEEDED(aURI->SchemeIs("resource", &isResource)))
398 return (isChrome || isResource);
399 return false;
400 }
403 // This function loads a particular XBL file and installs all of the bindings
404 // onto the element.
405 nsresult
406 nsXBLService::LoadBindings(nsIContent* aContent, nsIURI* aURL,
407 nsIPrincipal* aOriginPrincipal,
408 nsXBLBinding** aBinding, bool* aResolveStyle)
409 {
410 NS_PRECONDITION(aOriginPrincipal, "Must have an origin principal");
412 *aBinding = nullptr;
413 *aResolveStyle = false;
415 nsresult rv;
417 nsCOMPtr<nsIDocument> document = aContent->OwnerDoc();
419 nsAutoCString urlspec;
420 if (nsContentUtils::GetWrapperSafeScriptFilename(document, aURL, urlspec)) {
421 // Block an attempt to load a binding that has special wrapper
422 // automation needs.
424 return NS_OK;
425 }
427 nsXBLBinding *binding = aContent->GetXBLBinding();
428 if (binding) {
429 if (binding->MarkedForDeath()) {
430 FlushStyleBindings(aContent);
431 binding = nullptr;
432 }
433 else {
434 // See if the URIs match.
435 if (binding->PrototypeBinding()->CompareBindingURI(aURL))
436 return NS_OK;
437 FlushStyleBindings(aContent);
438 binding = nullptr;
439 }
440 }
442 bool ready;
443 nsRefPtr<nsXBLBinding> newBinding;
444 if (NS_FAILED(rv = GetBinding(aContent, aURL, false, aOriginPrincipal,
445 &ready, getter_AddRefs(newBinding)))) {
446 return rv;
447 }
449 if (!newBinding) {
450 #ifdef DEBUG
451 nsAutoCString spec;
452 aURL->GetSpec(spec);
453 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);
454 NS_ERROR(str.get());
455 #endif
456 return NS_OK;
457 }
459 if (::IsAncestorBinding(document, aURL, aContent)) {
460 return NS_ERROR_ILLEGAL_VALUE;
461 }
463 // We loaded a style binding. It goes on the end.
464 if (binding) {
465 // Get the last binding that is in the append layer.
466 binding->RootBinding()->SetBaseBinding(newBinding);
467 }
468 else {
469 // Install the binding on the content node.
470 aContent->SetXBLBinding(newBinding);
471 }
473 {
474 nsAutoScriptBlocker scriptBlocker;
476 // Set the binding's bound element.
477 newBinding->SetBoundElement(aContent);
479 // Tell the binding to build the anonymous content.
480 newBinding->GenerateAnonymousContent();
482 // Tell the binding to install event handlers
483 newBinding->InstallEventHandlers();
485 // Set up our properties
486 rv = newBinding->InstallImplementation();
487 NS_ENSURE_SUCCESS(rv, rv);
489 // Figure out if we have any scoped sheets. If so, we do a second resolve.
490 *aResolveStyle = newBinding->HasStyleSheets();
492 newBinding.swap(*aBinding);
493 }
495 return NS_OK;
496 }
498 nsresult
499 nsXBLService::FlushStyleBindings(nsIContent* aContent)
500 {
501 nsCOMPtr<nsIDocument> document = aContent->OwnerDoc();
503 nsXBLBinding *binding = aContent->GetXBLBinding();
504 if (binding) {
505 // Clear out the script references.
506 binding->ChangeDocument(document, nullptr);
508 aContent->SetXBLBinding(nullptr); // Flush old style bindings
509 }
511 return NS_OK;
512 }
514 //
515 // AttachGlobalKeyHandler
516 //
517 // Creates a new key handler and prepares to listen to key events on the given
518 // event receiver (either a document or an content node). If the receiver is content,
519 // then extra work needs to be done to hook it up to the document (XXX WHY??)
520 //
521 nsresult
522 nsXBLService::AttachGlobalKeyHandler(EventTarget* aTarget)
523 {
524 // check if the receiver is a content node (not a document), and hook
525 // it to the document if that is the case.
526 nsCOMPtr<EventTarget> piTarget = aTarget;
527 nsCOMPtr<nsIContent> contentNode(do_QueryInterface(aTarget));
528 if (contentNode) {
529 // Only attach if we're really in a document
530 nsCOMPtr<nsIDocument> doc = contentNode->GetCurrentDoc();
531 if (doc)
532 piTarget = doc; // We're a XUL keyset. Attach to our document.
533 }
535 EventListenerManager* manager = piTarget->GetOrCreateListenerManager();
537 if (!piTarget || !manager)
538 return NS_ERROR_FAILURE;
540 // the listener already exists, so skip this
541 if (contentNode && contentNode->GetProperty(nsGkAtoms::listener))
542 return NS_OK;
544 nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(contentNode));
546 // Create the key handler
547 nsRefPtr<nsXBLWindowKeyHandler> handler =
548 NS_NewXBLWindowKeyHandler(elt, piTarget);
550 // listen to these events
551 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keydown"),
552 TrustedEventsAtSystemGroupBubble());
553 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keyup"),
554 TrustedEventsAtSystemGroupBubble());
555 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keypress"),
556 TrustedEventsAtSystemGroupBubble());
558 // The capturing listener is only used for XUL keysets to properly handle
559 // shortcut keys in a multi-process environment.
560 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keydown"),
561 TrustedEventsAtSystemGroupCapture());
562 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keyup"),
563 TrustedEventsAtSystemGroupCapture());
564 manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keypress"),
565 TrustedEventsAtSystemGroupCapture());
567 if (contentNode)
568 return contentNode->SetProperty(nsGkAtoms::listener,
569 handler.forget().take(),
570 nsPropertyTable::SupportsDtorFunc, true);
572 // The reference to the handler will be maintained by the event target,
573 // and, if there is a content node, the property.
574 return NS_OK;
575 }
577 //
578 // DetachGlobalKeyHandler
579 //
580 // Removes a key handler added by DeatchGlobalKeyHandler.
581 //
582 nsresult
583 nsXBLService::DetachGlobalKeyHandler(EventTarget* aTarget)
584 {
585 nsCOMPtr<EventTarget> piTarget = aTarget;
586 nsCOMPtr<nsIContent> contentNode(do_QueryInterface(aTarget));
587 if (!contentNode) // detaching is only supported for content nodes
588 return NS_ERROR_FAILURE;
590 // Only attach if we're really in a document
591 nsCOMPtr<nsIDocument> doc = contentNode->GetCurrentDoc();
592 if (doc)
593 piTarget = do_QueryInterface(doc);
595 EventListenerManager* manager = piTarget->GetOrCreateListenerManager();
597 if (!piTarget || !manager)
598 return NS_ERROR_FAILURE;
600 nsIDOMEventListener* handler =
601 static_cast<nsIDOMEventListener*>(contentNode->GetProperty(nsGkAtoms::listener));
602 if (!handler)
603 return NS_ERROR_FAILURE;
605 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keydown"),
606 TrustedEventsAtSystemGroupBubble());
607 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keyup"),
608 TrustedEventsAtSystemGroupBubble());
609 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keypress"),
610 TrustedEventsAtSystemGroupBubble());
612 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keydown"),
613 TrustedEventsAtSystemGroupCapture());
614 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keyup"),
615 TrustedEventsAtSystemGroupCapture());
616 manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keypress"),
617 TrustedEventsAtSystemGroupCapture());
619 contentNode->DeleteProperty(nsGkAtoms::listener);
621 return NS_OK;
622 }
624 // Internal helper methods ////////////////////////////////////////////////////////////////
626 nsresult
627 nsXBLService::BindingReady(nsIContent* aBoundElement,
628 nsIURI* aURI,
629 bool* aIsReady)
630 {
631 // Don't do a security check here; we know this binding is set to go.
632 return GetBinding(aBoundElement, aURI, true, nullptr, aIsReady, nullptr);
633 }
635 nsresult
636 nsXBLService::GetBinding(nsIContent* aBoundElement, nsIURI* aURI,
637 bool aPeekOnly, nsIPrincipal* aOriginPrincipal,
638 bool* aIsReady, nsXBLBinding** aResult)
639 {
640 // More than 6 binding URIs are rare, see bug 55070 comment 18.
641 nsAutoTArray<nsIURI*, 6> uris;
642 return GetBinding(aBoundElement, aURI, aPeekOnly, aOriginPrincipal, aIsReady,
643 aResult, uris);
644 }
646 nsresult
647 nsXBLService::GetBinding(nsIContent* aBoundElement, nsIURI* aURI,
648 bool aPeekOnly, nsIPrincipal* aOriginPrincipal,
649 bool* aIsReady, nsXBLBinding** aResult,
650 nsTArray<nsIURI*>& aDontExtendURIs)
651 {
652 NS_ASSERTION(aPeekOnly || aResult,
653 "Must have non-null out param if not just peeking to see "
654 "whether the binding is ready");
656 if (aResult)
657 *aResult = nullptr;
659 if (!aURI)
660 return NS_ERROR_FAILURE;
662 nsAutoCString ref;
663 aURI->GetRef(ref);
665 nsCOMPtr<nsIDocument> boundDocument = aBoundElement->OwnerDoc();
667 nsRefPtr<nsXBLDocumentInfo> docInfo;
668 nsresult rv = LoadBindingDocumentInfo(aBoundElement, boundDocument, aURI,
669 aOriginPrincipal,
670 false, getter_AddRefs(docInfo));
671 NS_ENSURE_SUCCESS(rv, rv);
673 if (!docInfo)
674 return NS_ERROR_FAILURE;
676 nsXBLPrototypeBinding* protoBinding = docInfo->GetPrototypeBinding(ref);
678 if (!protoBinding) {
679 #ifdef DEBUG
680 nsAutoCString uriSpec;
681 aURI->GetSpec(uriSpec);
682 nsAutoCString doc;
683 boundDocument->GetDocumentURI()->GetSpec(doc);
684 nsAutoCString message("Unable to locate an XBL binding for URI ");
685 message += uriSpec;
686 message += " in document ";
687 message += doc;
688 NS_WARNING(message.get());
689 #endif
690 return NS_ERROR_FAILURE;
691 }
693 NS_ENSURE_TRUE(aDontExtendURIs.AppendElement(protoBinding->BindingURI()),
694 NS_ERROR_OUT_OF_MEMORY);
695 nsCOMPtr<nsIURI> altBindingURI = protoBinding->AlternateBindingURI();
696 if (altBindingURI) {
697 NS_ENSURE_TRUE(aDontExtendURIs.AppendElement(altBindingURI),
698 NS_ERROR_OUT_OF_MEMORY);
699 }
701 // Our prototype binding must have all its resources loaded.
702 bool ready = protoBinding->LoadResources();
703 if (!ready) {
704 // Add our bound element to the protos list of elts that should
705 // be notified when the stylesheets and scripts finish loading.
706 protoBinding->AddResourceListener(aBoundElement);
707 return NS_ERROR_FAILURE; // The binding isn't ready yet.
708 }
710 rv = protoBinding->ResolveBaseBinding();
711 NS_ENSURE_SUCCESS(rv, rv);
713 nsIURI* baseBindingURI;
714 nsXBLPrototypeBinding* baseProto = protoBinding->GetBasePrototype();
715 if (baseProto) {
716 baseBindingURI = baseProto->BindingURI();
717 }
718 else {
719 baseBindingURI = protoBinding->GetBaseBindingURI();
720 if (baseBindingURI) {
721 uint32_t count = aDontExtendURIs.Length();
722 for (uint32_t index = 0; index < count; ++index) {
723 bool equal;
724 rv = aDontExtendURIs[index]->Equals(baseBindingURI, &equal);
725 NS_ENSURE_SUCCESS(rv, rv);
726 if (equal) {
727 nsAutoCString spec, basespec;
728 protoBinding->BindingURI()->GetSpec(spec);
729 NS_ConvertUTF8toUTF16 protoSpec(spec);
730 baseBindingURI->GetSpec(basespec);
731 NS_ConvertUTF8toUTF16 baseSpecUTF16(basespec);
732 const char16_t* params[] = { protoSpec.get(), baseSpecUTF16.get() };
733 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
734 NS_LITERAL_CSTRING("XBL"), nullptr,
735 nsContentUtils::eXBL_PROPERTIES,
736 "CircularExtendsBinding",
737 params, ArrayLength(params),
738 boundDocument->GetDocumentURI());
739 return NS_ERROR_ILLEGAL_VALUE;
740 }
741 }
742 }
743 }
745 nsRefPtr<nsXBLBinding> baseBinding;
746 if (baseBindingURI) {
747 nsIContent* child = protoBinding->GetBindingElement();
748 rv = GetBinding(aBoundElement, baseBindingURI, aPeekOnly,
749 child->NodePrincipal(), aIsReady,
750 getter_AddRefs(baseBinding), aDontExtendURIs);
751 if (NS_FAILED(rv))
752 return rv; // We aren't ready yet.
753 }
755 *aIsReady = true;
757 if (!aPeekOnly) {
758 // Make a new binding
759 nsXBLBinding *newBinding = new nsXBLBinding(protoBinding);
760 NS_ENSURE_TRUE(newBinding, NS_ERROR_OUT_OF_MEMORY);
762 if (baseBinding) {
763 if (!baseProto) {
764 protoBinding->SetBasePrototype(baseBinding->PrototypeBinding());
765 }
766 newBinding->SetBaseBinding(baseBinding);
767 }
769 NS_ADDREF(*aResult = newBinding);
770 }
772 return NS_OK;
773 }
775 static bool SchemeIs(nsIURI* aURI, const char* aScheme)
776 {
777 nsCOMPtr<nsIURI> baseURI = NS_GetInnermostURI(aURI);
778 NS_ENSURE_TRUE(baseURI, false);
780 bool isScheme = false;
781 return NS_SUCCEEDED(baseURI->SchemeIs(aScheme, &isScheme)) && isScheme;
782 }
784 static bool
785 IsSystemOrChromeURLPrincipal(nsIPrincipal* aPrincipal)
786 {
787 if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
788 return true;
789 }
791 nsCOMPtr<nsIURI> uri;
792 aPrincipal->GetURI(getter_AddRefs(uri));
793 NS_ENSURE_TRUE(uri, false);
795 bool isChrome = false;
796 return NS_SUCCEEDED(uri->SchemeIs("chrome", &isChrome)) && isChrome;
797 }
799 nsresult
800 nsXBLService::LoadBindingDocumentInfo(nsIContent* aBoundElement,
801 nsIDocument* aBoundDocument,
802 nsIURI* aBindingURI,
803 nsIPrincipal* aOriginPrincipal,
804 bool aForceSyncLoad,
805 nsXBLDocumentInfo** aResult)
806 {
807 NS_PRECONDITION(aBindingURI, "Must have a binding URI");
808 NS_PRECONDITION(!aOriginPrincipal || aBoundDocument,
809 "If we're doing a security check, we better have a document!");
811 nsresult rv;
812 if (aOriginPrincipal) {
813 // Security check - Enforce same-origin policy, except to chrome.
814 // We have to be careful to not pass aContent as the context here.
815 // Otherwise, if there is a JS-implemented content policy, we will attempt
816 // to wrap the content node, which will try to load XBL bindings for it, if
817 // any. Since we're not done loading this binding yet, that will reenter
818 // this method and we'll end up creating a binding and then immediately
819 // clobbering it in our table. That makes things very confused, leading to
820 // misbehavior and crashes.
821 rv = nsContentUtils::
822 CheckSecurityBeforeLoad(aBindingURI, aOriginPrincipal,
823 nsIScriptSecurityManager::ALLOW_CHROME,
824 gAllowDataURIs,
825 nsIContentPolicy::TYPE_XBL,
826 aBoundDocument);
827 NS_ENSURE_SUCCESS(rv, NS_ERROR_XBL_BLOCKED);
829 if (!IsSystemOrChromeURLPrincipal(aOriginPrincipal)) {
830 // Also make sure that we're same-origin with the bound document
831 // except if the stylesheet has the system principal.
832 if (!(gAllowDataURIs && SchemeIs(aBindingURI, "data")) &&
833 !SchemeIs(aBindingURI, "chrome")) {
834 rv = aBoundDocument->NodePrincipal()->CheckMayLoad(aBindingURI,
835 true, false);
836 NS_ENSURE_SUCCESS(rv, NS_ERROR_XBL_BLOCKED);
837 }
839 // Finally check if this document is allowed to use XBL at all.
840 NS_ENSURE_TRUE(aBoundDocument->AllowXULXBL(),
841 NS_ERROR_XBL_BLOCKED);
842 }
843 }
845 *aResult = nullptr;
846 nsRefPtr<nsXBLDocumentInfo> info;
848 nsCOMPtr<nsIURI> documentURI;
849 rv = aBindingURI->CloneIgnoringRef(getter_AddRefs(documentURI));
850 NS_ENSURE_SUCCESS(rv, rv);
852 #ifdef MOZ_XUL
853 // We've got a file. Check our XBL document cache.
854 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
855 bool useXULCache = cache && cache->IsEnabled();
857 if (useXULCache) {
858 // The first line of defense is the chrome cache.
859 // This cache crosses the entire product, so that any XBL bindings that are
860 // part of chrome will be reused across all XUL documents.
861 info = cache->GetXBLDocumentInfo(documentURI);
862 }
863 #endif
865 if (!info) {
866 // The second line of defense is the binding manager's document table.
867 nsBindingManager *bindingManager = nullptr;
869 if (aBoundDocument) {
870 bindingManager = aBoundDocument->BindingManager();
871 info = bindingManager->GetXBLDocumentInfo(documentURI);
872 if (aBoundDocument->IsStaticDocument() &&
873 IsChromeOrResourceURI(aBindingURI)) {
874 aForceSyncLoad = true;
875 }
876 }
878 nsINodeInfo *ni = nullptr;
879 if (aBoundElement)
880 ni = aBoundElement->NodeInfo();
882 if (!info && bindingManager &&
883 (!ni || !(ni->Equals(nsGkAtoms::scrollbar, kNameSpaceID_XUL) ||
884 ni->Equals(nsGkAtoms::thumb, kNameSpaceID_XUL) ||
885 ((ni->Equals(nsGkAtoms::input) ||
886 ni->Equals(nsGkAtoms::select)) &&
887 aBoundElement->IsHTML()))) && !aForceSyncLoad) {
888 // The third line of defense is to investigate whether or not the
889 // document is currently being loaded asynchronously. If so, there's no
890 // document yet, but we need to glom on our request so that it will be
891 // processed whenever the doc does finish loading.
892 nsCOMPtr<nsIStreamListener> listener;
893 if (bindingManager)
894 listener = bindingManager->GetLoadingDocListener(documentURI);
895 if (listener) {
896 nsXBLStreamListener* xblListener =
897 static_cast<nsXBLStreamListener*>(listener.get());
898 // Create a new load observer.
899 if (!xblListener->HasRequest(aBindingURI, aBoundElement)) {
900 nsXBLBindingRequest* req = new nsXBLBindingRequest(aBindingURI, aBoundElement);
901 xblListener->AddRequest(req);
902 }
903 return NS_OK;
904 }
905 }
907 #ifdef MOZ_XUL
908 // Next, look in the startup cache
909 bool useStartupCache = useXULCache && IsChromeOrResourceURI(documentURI);
910 if (!info && useStartupCache) {
911 rv = nsXBLDocumentInfo::ReadPrototypeBindings(documentURI, getter_AddRefs(info));
912 if (NS_SUCCEEDED(rv)) {
913 cache->PutXBLDocumentInfo(info);
915 if (bindingManager) {
916 // Cache it in our binding manager's document table.
917 bindingManager->PutXBLDocumentInfo(info);
918 }
919 }
920 }
921 #endif
923 if (!info) {
924 // Finally, if all lines of defense fail, we go and fetch the binding
925 // document.
927 // Always load chrome synchronously
928 bool chrome;
929 if (NS_SUCCEEDED(documentURI->SchemeIs("chrome", &chrome)) && chrome)
930 aForceSyncLoad = true;
932 nsCOMPtr<nsIDocument> document;
933 FetchBindingDocument(aBoundElement, aBoundDocument, documentURI,
934 aBindingURI, aForceSyncLoad, getter_AddRefs(document));
936 if (document) {
937 nsBindingManager *xblDocBindingManager = document->BindingManager();
938 info = xblDocBindingManager->GetXBLDocumentInfo(documentURI);
939 if (!info) {
940 NS_ERROR("An XBL file is malformed. Did you forget the XBL namespace on the bindings tag?");
941 return NS_ERROR_FAILURE;
942 }
943 xblDocBindingManager->RemoveXBLDocumentInfo(info); // Break the self-imposed cycle.
945 // If the doc is a chrome URI, then we put it into the XUL cache.
946 #ifdef MOZ_XUL
947 if (useStartupCache) {
948 cache->PutXBLDocumentInfo(info);
950 // now write the bindings into the startup cache
951 info->WritePrototypeBindings();
952 }
953 #endif
955 if (bindingManager) {
956 // Also put it in our binding manager's document table.
957 bindingManager->PutXBLDocumentInfo(info);
958 }
959 }
960 }
961 }
963 info.forget(aResult);
965 return NS_OK;
966 }
968 nsresult
969 nsXBLService::FetchBindingDocument(nsIContent* aBoundElement, nsIDocument* aBoundDocument,
970 nsIURI* aDocumentURI, nsIURI* aBindingURI,
971 bool aForceSyncLoad, nsIDocument** aResult)
972 {
973 nsresult rv = NS_OK;
974 // Initialize our out pointer to nullptr
975 *aResult = nullptr;
977 // Now we have to synchronously load the binding file.
978 // Create an XML content sink and a parser.
979 nsCOMPtr<nsILoadGroup> loadGroup;
980 if (aBoundDocument)
981 loadGroup = aBoundDocument->GetDocumentLoadGroup();
983 // We really shouldn't have to force a sync load for anything here... could
984 // we get away with not doing that? Not sure.
985 if (IsChromeOrResourceURI(aDocumentURI))
986 aForceSyncLoad = true;
988 // Create document and contentsink and set them up.
989 nsCOMPtr<nsIDocument> doc;
990 rv = NS_NewXMLDocument(getter_AddRefs(doc));
991 NS_ENSURE_SUCCESS(rv, rv);
993 nsCOMPtr<nsIXMLContentSink> xblSink;
994 rv = NS_NewXBLContentSink(getter_AddRefs(xblSink), doc, aDocumentURI, nullptr);
995 NS_ENSURE_SUCCESS(rv, rv);
997 // Open channel
998 nsCOMPtr<nsIChannel> channel;
999 rv = NS_NewChannel(getter_AddRefs(channel), aDocumentURI, nullptr, loadGroup);
1000 NS_ENSURE_SUCCESS(rv, rv);
1002 nsCOMPtr<nsIInterfaceRequestor> sameOriginChecker = nsContentUtils::GetSameOriginChecker();
1003 NS_ENSURE_TRUE(sameOriginChecker, NS_ERROR_OUT_OF_MEMORY);
1005 channel->SetNotificationCallbacks(sameOriginChecker);
1007 if (!aForceSyncLoad) {
1008 // We can be asynchronous
1009 nsXBLStreamListener* xblListener =
1010 new nsXBLStreamListener(aBoundDocument, xblSink, doc);
1011 NS_ENSURE_TRUE(xblListener,NS_ERROR_OUT_OF_MEMORY);
1013 // Add ourselves to the list of loading docs.
1014 nsBindingManager *bindingManager;
1015 if (aBoundDocument)
1016 bindingManager = aBoundDocument->BindingManager();
1017 else
1018 bindingManager = nullptr;
1020 if (bindingManager)
1021 bindingManager->PutLoadingDocListener(aDocumentURI, xblListener);
1023 // Add our request.
1024 nsXBLBindingRequest* req = new nsXBLBindingRequest(aBindingURI,
1025 aBoundElement);
1026 xblListener->AddRequest(req);
1028 // Now kick off the async read.
1029 rv = channel->AsyncOpen(xblListener, nullptr);
1030 if (NS_FAILED(rv)) {
1031 // Well, we won't be getting a load. Make sure to clean up our stuff!
1032 if (bindingManager) {
1033 bindingManager->RemoveLoadingDocListener(aDocumentURI);
1034 }
1035 }
1036 return NS_OK;
1037 }
1039 nsCOMPtr<nsIStreamListener> listener;
1040 rv = doc->StartDocumentLoad("loadAsInteractiveData",
1041 channel,
1042 loadGroup,
1043 nullptr,
1044 getter_AddRefs(listener),
1045 true,
1046 xblSink);
1047 NS_ENSURE_SUCCESS(rv, rv);
1049 // Now do a blocking synchronous parse of the file.
1050 nsCOMPtr<nsIInputStream> in;
1051 rv = channel->Open(getter_AddRefs(in));
1052 NS_ENSURE_SUCCESS(rv, rv);
1054 rv = nsSyncLoadService::PushSyncStreamToListener(in, listener, channel);
1055 NS_ENSURE_SUCCESS(rv, rv);
1057 doc.swap(*aResult);
1059 return NS_OK;
1060 }