summaryrefslogtreecommitdiffstats
path: root/dom/xbl/nsXBLService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/xbl/nsXBLService.cpp')
-rw-r--r--dom/xbl/nsXBLService.cpp1090
1 files changed, 1090 insertions, 0 deletions
diff --git a/dom/xbl/nsXBLService.cpp b/dom/xbl/nsXBLService.cpp
new file mode 100644
index 000000000..1475b1368
--- /dev/null
+++ b/dom/xbl/nsXBLService.cpp
@@ -0,0 +1,1090 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsNetUtil.h"
+#include "nsXBLService.h"
+#include "nsXBLWindowKeyHandler.h"
+#include "nsIInputStream.h"
+#include "nsNameSpaceManager.h"
+#include "nsIURI.h"
+#include "nsIDOMElement.h"
+#include "nsIURL.h"
+#include "nsIChannel.h"
+#include "nsXPIDLString.h"
+#include "plstr.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIXMLContentSink.h"
+#include "nsContentCID.h"
+#include "mozilla/dom/XMLDocument.h"
+#include "nsGkAtoms.h"
+#include "nsIMemory.h"
+#include "nsIObserverService.h"
+#include "nsIDOMNodeList.h"
+#include "nsXBLContentSink.h"
+#include "nsXBLBinding.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsXBLDocumentInfo.h"
+#include "nsCRT.h"
+#include "nsContentUtils.h"
+#include "nsSyncLoadService.h"
+#include "nsContentPolicyUtils.h"
+#include "nsTArray.h"
+#include "nsError.h"
+
+#include "nsIPresShell.h"
+#include "nsIDocumentObserver.h"
+#include "nsFrameManager.h"
+#include "nsStyleContext.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIScriptError.h"
+#include "nsXBLSerialize.h"
+
+#ifdef MOZ_XUL
+#include "nsXULPrototypeCache.h"
+#endif
+#include "nsIDOMEventListener.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#define NS_MAX_XBL_BINDING_RECURSION 20
+
+nsXBLService* nsXBLService::gInstance = nullptr;
+
+static bool
+IsAncestorBinding(nsIDocument* aDocument,
+ nsIURI* aChildBindingURI,
+ nsIContent* aChild)
+{
+ NS_ASSERTION(aDocument, "expected a document");
+ NS_ASSERTION(aChildBindingURI, "expected a binding URI");
+ NS_ASSERTION(aChild, "expected a child content");
+
+ uint32_t bindingRecursion = 0;
+ for (nsIContent *bindingParent = aChild->GetBindingParent();
+ bindingParent;
+ bindingParent = bindingParent->GetBindingParent()) {
+ nsXBLBinding* binding = bindingParent->GetXBLBinding();
+ if (!binding) {
+ continue;
+ }
+
+ if (binding->PrototypeBinding()->CompareBindingURI(aChildBindingURI)) {
+ ++bindingRecursion;
+ if (bindingRecursion < NS_MAX_XBL_BINDING_RECURSION) {
+ continue;
+ }
+ NS_ConvertUTF8toUTF16 bindingURI(aChildBindingURI->GetSpecOrDefault());
+ const char16_t* params[] = { bindingURI.get() };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("XBL"), aDocument,
+ nsContentUtils::eXBL_PROPERTIES,
+ "TooDeepBindingRecursion",
+ params, ArrayLength(params));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Individual binding requests.
+class nsXBLBindingRequest
+{
+public:
+ nsCOMPtr<nsIURI> mBindingURI;
+ nsCOMPtr<nsIContent> mBoundElement;
+
+ void DocumentLoaded(nsIDocument* aBindingDoc)
+ {
+ // We only need the document here to cause frame construction, so
+ // we need the current doc, not the owner doc.
+ nsIDocument* doc = mBoundElement->GetUncomposedDoc();
+ if (!doc)
+ return;
+
+ // Destroy the frames for mBoundElement.
+ nsIContent* destroyedFramesFor = nullptr;
+ nsIPresShell* shell = doc->GetShell();
+ if (shell) {
+ shell->DestroyFramesFor(mBoundElement, &destroyedFramesFor);
+ }
+ MOZ_ASSERT(!mBoundElement->GetPrimaryFrame());
+
+ // Get the binding.
+ bool ready = false;
+ nsXBLService::GetInstance()->BindingReady(mBoundElement, mBindingURI, &ready);
+ if (!ready)
+ return;
+
+ // If |mBoundElement| is (in addition to having binding |mBinding|)
+ // also a descendant of another element with binding |mBinding|,
+ // then we might have just constructed it due to the
+ // notification of its parent. (We can know about both if the
+ // binding loads were triggered from the DOM rather than frame
+ // construction.) So we have to check both whether the element
+ // has a primary frame and whether it's in the frame manager maps
+ // before sending a ContentInserted notification, or bad things
+ // will happen.
+ MOZ_ASSERT(shell == doc->GetShell());
+ if (shell) {
+ nsIFrame* childFrame = mBoundElement->GetPrimaryFrame();
+ if (!childFrame) {
+ // Check to see if it's in the undisplayed content map...
+ nsFrameManager* fm = shell->FrameManager();
+ nsStyleContext* sc = fm->GetUndisplayedContent(mBoundElement);
+ if (!sc) {
+ // or in the display:contents map.
+ sc = fm->GetDisplayContentsStyleFor(mBoundElement);
+ }
+ if (!sc) {
+ shell->CreateFramesFor(destroyedFramesFor);
+ }
+ }
+ }
+ }
+
+ nsXBLBindingRequest(nsIURI* aURI, nsIContent* aBoundElement)
+ : mBindingURI(aURI),
+ mBoundElement(aBoundElement)
+ {
+ }
+};
+
+// nsXBLStreamListener, a helper class used for
+// asynchronous parsing of URLs
+/* Header file */
+class nsXBLStreamListener final : public nsIStreamListener,
+ public nsIDOMEventListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ nsXBLStreamListener(nsIDocument* aBoundDocument,
+ nsIXMLContentSink* aSink,
+ nsIDocument* aBindingDocument);
+
+ void AddRequest(nsXBLBindingRequest* aRequest) { mBindingRequests.AppendElement(aRequest); }
+ bool HasRequest(nsIURI* aURI, nsIContent* aBoundElement);
+
+private:
+ ~nsXBLStreamListener();
+
+ nsCOMPtr<nsIStreamListener> mInner;
+ AutoTArray<nsXBLBindingRequest*, 8> mBindingRequests;
+
+ nsCOMPtr<nsIWeakReference> mBoundDocument;
+ nsCOMPtr<nsIXMLContentSink> mSink; // Only set until OnStartRequest
+ nsCOMPtr<nsIDocument> mBindingDocument; // Only set until OnStartRequest
+};
+
+/* Implementation file */
+NS_IMPL_ISUPPORTS(nsXBLStreamListener,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIDOMEventListener)
+
+nsXBLStreamListener::nsXBLStreamListener(nsIDocument* aBoundDocument,
+ nsIXMLContentSink* aSink,
+ nsIDocument* aBindingDocument)
+: mSink(aSink), mBindingDocument(aBindingDocument)
+{
+ /* member initializers and constructor code */
+ mBoundDocument = do_GetWeakReference(aBoundDocument);
+}
+
+nsXBLStreamListener::~nsXBLStreamListener()
+{
+ for (uint32_t i = 0; i < mBindingRequests.Length(); i++) {
+ nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
+ delete req;
+ }
+}
+
+NS_IMETHODIMP
+nsXBLStreamListener::OnDataAvailable(nsIRequest *request, nsISupports* aCtxt,
+ nsIInputStream* aInStr,
+ uint64_t aSourceOffset, uint32_t aCount)
+{
+ if (mInner)
+ return mInner->OnDataAvailable(request, aCtxt, aInStr, aSourceOffset, aCount);
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsXBLStreamListener::OnStartRequest(nsIRequest* request, nsISupports* aCtxt)
+{
+ // Make sure we don't hold on to the sink and binding document past this point
+ nsCOMPtr<nsIXMLContentSink> sink;
+ mSink.swap(sink);
+ nsCOMPtr<nsIDocument> doc;
+ mBindingDocument.swap(doc);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsILoadGroup> group;
+ request->GetLoadGroup(getter_AddRefs(group));
+
+ nsresult rv = doc->StartDocumentLoad("loadAsInteractiveData",
+ channel,
+ group,
+ nullptr,
+ getter_AddRefs(mInner),
+ true,
+ sink);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make sure to add ourselves as a listener after StartDocumentLoad,
+ // since that resets the event listners on the document.
+ doc->AddEventListener(NS_LITERAL_STRING("load"), this, false);
+
+ return mInner->OnStartRequest(request, aCtxt);
+}
+
+NS_IMETHODIMP
+nsXBLStreamListener::OnStopRequest(nsIRequest* request, nsISupports* aCtxt, nsresult aStatus)
+{
+ nsresult rv = NS_OK;
+ if (mInner) {
+ rv = mInner->OnStopRequest(request, aCtxt, aStatus);
+ }
+
+ // Don't hold onto the inner listener; holding onto it can create a cycle
+ // with the document
+ mInner = nullptr;
+
+ return rv;
+}
+
+bool
+nsXBLStreamListener::HasRequest(nsIURI* aURI, nsIContent* aElt)
+{
+ // XXX Could be more efficient.
+ uint32_t count = mBindingRequests.Length();
+ for (uint32_t i = 0; i < count; i++) {
+ nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
+ bool eq;
+ if (req->mBoundElement == aElt &&
+ NS_SUCCEEDED(req->mBindingURI->Equals(aURI, &eq)) && eq)
+ return true;
+ }
+
+ return false;
+}
+
+nsresult
+nsXBLStreamListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsresult rv = NS_OK;
+ uint32_t i;
+ uint32_t count = mBindingRequests.Length();
+
+ // Get the binding document; note that we don't hold onto it in this object
+ // to avoid creating a cycle
+ Event* event = aEvent->InternalDOMEvent();
+ EventTarget* target = event->GetCurrentTarget();
+ nsCOMPtr<nsIDocument> bindingDocument = do_QueryInterface(target);
+ NS_ASSERTION(bindingDocument, "Event not targeted at document?!");
+
+ // See if we're still alive.
+ nsCOMPtr<nsIDocument> doc(do_QueryReferent(mBoundDocument));
+ if (!doc) {
+ NS_WARNING("XBL load did not complete until after document went away! Modal dialog bug?\n");
+ }
+ else {
+ // We have to do a flush prior to notification of the document load.
+ // This has to happen since the HTML content sink can be holding on
+ // to notifications related to our children (e.g., if you bind to the
+ // <body> tag) that result in duplication of content.
+ // We need to get the sink's notifications flushed and then make the binding
+ // ready.
+ if (count > 0) {
+ nsXBLBindingRequest* req = mBindingRequests.ElementAt(0);
+ nsIDocument* document = req->mBoundElement->GetUncomposedDoc();
+ if (document)
+ document->FlushPendingNotifications(Flush_ContentAndNotify);
+ }
+
+ // Remove ourselves from the set of pending docs.
+ nsBindingManager *bindingManager = doc->BindingManager();
+ nsIURI* documentURI = bindingDocument->GetDocumentURI();
+ bindingManager->RemoveLoadingDocListener(documentURI);
+
+ if (!bindingDocument->GetRootElement()) {
+ // FIXME: How about an error console warning?
+ NS_WARNING("XBL doc with no root element - this usually shouldn't happen");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Put our doc info in the doc table.
+ nsBindingManager *xblDocBindingManager = bindingDocument->BindingManager();
+ RefPtr<nsXBLDocumentInfo> info =
+ xblDocBindingManager->GetXBLDocumentInfo(documentURI);
+ xblDocBindingManager->RemoveXBLDocumentInfo(info); // Break the self-imposed cycle.
+ if (!info) {
+ if (nsXBLService::IsChromeOrResourceURI(documentURI)) {
+ NS_WARNING("An XBL file is malformed. Did you forget the XBL namespace on the bindings tag?");
+ }
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("XBL"), nullptr,
+ nsContentUtils::eXBL_PROPERTIES,
+ "MalformedXBL",
+ nullptr, 0, documentURI);
+ return NS_ERROR_FAILURE;
+ }
+
+ // If the doc is a chrome URI, then we put it into the XUL cache.
+#ifdef MOZ_XUL
+ if (nsXBLService::IsChromeOrResourceURI(documentURI)) {
+ nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
+ if (cache && cache->IsEnabled())
+ cache->PutXBLDocumentInfo(info);
+ }
+#endif
+
+ bindingManager->PutXBLDocumentInfo(info);
+
+ // Notify all pending requests that their bindings are
+ // ready and can be installed.
+ for (i = 0; i < count; i++) {
+ nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
+ req->DocumentLoaded(bindingDocument);
+ }
+ }
+
+ target->RemoveEventListener(NS_LITERAL_STRING("load"), this, false);
+
+ return rv;
+}
+
+// Implementation /////////////////////////////////////////////////////////////////
+
+// Implement our nsISupports methods
+NS_IMPL_ISUPPORTS(nsXBLService, nsISupportsWeakReference)
+
+void
+nsXBLService::Init()
+{
+ gInstance = new nsXBLService();
+ NS_ADDREF(gInstance);
+}
+
+// Constructors/Destructors
+nsXBLService::nsXBLService(void)
+{
+}
+
+nsXBLService::~nsXBLService(void)
+{
+}
+
+// static
+bool
+nsXBLService::IsChromeOrResourceURI(nsIURI* aURI)
+{
+ bool isChrome = false;
+ bool isResource = false;
+ if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) &&
+ NS_SUCCEEDED(aURI->SchemeIs("resource", &isResource)))
+ return (isChrome || isResource);
+ return false;
+}
+
+
+// This function loads a particular XBL file and installs all of the bindings
+// onto the element.
+nsresult
+nsXBLService::LoadBindings(nsIContent* aContent, nsIURI* aURL,
+ nsIPrincipal* aOriginPrincipal,
+ nsXBLBinding** aBinding, bool* aResolveStyle)
+{
+ NS_PRECONDITION(aOriginPrincipal, "Must have an origin principal");
+
+ *aBinding = nullptr;
+ *aResolveStyle = false;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIDocument> document = aContent->OwnerDoc();
+
+ nsAutoCString urlspec;
+ bool ok = nsContentUtils::GetWrapperSafeScriptFilename(document, aURL,
+ urlspec, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (ok) {
+ // Block an attempt to load a binding that has special wrapper
+ // automation needs.
+ return NS_OK;
+ }
+
+ nsXBLBinding *binding = aContent->GetXBLBinding();
+ if (binding) {
+ if (binding->MarkedForDeath()) {
+ FlushStyleBindings(aContent);
+ binding = nullptr;
+ }
+ else {
+ // See if the URIs match.
+ if (binding->PrototypeBinding()->CompareBindingURI(aURL))
+ return NS_OK;
+ FlushStyleBindings(aContent);
+ binding = nullptr;
+ }
+ }
+
+ bool ready;
+ RefPtr<nsXBLBinding> newBinding;
+ if (NS_FAILED(rv = GetBinding(aContent, aURL, false, aOriginPrincipal,
+ &ready, getter_AddRefs(newBinding)))) {
+ return rv;
+ }
+
+ if (!newBinding) {
+#ifdef DEBUG
+ 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: ") + aURL->GetSpecOrDefault());
+ NS_ERROR(str.get());
+#endif
+ return NS_OK;
+ }
+
+ if (::IsAncestorBinding(document, aURL, aContent)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // We loaded a style binding. It goes on the end.
+ if (binding) {
+ // Get the last binding that is in the append layer.
+ binding->RootBinding()->SetBaseBinding(newBinding);
+ }
+ else {
+ // Install the binding on the content node.
+ aContent->SetXBLBinding(newBinding);
+ }
+
+ {
+ nsAutoScriptBlocker scriptBlocker;
+
+ // Set the binding's bound element.
+ newBinding->SetBoundElement(aContent);
+
+ // Tell the binding to build the anonymous content.
+ newBinding->GenerateAnonymousContent();
+
+ // Tell the binding to install event handlers
+ newBinding->InstallEventHandlers();
+
+ // Set up our properties
+ rv = newBinding->InstallImplementation();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Figure out if we have any scoped sheets. If so, we do a second resolve.
+ *aResolveStyle = newBinding->HasStyleSheets();
+
+ newBinding.forget(aBinding);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLService::FlushStyleBindings(nsIContent* aContent)
+{
+ nsCOMPtr<nsIDocument> document = aContent->OwnerDoc();
+
+ nsXBLBinding *binding = aContent->GetXBLBinding();
+ if (binding) {
+ // Clear out the script references.
+ binding->ChangeDocument(document, nullptr);
+
+ aContent->SetXBLBinding(nullptr); // Flush old style bindings
+ }
+
+ return NS_OK;
+}
+
+//
+// AttachGlobalKeyHandler
+//
+// Creates a new key handler and prepares to listen to key events on the given
+// event receiver (either a document or an content node). If the receiver is content,
+// then extra work needs to be done to hook it up to the document (XXX WHY??)
+//
+nsresult
+nsXBLService::AttachGlobalKeyHandler(EventTarget* aTarget)
+{
+ // check if the receiver is a content node (not a document), and hook
+ // it to the document if that is the case.
+ nsCOMPtr<EventTarget> piTarget = aTarget;
+ nsCOMPtr<nsIContent> contentNode(do_QueryInterface(aTarget));
+ if (contentNode) {
+ // Only attach if we're really in a document
+ nsCOMPtr<nsIDocument> doc = contentNode->GetUncomposedDoc();
+ if (doc)
+ piTarget = doc; // We're a XUL keyset. Attach to our document.
+ }
+
+ if (!piTarget)
+ return NS_ERROR_FAILURE;
+
+ EventListenerManager* manager = piTarget->GetOrCreateListenerManager();
+ if (!manager)
+ return NS_ERROR_FAILURE;
+
+ // the listener already exists, so skip this
+ if (contentNode && contentNode->GetProperty(nsGkAtoms::listener))
+ return NS_OK;
+
+ nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(contentNode));
+
+ // Create the key handler
+ RefPtr<nsXBLWindowKeyHandler> handler =
+ NS_NewXBLWindowKeyHandler(elt, piTarget);
+
+ handler->InstallKeyboardEventListenersTo(manager);
+
+ if (contentNode)
+ return contentNode->SetProperty(nsGkAtoms::listener,
+ handler.forget().take(),
+ nsPropertyTable::SupportsDtorFunc, true);
+
+ // The reference to the handler will be maintained by the event target,
+ // and, if there is a content node, the property.
+ return NS_OK;
+}
+
+//
+// DetachGlobalKeyHandler
+//
+// Removes a key handler added by DeatchGlobalKeyHandler.
+//
+nsresult
+nsXBLService::DetachGlobalKeyHandler(EventTarget* aTarget)
+{
+ nsCOMPtr<EventTarget> piTarget = aTarget;
+ nsCOMPtr<nsIContent> contentNode(do_QueryInterface(aTarget));
+ if (!contentNode) // detaching is only supported for content nodes
+ return NS_ERROR_FAILURE;
+
+ // Only attach if we're really in a document
+ nsCOMPtr<nsIDocument> doc = contentNode->GetUncomposedDoc();
+ if (doc)
+ piTarget = do_QueryInterface(doc);
+
+ if (!piTarget)
+ return NS_ERROR_FAILURE;
+
+ EventListenerManager* manager = piTarget->GetOrCreateListenerManager();
+ if (!manager)
+ return NS_ERROR_FAILURE;
+
+ nsIDOMEventListener* handler =
+ static_cast<nsIDOMEventListener*>(contentNode->GetProperty(nsGkAtoms::listener));
+ if (!handler)
+ return NS_ERROR_FAILURE;
+
+ static_cast<nsXBLWindowKeyHandler*>(handler)->
+ RemoveKeyboardEventListenersFrom(manager);
+
+ contentNode->DeleteProperty(nsGkAtoms::listener);
+
+ return NS_OK;
+}
+
+// Internal helper methods ////////////////////////////////////////////////////////////////
+
+nsresult
+nsXBLService::BindingReady(nsIContent* aBoundElement,
+ nsIURI* aURI,
+ bool* aIsReady)
+{
+ // Don't do a security check here; we know this binding is set to go.
+ return GetBinding(aBoundElement, aURI, true, nullptr, aIsReady, nullptr);
+}
+
+nsresult
+nsXBLService::GetBinding(nsIContent* aBoundElement, nsIURI* aURI,
+ bool aPeekOnly, nsIPrincipal* aOriginPrincipal,
+ bool* aIsReady, nsXBLBinding** aResult)
+{
+ // More than 6 binding URIs are rare, see bug 55070 comment 18.
+ AutoTArray<nsCOMPtr<nsIURI>, 6> uris;
+ return GetBinding(aBoundElement, aURI, aPeekOnly, aOriginPrincipal, aIsReady,
+ aResult, uris);
+}
+
+static bool
+MayBindToContent(nsXBLPrototypeBinding* aProtoBinding, nsIContent* aBoundElement,
+ nsIURI* aURI)
+{
+ // If this binding explicitly allows untrusted content, we're done.
+ if (aProtoBinding->BindToUntrustedContent()) {
+ return true;
+ }
+
+ // We let XUL content and content in XUL documents through, since XUL is
+ // restricted anyway and we want to minimize remote XUL breakage.
+ if (aBoundElement->IsXULElement() ||
+ aBoundElement->OwnerDoc()->IsXULElement()) {
+ return true;
+ }
+
+ // Similarly, we make an exception for anonymous content (which
+ // lives in the XBL scope), because it's already protected from content,
+ // and tends to use a lot of bindings that we wouldn't otherwise need to
+ // whitelist.
+ if (aBoundElement->IsInAnonymousSubtree()) {
+ return true;
+ }
+
+ // Allow if the bound content subsumes the binding.
+ nsCOMPtr<nsIDocument> bindingDoc = aProtoBinding->XBLDocumentInfo()->GetDocument();
+ NS_ENSURE_TRUE(bindingDoc, false);
+ if (aBoundElement->NodePrincipal()->Subsumes(bindingDoc->NodePrincipal())) {
+ return true;
+ }
+
+ // One last special case: we need to watch out for in-document data: URI
+ // bindings from remote-XUL-whitelisted domains (especially tests), because
+ // they end up with a null principal (rather than inheriting the document's
+ // principal), which causes them to fail the check above.
+ if (nsContentUtils::AllowXULXBLForPrincipal(aBoundElement->NodePrincipal())) {
+ bool isDataURI = false;
+ nsresult rv = aURI->SchemeIs("data", &isDataURI);
+ NS_ENSURE_SUCCESS(rv, false);
+ if (isDataURI) {
+ return true;
+ }
+ }
+
+ // Disallow.
+ return false;
+}
+
+nsresult
+nsXBLService::GetBinding(nsIContent* aBoundElement, nsIURI* aURI,
+ bool aPeekOnly, nsIPrincipal* aOriginPrincipal,
+ bool* aIsReady, nsXBLBinding** aResult,
+ nsTArray<nsCOMPtr<nsIURI>>& aDontExtendURIs)
+{
+ NS_ASSERTION(aPeekOnly || aResult,
+ "Must have non-null out param if not just peeking to see "
+ "whether the binding is ready");
+
+ if (aResult)
+ *aResult = nullptr;
+
+ if (!aURI)
+ return NS_ERROR_FAILURE;
+
+ nsAutoCString ref;
+ aURI->GetRef(ref);
+
+ nsCOMPtr<nsIDocument> boundDocument = aBoundElement->OwnerDoc();
+
+ RefPtr<nsXBLDocumentInfo> docInfo;
+ nsresult rv = LoadBindingDocumentInfo(aBoundElement, boundDocument, aURI,
+ aOriginPrincipal,
+ false, getter_AddRefs(docInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!docInfo)
+ return NS_ERROR_FAILURE;
+
+ WeakPtr<nsXBLPrototypeBinding> protoBinding =
+ docInfo->GetPrototypeBinding(ref);
+
+ if (!protoBinding) {
+#ifdef DEBUG
+ nsAutoCString message("Unable to locate an XBL binding for URI ");
+ message += aURI->GetSpecOrDefault();
+ message += " in document ";
+ message += boundDocument->GetDocumentURI()->GetSpecOrDefault();
+ NS_WARNING(message.get());
+#endif
+ return NS_ERROR_FAILURE;
+ }
+
+ // If the binding isn't whitelisted, refuse to apply it to content that
+ // doesn't subsume it (modulo a few exceptions).
+ if (!MayBindToContent(protoBinding, aBoundElement, aURI)) {
+#ifdef DEBUG
+ nsAutoCString message("Permission denied to apply binding ");
+ message += aURI->GetSpecOrDefault();
+ message += " to unprivileged content. Set bindToUntrustedContent=true on "
+ "the binding to override this restriction.";
+ NS_WARNING(message.get());
+#endif
+ return NS_ERROR_FAILURE;
+ }
+
+ aDontExtendURIs.AppendElement(protoBinding->BindingURI());
+ nsCOMPtr<nsIURI> altBindingURI = protoBinding->AlternateBindingURI();
+ if (altBindingURI) {
+ aDontExtendURIs.AppendElement(altBindingURI);
+ }
+
+ // Our prototype binding must have all its resources loaded.
+ bool ready = protoBinding->LoadResources();
+ if (!ready) {
+ // Add our bound element to the protos list of elts that should
+ // be notified when the stylesheets and scripts finish loading.
+ protoBinding->AddResourceListener(aBoundElement);
+ return NS_ERROR_FAILURE; // The binding isn't ready yet.
+ }
+
+ rv = protoBinding->ResolveBaseBinding();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> baseBindingURI;
+ WeakPtr<nsXBLPrototypeBinding> baseProto = protoBinding->GetBasePrototype();
+ if (baseProto) {
+ baseBindingURI = baseProto->BindingURI();
+ }
+ else {
+ baseBindingURI = protoBinding->GetBaseBindingURI();
+ if (baseBindingURI) {
+ uint32_t count = aDontExtendURIs.Length();
+ for (uint32_t index = 0; index < count; ++index) {
+ bool equal;
+ rv = aDontExtendURIs[index]->Equals(baseBindingURI, &equal);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (equal) {
+ NS_ConvertUTF8toUTF16
+ protoSpec(protoBinding->BindingURI()->GetSpecOrDefault());
+ NS_ConvertUTF8toUTF16 baseSpec(baseBindingURI->GetSpecOrDefault());
+ const char16_t* params[] = { protoSpec.get(), baseSpec.get() };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("XBL"), nullptr,
+ nsContentUtils::eXBL_PROPERTIES,
+ "CircularExtendsBinding",
+ params, ArrayLength(params),
+ boundDocument->GetDocumentURI());
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+ }
+ }
+
+ RefPtr<nsXBLBinding> baseBinding;
+ if (baseBindingURI) {
+ nsCOMPtr<nsIContent> child = protoBinding->GetBindingElement();
+ rv = GetBinding(aBoundElement, baseBindingURI, aPeekOnly,
+ child->NodePrincipal(), aIsReady,
+ getter_AddRefs(baseBinding), aDontExtendURIs);
+ if (NS_FAILED(rv))
+ return rv; // We aren't ready yet.
+ }
+
+ *aIsReady = true;
+
+ if (!aPeekOnly) {
+ // Make a new binding
+ NS_ENSURE_STATE(protoBinding);
+ nsXBLBinding *newBinding = new nsXBLBinding(protoBinding);
+
+ if (baseBinding) {
+ if (!baseProto) {
+ protoBinding->SetBasePrototype(baseBinding->PrototypeBinding());
+ }
+ newBinding->SetBaseBinding(baseBinding);
+ }
+
+ NS_ADDREF(*aResult = newBinding);
+ }
+
+ return NS_OK;
+}
+
+static bool
+IsSystemOrChromeURLPrincipal(nsIPrincipal* aPrincipal)
+{
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ return true;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ aPrincipal->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_TRUE(uri, false);
+
+ bool isChrome = false;
+ return NS_SUCCEEDED(uri->SchemeIs("chrome", &isChrome)) && isChrome;
+}
+
+nsresult
+nsXBLService::LoadBindingDocumentInfo(nsIContent* aBoundElement,
+ nsIDocument* aBoundDocument,
+ nsIURI* aBindingURI,
+ nsIPrincipal* aOriginPrincipal,
+ bool aForceSyncLoad,
+ nsXBLDocumentInfo** aResult)
+{
+ NS_PRECONDITION(aBindingURI, "Must have a binding URI");
+ NS_PRECONDITION(!aOriginPrincipal || aBoundDocument,
+ "If we're doing a security check, we better have a document!");
+
+ *aResult = nullptr;
+ // Allow XBL in unprivileged documents if it's specified in a privileged or
+ // chrome: stylesheet. This allows themes to specify XBL bindings.
+ if (aOriginPrincipal && !IsSystemOrChromeURLPrincipal(aOriginPrincipal)) {
+ NS_ENSURE_TRUE(!aBoundDocument || aBoundDocument->AllowXULXBL(),
+ NS_ERROR_XBL_BLOCKED);
+ }
+
+ RefPtr<nsXBLDocumentInfo> info;
+
+ nsCOMPtr<nsIURI> documentURI;
+ nsresult rv = aBindingURI->CloneIgnoringRef(getter_AddRefs(documentURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsBindingManager *bindingManager = nullptr;
+
+ // The first thing to check is the binding manager, which (if it exists)
+ // should have a reference to the nsXBLDocumentInfo if this document
+ // has ever loaded this binding before.
+ if (aBoundDocument) {
+ bindingManager = aBoundDocument->BindingManager();
+ info = bindingManager->GetXBLDocumentInfo(documentURI);
+ if (aBoundDocument->IsStaticDocument() &&
+ IsChromeOrResourceURI(aBindingURI)) {
+ aForceSyncLoad = true;
+ }
+ }
+
+ // It's possible the document is already being loaded. If so, there's no
+ // document yet, but we need to glom on our request so that it will be
+ // processed whenever the doc does finish loading.
+ NodeInfo *ni = nullptr;
+ if (aBoundElement)
+ ni = aBoundElement->NodeInfo();
+
+ if (!info && bindingManager &&
+ (!ni || !(ni->Equals(nsGkAtoms::scrollbar, kNameSpaceID_XUL) ||
+ ni->Equals(nsGkAtoms::thumb, kNameSpaceID_XUL) ||
+ ((ni->Equals(nsGkAtoms::input) ||
+ ni->Equals(nsGkAtoms::select)) &&
+ aBoundElement->IsHTMLElement()))) && !aForceSyncLoad) {
+ nsCOMPtr<nsIStreamListener> listener;
+ if (bindingManager)
+ listener = bindingManager->GetLoadingDocListener(documentURI);
+ if (listener) {
+ nsXBLStreamListener* xblListener =
+ static_cast<nsXBLStreamListener*>(listener.get());
+ // Create a new load observer.
+ if (!xblListener->HasRequest(aBindingURI, aBoundElement)) {
+ nsXBLBindingRequest* req = new nsXBLBindingRequest(aBindingURI, aBoundElement);
+ xblListener->AddRequest(req);
+ }
+ return NS_OK;
+ }
+ }
+
+#ifdef MOZ_XUL
+ // The second line of defense is the global nsXULPrototypeCache,
+ // if it's being used.
+ nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
+ bool useXULCache = cache && cache->IsEnabled();
+
+ if (!info && useXULCache) {
+ // This cache crosses the entire product, so that any XBL bindings that are
+ // part of chrome will be reused across all XUL documents.
+ info = cache->GetXBLDocumentInfo(documentURI);
+ }
+
+ bool useStartupCache = useXULCache && IsChromeOrResourceURI(documentURI);
+
+ if (!info) {
+ // Next, look in the startup cache
+ if (!info && useStartupCache) {
+ rv = nsXBLDocumentInfo::ReadPrototypeBindings(documentURI, getter_AddRefs(info));
+ if (NS_SUCCEEDED(rv)) {
+ cache->PutXBLDocumentInfo(info);
+ }
+ }
+ }
+#endif
+
+ if (!info) {
+ // Finally, if all lines of defense fail, we go and fetch the binding
+ // document.
+
+ // Always load chrome synchronously
+ bool chrome;
+ if (NS_SUCCEEDED(documentURI->SchemeIs("chrome", &chrome)) && chrome)
+ aForceSyncLoad = true;
+
+ nsCOMPtr<nsIDocument> document;
+ rv = FetchBindingDocument(aBoundElement, aBoundDocument, documentURI,
+ aBindingURI, aOriginPrincipal, aForceSyncLoad,
+ getter_AddRefs(document));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (document) {
+ nsBindingManager *xblDocBindingManager = document->BindingManager();
+ info = xblDocBindingManager->GetXBLDocumentInfo(documentURI);
+ if (!info) {
+ NS_ERROR("An XBL file is malformed. Did you forget the XBL namespace on the bindings tag?");
+ return NS_ERROR_FAILURE;
+ }
+ xblDocBindingManager->RemoveXBLDocumentInfo(info); // Break the self-imposed cycle.
+
+ // If the doc is a chrome URI, then we put it into the XUL cache.
+#ifdef MOZ_XUL
+ if (useStartupCache) {
+ cache->PutXBLDocumentInfo(info);
+
+ // now write the bindings into the startup cache
+ info->WritePrototypeBindings();
+ }
+#endif
+ }
+ }
+
+ if (info && bindingManager) {
+ // Cache it in our binding manager's document table. This way,
+ // we can ensure that if the document has loaded this binding
+ // before, it can continue to use it even if the XUL prototype
+ // cache gets flushed. That way, if a flush does occur, we
+ // don't get into a weird state where we're using different
+ // XBLDocumentInfos for the same XBL document in a single
+ // document that has loaded some bindings.
+ bindingManager->PutXBLDocumentInfo(info);
+ }
+
+ info.forget(aResult);
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLService::FetchBindingDocument(nsIContent* aBoundElement, nsIDocument* aBoundDocument,
+ nsIURI* aDocumentURI, nsIURI* aBindingURI,
+ nsIPrincipal* aOriginPrincipal, bool aForceSyncLoad,
+ nsIDocument** aResult)
+{
+ nsresult rv = NS_OK;
+ // Initialize our out pointer to nullptr
+ *aResult = nullptr;
+
+ // Now we have to synchronously load the binding file.
+ // Create an XML content sink and a parser.
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ if (aBoundDocument)
+ loadGroup = aBoundDocument->GetDocumentLoadGroup();
+
+ // We really shouldn't have to force a sync load for anything here... could
+ // we get away with not doing that? Not sure.
+ if (IsChromeOrResourceURI(aDocumentURI))
+ aForceSyncLoad = true;
+
+ // Create document and contentsink and set them up.
+ nsCOMPtr<nsIDocument> doc;
+ rv = NS_NewXMLDocument(getter_AddRefs(doc));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIXMLContentSink> xblSink;
+ rv = NS_NewXBLContentSink(getter_AddRefs(xblSink), doc, aDocumentURI, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Open channel
+ // Note: There are some cases where aOriginPrincipal and aBoundDocument are purposely
+ // set to null (to bypass security checks) when calling LoadBindingDocumentInfo() which calls
+ // FetchBindingDocument(). LoadInfo will end up with no principal or node in those cases,
+ // so we use systemPrincipal. This achieves the same result of bypassing security checks,
+ // but it gives the wrong information to potential future consumers of loadInfo.
+ nsCOMPtr<nsIChannel> channel;
+
+ if (aOriginPrincipal) {
+ // if there is an originPrincipal we should also have aBoundDocument
+ MOZ_ASSERT(aBoundDocument, "can not create a channel without aBoundDocument");
+
+ rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(channel),
+ aDocumentURI,
+ aBoundDocument,
+ aOriginPrincipal,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS |
+ nsILoadInfo::SEC_ALLOW_CHROME,
+ nsIContentPolicy::TYPE_XBL,
+ loadGroup);
+ }
+ else {
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ aDocumentURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS,
+ nsIContentPolicy::TYPE_XBL,
+ loadGroup);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aForceSyncLoad) {
+ // We can be asynchronous
+ nsXBLStreamListener* xblListener =
+ new nsXBLStreamListener(aBoundDocument, xblSink, doc);
+
+ // Add ourselves to the list of loading docs.
+ nsBindingManager *bindingManager;
+ if (aBoundDocument)
+ bindingManager = aBoundDocument->BindingManager();
+ else
+ bindingManager = nullptr;
+
+ if (bindingManager)
+ bindingManager->PutLoadingDocListener(aDocumentURI, xblListener);
+
+ // Add our request.
+ nsXBLBindingRequest* req = new nsXBLBindingRequest(aBindingURI,
+ aBoundElement);
+ xblListener->AddRequest(req);
+
+ // Now kick off the async read.
+ rv = channel->AsyncOpen2(xblListener);
+ if (NS_FAILED(rv)) {
+ // Well, we won't be getting a load. Make sure to clean up our stuff!
+ if (bindingManager) {
+ bindingManager->RemoveLoadingDocListener(aDocumentURI);
+ }
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = doc->StartDocumentLoad("loadAsInteractiveData",
+ channel,
+ loadGroup,
+ nullptr,
+ getter_AddRefs(listener),
+ true,
+ xblSink);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now do a blocking synchronous parse of the file.
+ nsCOMPtr<nsIInputStream> in;
+ rv = channel->Open2(getter_AddRefs(in));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsSyncLoadService::PushSyncStreamToListener(in, listener, channel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ doc.swap(*aResult);
+
+ return NS_OK;
+}