/* -*- 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 "mozilla/ComputedStyle.h" #include "nsCOMPtr.h" #include "nsNetUtil.h" #include "nsXBLService.h" #include "nsXBLWindowKeyHandler.h" #include "nsIInputStream.h" #include "nsNameSpaceManager.h" #include "nsIURI.h" #include "nsIURL.h" #include "nsIChannel.h" #include "nsString.h" #include "plstr.h" #include "nsIContent.h" #include "mozilla/dom/Document.h" #include "nsIXMLContentSink.h" #include "nsContentCID.h" #include "mozilla/dom/XMLDocument.h" #include "nsGkAtoms.h" #include "nsIObserverService.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 "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/ServoStyleSet.h" #include "mozilla/RestyleManager.h" #include "mozilla/dom/ChildIterator.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(Document* 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 mBindingURI; nsCOMPtr mBoundElement; void DocumentLoaded(Document* aBindingDoc) { // We only need the document here to cause frame construction, so // we need the current doc, not the owner doc. Document* doc = mBoundElement->GetUncomposedDoc(); if (!doc) return; // Get the binding. bool ready = false; nsXBLService::GetInstance()->BindingReady(mBoundElement, mBindingURI, &ready); if (!ready) return; // Destroy the frames for mBoundElement. Do this after getting the binding, // since if the binding fetch fails then we don't want to destroy the // frames. if (nsIPresShell* shell = doc->GetShell()) { shell->DestroyFramesForAndRestyle(mBoundElement->AsElement()); } MOZ_ASSERT(!mBoundElement->GetPrimaryFrame()); } 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(Document* aBoundDocument, nsIXMLContentSink* aSink, Document* aBindingDocument); void AddRequest(nsXBLBindingRequest* aRequest) { mBindingRequests.AppendElement(aRequest); } bool HasRequest(nsIURI* aURI, nsIContent* aBoundElement); private: ~nsXBLStreamListener(); nsCOMPtr mInner; AutoTArray mBindingRequests; nsWeakPtr mBoundDocument; nsCOMPtr mSink; // Only set until OnStartRequest nsCOMPtr mBindingDocument; // Only set until OnStartRequest }; /* Implementation file */ NS_IMPL_ISUPPORTS(nsXBLStreamListener, nsIStreamListener, nsIRequestObserver, nsIDOMEventListener) nsXBLStreamListener::nsXBLStreamListener(Document* aBoundDocument, nsIXMLContentSink* aSink, Document* 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, nsIInputStream* aInStr, uint64_t aSourceOffset, uint32_t aCount) { if (mInner) return mInner->OnDataAvailable(request, aInStr, aSourceOffset, aCount); return NS_ERROR_FAILURE; } NS_IMETHODIMP nsXBLStreamListener::OnStartRequest(nsIRequest* request) { // 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); } NS_IMETHODIMP nsXBLStreamListener::OnStopRequest(nsIRequest* request, nsresult aStatus) { nsresult rv = NS_OK; if (mInner) { rv = mInner->OnStopRequest(request, 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(Event* 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 EventTarget* target = aEvent->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); Document* document = req->mBoundElement->GetUncomposedDoc(); if (document) document->FlushPendingNotifications(FlushType::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 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; } // Servo avoids wasting work styling subtrees of elements with XBL bindings by // default, so whenever we leave LoadBindings in a way that doesn't guarantee // that the subtree is styled we need to take care of doing it manually. static void EnsureSubtreeStyled(Element* aElement) { if (!aElement->HasServoData()) { return; } if (Servo_Element_IsDisplayNone(aElement)) { return; } nsIPresShell* presShell = aElement->OwnerDoc()->GetShell(); if (!presShell || !presShell->DidInitialize()) { return; } ServoStyleSet* servoSet = presShell->StyleSet(); StyleChildrenIterator iter(aElement); for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { Element* element = Element::FromNode(child); if (!element) { continue; } if (element->HasServoData()) { // If any child was styled, all of them should be styled already, so we // can bail out. return; } servoSet->StyleNewSubtree(element); } } // Ensures that EnsureSubtreeStyled is called on the element on destruction. class MOZ_RAII AutoEnsureSubtreeStyled { public: explicit AutoEnsureSubtreeStyled(Element* aElement) : mElement(aElement) {} ~AutoEnsureSubtreeStyled() { EnsureSubtreeStyled(mElement); } private: Element* mElement; }; // RAII class to restyle the XBL bound element when it shuffles the flat tree. class MOZ_RAII AutoStyleElement { public: AutoStyleElement(Element* aElement, bool* aResolveStyle) : mElement(aElement), mHadData(aElement->HasServoData()), mResolveStyle(aResolveStyle) { MOZ_ASSERT(mResolveStyle); if (mHadData) { RestyleManager::ClearServoDataFromSubtree( mElement, RestyleManager::IncludeRoot::No); } } ~AutoStyleElement() { nsIPresShell* presShell = mElement->OwnerDoc()->GetShell(); if (!mHadData || !presShell || !presShell->DidInitialize()) { return; } if (*mResolveStyle) { mElement->ClearServoData(); ServoStyleSet* servoSet = presShell->StyleSet(); servoSet->StyleNewSubtree(mElement); } } private: Element* mElement; bool mHadData; bool* mResolveStyle; }; 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; } // This function loads a particular XBL file and installs all of the bindings // onto the element. nsresult nsXBLService::LoadBindings(Element* aElement, nsIURI* aURL, nsIPrincipal* aOriginPrincipal, nsXBLBinding** aBinding, bool* aResolveStyle) { MOZ_ASSERT(aOriginPrincipal, "Must have an origin principal"); *aBinding = nullptr; *aResolveStyle = false; AutoEnsureSubtreeStyled subtreeStyled(aElement); if (MOZ_UNLIKELY(!aURL)) { return NS_OK; } #ifdef DEBUG // Ensures that XBL bindings are not used in the following conditions: // // 1) In the content process // 2) In a document that disallows XUL/XBL which only loads bindings // referenced in a chrome stylesheet. // // If the conditions are met, trigger an assertion since there shouldn't // be this kind of binding usage. // // The assertion might not catch all violations because (2) is needed // for the current test setup. Someone may unknownly using a binding // in AllowXULXBL() documents in content process in production without // knowing. if (XRE_IsContentProcess() && IsSystemOrChromeURLPrincipal(aOriginPrincipal) && aElement->OwnerDoc() && !aElement->OwnerDoc()->AllowXULXBL()) { MOZ_ASSERT(false, "Unexpected XBL binding used in the content process"); } #endif // Easy case: The binding was already loaded. nsXBLBinding* binding = aElement->GetXBLBinding(); if (binding && !binding->MarkedForDeath() && binding->PrototypeBinding()->CompareBindingURI(aURL)) { return NS_OK; } nsCOMPtr document = aElement->OwnerDoc(); nsAutoCString urlspec; nsresult rv = aURL->GetSpec(urlspec); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (binding) { FlushStyleBindings(aElement); binding = nullptr; } bool ready; RefPtr newBinding; if (NS_FAILED(rv = GetBinding(aElement, 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, aElement)) { return NS_ERROR_ILLEGAL_VALUE; } AutoStyleElement styleElement(aElement, aResolveStyle); // We loaded a style binding. It goes on the end. // Install the binding on the content node. aElement->SetXBLBinding(newBinding); { nsAutoScriptBlocker scriptBlocker; // Set the binding's bound element. newBinding->SetBoundElement(aElement); // 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; } void nsXBLService::FlushStyleBindings(Element* aElement) { nsCOMPtr document = aElement->OwnerDoc(); nsXBLBinding* binding = aElement->GetXBLBinding(); if (binding) { // Clear out the script references. binding->ChangeDocument(document, nullptr); aElement->SetXBLBinding(nullptr); // Flush old style bindings } } // // 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->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; Element* elt = Element::FromNodeOrNull(contentNode); // Create the key handler RefPtr 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 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->GetUncomposedDoc(); if (doc) piTarget = doc; if (!piTarget) return NS_ERROR_FAILURE; EventListenerManager* manager = piTarget->GetOrCreateListenerManager(); if (!manager) return NS_ERROR_FAILURE; nsIDOMEventListener* handler = static_cast( contentNode->GetProperty(nsGkAtoms::listener)); if (!handler) return NS_ERROR_FAILURE; static_cast(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, 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 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(); RefPtr docInfo; nsresult rv = LoadBindingDocumentInfo(aBoundElement, boundDocument, aURI, aOriginPrincipal, false, getter_AddRefs(docInfo)); NS_ENSURE_SUCCESS(rv, rv); if (!docInfo) return NS_ERROR_FAILURE; WeakPtr 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 altBindingURI = protoBinding->AlternateBindingURI(); if (altBindingURI) { aDontExtendURIs.AppendElement(altBindingURI); } // Our prototype binding must have all its resources loaded. bool ready = protoBinding->LoadResources(aBoundElement); 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 baseBindingURI; WeakPtr 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 baseBinding; if (baseBindingURI) { nsCOMPtr 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; } nsresult nsXBLService::LoadBindingDocumentInfo(nsIContent* aBoundElement, Document* aBoundDocument, nsIURI* aBindingURI, nsIPrincipal* aOriginPrincipal, bool aForceSyncLoad, nsXBLDocumentInfo** aResult) { MOZ_ASSERT(aBindingURI, "Must have a binding URI"); MOZ_ASSERT(!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 info; nsCOMPtr documentURI; nsresult rv = NS_GetURIWithoutRef(aBindingURI, 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 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 // 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), aBoundDocument); 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 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, Document* aBoundDocument, nsIURI* aDocumentURI, nsIURI* aBindingURI, nsIPrincipal* aOriginPrincipal, bool aForceSyncLoad, Document** 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); // XBL documents must allow XUL and XBL elements in them but the usual check // only checks if the document is loaded in the system principal which is // sometimes not the case. doc->ForceEnableXULXBL(); 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 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, nullptr, // aPerformanceStorage loadGroup); } else { rv = NS_NewChannel(getter_AddRefs(channel), aDocumentURI, nsContentUtils::GetSystemPrincipal(), nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS, nsIContentPolicy::TYPE_XBL, nullptr, // nsICookieSettings nullptr, // PerformanceStorage 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->AsyncOpen(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 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.forget(), listener, channel); NS_ENSURE_SUCCESS(rv, rv); doc.swap(*aResult); return NS_OK; }