/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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; } nsAutoCString spec; aChildBindingURI->GetSpec(spec); NS_ConvertUTF8toUTF16 bindingURI(spec); 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 mBindingURI; nsCOMPtr 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->GetCurrentDoc(); 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 mInner; nsAutoTArray mBindingRequests; nsCOMPtr mBoundDocument; nsCOMPtr mSink; // Only set until OnStartRequest nsCOMPtr 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 sink; mSink.swap(sink); nsCOMPtr doc; mBindingDocument.swap(doc); nsCOMPtr channel = do_QueryInterface(request); NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED); nsCOMPtr 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 bindingDocument = do_QueryInterface(target); NS_ASSERTION(bindingDocument, "Event not targeted at document?!"); // See if we're still alive. nsCOMPtr 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 // 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->GetCurrentDoc(); 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(); nsRefPtr 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 ///////////////////////////////////////////////////////////////// // Static member variable initialization bool nsXBLService::gAllowDataURIs = false; // Implement our nsISupports methods NS_IMPL_ISUPPORTS(nsXBLService, nsISupportsWeakReference) void nsXBLService::Init() { gInstance = new nsXBLService(); NS_ADDREF(gInstance); } // Constructors/Destructors nsXBLService::nsXBLService(void) { Preferences::AddBoolVarCache(&gAllowDataURIs, "layout.debug.enable_data_xbl"); } 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 document = aContent->OwnerDoc(); nsAutoCString urlspec; if (nsContentUtils::GetWrapperSafeScriptFilename(document, aURL, urlspec)) { // 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; nsRefPtr newBinding; if (NS_FAILED(rv = GetBinding(aContent, aURL, false, aOriginPrincipal, &ready, getter_AddRefs(newBinding)))) { return rv; } if (!newBinding) { #ifdef DEBUG nsAutoCString spec; aURL->GetSpec(spec); 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); 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.swap(*aBinding); } return NS_OK; } nsresult nsXBLService::FlushStyleBindings(nsIContent* aContent) { nsCOMPtr 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 piTarget = aTarget; nsCOMPtr contentNode(do_QueryInterface(aTarget)); if (contentNode) { // Only attach if we're really in a document nsCOMPtr doc = contentNode->GetCurrentDoc(); if (doc) piTarget = doc; // We're a XUL keyset. Attach to our document. } EventListenerManager* manager = piTarget->GetOrCreateListenerManager(); if (!piTarget || !manager) return NS_ERROR_FAILURE; // the listener already exists, so skip this if (contentNode && contentNode->GetProperty(nsGkAtoms::listener)) return NS_OK; nsCOMPtr elt(do_QueryInterface(contentNode)); // Create the key handler nsRefPtr handler = NS_NewXBLWindowKeyHandler(elt, piTarget); // listen to these events manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keydown"), TrustedEventsAtSystemGroupBubble()); manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keyup"), TrustedEventsAtSystemGroupBubble()); manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keypress"), TrustedEventsAtSystemGroupBubble()); // The capturing listener is only used for XUL keysets to properly handle // shortcut keys in a multi-process environment. manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keydown"), TrustedEventsAtSystemGroupCapture()); manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keyup"), TrustedEventsAtSystemGroupCapture()); manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keypress"), TrustedEventsAtSystemGroupCapture()); 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 piTarget = aTarget; nsCOMPtr 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 doc = contentNode->GetCurrentDoc(); if (doc) piTarget = do_QueryInterface(doc); EventListenerManager* manager = piTarget->GetOrCreateListenerManager(); if (!piTarget || !manager) return NS_ERROR_FAILURE; nsIDOMEventListener* handler = static_cast(contentNode->GetProperty(nsGkAtoms::listener)); if (!handler) return NS_ERROR_FAILURE; manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keydown"), TrustedEventsAtSystemGroupBubble()); manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keyup"), TrustedEventsAtSystemGroupBubble()); manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keypress"), TrustedEventsAtSystemGroupBubble()); manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keydown"), TrustedEventsAtSystemGroupCapture()); manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keyup"), TrustedEventsAtSystemGroupCapture()); manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keypress"), TrustedEventsAtSystemGroupCapture()); 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. nsAutoTArray 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 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& 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 boundDocument = aBoundElement->OwnerDoc(); nsRefPtr docInfo; nsresult rv = LoadBindingDocumentInfo(aBoundElement, boundDocument, aURI, aOriginPrincipal, false, getter_AddRefs(docInfo)); NS_ENSURE_SUCCESS(rv, rv); if (!docInfo) return NS_ERROR_FAILURE; nsXBLPrototypeBinding* protoBinding = docInfo->GetPrototypeBinding(ref); if (!protoBinding) { #ifdef DEBUG nsAutoCString uriSpec; aURI->GetSpec(uriSpec); nsAutoCString doc; boundDocument->GetDocumentURI()->GetSpec(doc); nsAutoCString message("Unable to locate an XBL binding for URI "); message += uriSpec; message += " in document "; message += doc; 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 uriSpec; aURI->GetSpec(uriSpec); nsAutoCString message("Permission denied to apply binding "); message += uriSpec; 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 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); nsIURI* baseBindingURI; 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) { nsAutoCString spec, basespec; protoBinding->BindingURI()->GetSpec(spec); NS_ConvertUTF8toUTF16 protoSpec(spec); baseBindingURI->GetSpec(basespec); NS_ConvertUTF8toUTF16 baseSpecUTF16(basespec); const char16_t* params[] = { protoSpec.get(), baseSpecUTF16.get() }; nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, NS_LITERAL_CSTRING("XBL"), nullptr, nsContentUtils::eXBL_PROPERTIES, "CircularExtendsBinding", params, ArrayLength(params), boundDocument->GetDocumentURI()); return NS_ERROR_ILLEGAL_VALUE; } } } } nsRefPtr baseBinding; if (baseBindingURI) { 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 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 SchemeIs(nsIURI* aURI, const char* aScheme) { nsCOMPtr baseURI = NS_GetInnermostURI(aURI); NS_ENSURE_TRUE(baseURI, false); bool isScheme = false; return NS_SUCCEEDED(baseURI->SchemeIs(aScheme, &isScheme)) && isScheme; } static bool IsSystemOrChromeURLPrincipal(nsIPrincipal* aPrincipal) { if (nsContentUtils::IsSystemPrincipal(aPrincipal)) { return true; } nsCOMPtr 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!"); nsresult rv; if (aOriginPrincipal) { // Security check - Enforce same-origin policy, except to chrome. // We have to be careful to not pass aContent as the context here. // Otherwise, if there is a JS-implemented content policy, we will attempt // to wrap the content node, which will try to load XBL bindings for it, if // any. Since we're not done loading this binding yet, that will reenter // this method and we'll end up creating a binding and then immediately // clobbering it in our table. That makes things very confused, leading to // misbehavior and crashes. rv = nsContentUtils:: CheckSecurityBeforeLoad(aBindingURI, aOriginPrincipal, nsIScriptSecurityManager::ALLOW_CHROME, gAllowDataURIs, nsIContentPolicy::TYPE_XBL, aBoundDocument); NS_ENSURE_SUCCESS(rv, NS_ERROR_XBL_BLOCKED); if (!IsSystemOrChromeURLPrincipal(aOriginPrincipal)) { // Also make sure that we're same-origin with the bound document // except if the stylesheet has the system principal. if (!(gAllowDataURIs && SchemeIs(aBindingURI, "data")) && !SchemeIs(aBindingURI, "chrome")) { rv = aBoundDocument->NodePrincipal()->CheckMayLoad(aBindingURI, true, false); NS_ENSURE_SUCCESS(rv, NS_ERROR_XBL_BLOCKED); } // Finally check if this document is allowed to use XBL at all. NS_ENSURE_TRUE(aBoundDocument->AllowXULXBL(), NS_ERROR_XBL_BLOCKED); } } *aResult = nullptr; nsRefPtr info; nsCOMPtr documentURI; rv = aBindingURI->CloneIgnoringRef(getter_AddRefs(documentURI)); NS_ENSURE_SUCCESS(rv, rv); #ifdef MOZ_XUL // We've got a file. Check our XBL document cache. nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance(); bool useXULCache = cache && cache->IsEnabled(); if (useXULCache) { // The first line of defense is the chrome cache. // 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); } #endif if (!info) { // The second line of defense is the binding manager's document table. nsBindingManager *bindingManager = nullptr; if (aBoundDocument) { bindingManager = aBoundDocument->BindingManager(); info = bindingManager->GetXBLDocumentInfo(documentURI); if (aBoundDocument->IsStaticDocument() && IsChromeOrResourceURI(aBindingURI)) { aForceSyncLoad = true; } } 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) { // The third line of defense is to investigate whether or not the // document is currently being loaded asynchronously. 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. nsCOMPtr listener; if (bindingManager) listener = bindingManager->GetLoadingDocListener(documentURI); if (listener) { nsXBLStreamListener* xblListener = static_cast(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 // Next, look in the startup cache bool useStartupCache = useXULCache && IsChromeOrResourceURI(documentURI); if (!info && useStartupCache) { rv = nsXBLDocumentInfo::ReadPrototypeBindings(documentURI, getter_AddRefs(info)); if (NS_SUCCEEDED(rv)) { cache->PutXBLDocumentInfo(info); if (bindingManager) { // Cache it in our binding manager's document table. bindingManager->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 document; FetchBindingDocument(aBoundElement, aBoundDocument, documentURI, aBindingURI, aOriginPrincipal, aForceSyncLoad, getter_AddRefs(document)); 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 (bindingManager) { // Also put it in our binding manager's document table. 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 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 doc; rv = NS_NewXMLDocument(getter_AddRefs(doc)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 channel; if (aOriginPrincipal) { // if there is an originPrincipal we should also have aBoundDocument NS_ASSERTION(aBoundDocument, "can not create a channel without aBoundDocument"); rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(channel), aDocumentURI, aBoundDocument, aOriginPrincipal, nsILoadInfo::SEC_NORMAL, nsIContentPolicy::TYPE_OTHER, loadGroup); } else { rv = NS_NewChannel(getter_AddRefs(channel), aDocumentURI, nsContentUtils::GetSystemPrincipal(), nsILoadInfo::SEC_NORMAL, nsIContentPolicy::TYPE_OTHER, loadGroup); } NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr sameOriginChecker = nsContentUtils::SameOriginChecker(); channel->SetNotificationCallbacks(sameOriginChecker); 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->AsyncOpen(xblListener, nullptr); 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 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 in; rv = channel->Open(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; }