diff options
Diffstat (limited to 'dom/xbl/nsXBLService.cpp')
-rw-r--r-- | dom/xbl/nsXBLService.cpp | 1090 |
1 files changed, 1090 insertions, 0 deletions
diff --git a/dom/xbl/nsXBLService.cpp b/dom/xbl/nsXBLService.cpp new file mode 100644 index 0000000000..1475b1368d --- /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; +} |