michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsXBLService.h" michael@0: #include "nsXBLWindowKeyHandler.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIURL.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "plstr.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIXMLContentSink.h" michael@0: #include "nsContentCID.h" michael@0: #include "mozilla/dom/XMLDocument.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIMemory.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIDOMNodeList.h" michael@0: #include "nsXBLContentSink.h" michael@0: #include "nsXBLBinding.h" michael@0: #include "nsXBLPrototypeBinding.h" michael@0: #include "nsXBLDocumentInfo.h" michael@0: #include "nsCRT.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsSyncLoadService.h" michael@0: #include "nsContentPolicyUtils.h" michael@0: #include "nsTArray.h" michael@0: #include "nsError.h" michael@0: michael@0: #include "nsIPresShell.h" michael@0: #include "nsIDocumentObserver.h" michael@0: #include "nsFrameManager.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIScriptError.h" michael@0: #include "nsXBLSerialize.h" michael@0: michael@0: #ifdef MOZ_XUL michael@0: #include "nsXULPrototypeCache.h" michael@0: #endif michael@0: #include "nsIDOMEventListener.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/EventListenerManager.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/dom/Event.h" michael@0: #include "mozilla/dom/Element.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: #define NS_MAX_XBL_BINDING_RECURSION 20 michael@0: michael@0: nsXBLService* nsXBLService::gInstance = nullptr; michael@0: michael@0: static bool michael@0: IsAncestorBinding(nsIDocument* aDocument, michael@0: nsIURI* aChildBindingURI, michael@0: nsIContent* aChild) michael@0: { michael@0: NS_ASSERTION(aDocument, "expected a document"); michael@0: NS_ASSERTION(aChildBindingURI, "expected a binding URI"); michael@0: NS_ASSERTION(aChild, "expected a child content"); michael@0: michael@0: uint32_t bindingRecursion = 0; michael@0: for (nsIContent *bindingParent = aChild->GetBindingParent(); michael@0: bindingParent; michael@0: bindingParent = bindingParent->GetBindingParent()) { michael@0: nsXBLBinding* binding = bindingParent->GetXBLBinding(); michael@0: if (!binding) { michael@0: continue; michael@0: } michael@0: michael@0: if (binding->PrototypeBinding()->CompareBindingURI(aChildBindingURI)) { michael@0: ++bindingRecursion; michael@0: if (bindingRecursion < NS_MAX_XBL_BINDING_RECURSION) { michael@0: continue; michael@0: } michael@0: nsAutoCString spec; michael@0: aChildBindingURI->GetSpec(spec); michael@0: NS_ConvertUTF8toUTF16 bindingURI(spec); michael@0: const char16_t* params[] = { bindingURI.get() }; michael@0: nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, michael@0: NS_LITERAL_CSTRING("XBL"), aDocument, michael@0: nsContentUtils::eXBL_PROPERTIES, michael@0: "TooDeepBindingRecursion", michael@0: params, ArrayLength(params)); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // Individual binding requests. michael@0: class nsXBLBindingRequest michael@0: { michael@0: public: michael@0: nsCOMPtr mBindingURI; michael@0: nsCOMPtr mBoundElement; michael@0: michael@0: void DocumentLoaded(nsIDocument* aBindingDoc) michael@0: { michael@0: // We only need the document here to cause frame construction, so michael@0: // we need the current doc, not the owner doc. michael@0: nsIDocument* doc = mBoundElement->GetCurrentDoc(); michael@0: if (!doc) michael@0: return; michael@0: michael@0: // Get the binding. michael@0: bool ready = false; michael@0: nsXBLService::GetInstance()->BindingReady(mBoundElement, mBindingURI, &ready); michael@0: if (!ready) michael@0: return; michael@0: michael@0: // If |mBoundElement| is (in addition to having binding |mBinding|) michael@0: // also a descendant of another element with binding |mBinding|, michael@0: // then we might have just constructed it due to the michael@0: // notification of its parent. (We can know about both if the michael@0: // binding loads were triggered from the DOM rather than frame michael@0: // construction.) So we have to check both whether the element michael@0: // has a primary frame and whether it's in the undisplayed map michael@0: // before sending a ContentInserted notification, or bad things michael@0: // will happen. michael@0: nsIPresShell *shell = doc->GetShell(); michael@0: if (shell) { michael@0: nsIFrame* childFrame = mBoundElement->GetPrimaryFrame(); michael@0: if (!childFrame) { michael@0: // Check to see if it's in the undisplayed content map. michael@0: nsStyleContext* sc = michael@0: shell->FrameManager()->GetUndisplayedContent(mBoundElement); michael@0: michael@0: if (!sc) { michael@0: shell->RecreateFramesFor(mBoundElement); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsXBLBindingRequest(nsIURI* aURI, nsIContent* aBoundElement) michael@0: : mBindingURI(aURI), michael@0: mBoundElement(aBoundElement) michael@0: { michael@0: } michael@0: }; michael@0: michael@0: // nsXBLStreamListener, a helper class used for michael@0: // asynchronous parsing of URLs michael@0: /* Header file */ michael@0: class nsXBLStreamListener MOZ_FINAL : public nsIStreamListener, michael@0: public nsIDOMEventListener michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSISTREAMLISTENER michael@0: NS_DECL_NSIREQUESTOBSERVER michael@0: NS_DECL_NSIDOMEVENTLISTENER michael@0: michael@0: nsXBLStreamListener(nsIDocument* aBoundDocument, michael@0: nsIXMLContentSink* aSink, michael@0: nsIDocument* aBindingDocument); michael@0: ~nsXBLStreamListener(); michael@0: michael@0: void AddRequest(nsXBLBindingRequest* aRequest) { mBindingRequests.AppendElement(aRequest); } michael@0: bool HasRequest(nsIURI* aURI, nsIContent* aBoundElement); michael@0: michael@0: private: michael@0: nsCOMPtr mInner; michael@0: nsAutoTArray mBindingRequests; michael@0: michael@0: nsCOMPtr mBoundDocument; michael@0: nsCOMPtr mSink; // Only set until OnStartRequest michael@0: nsCOMPtr mBindingDocument; // Only set until OnStartRequest michael@0: }; michael@0: michael@0: /* Implementation file */ michael@0: NS_IMPL_ISUPPORTS(nsXBLStreamListener, michael@0: nsIStreamListener, michael@0: nsIRequestObserver, michael@0: nsIDOMEventListener) michael@0: michael@0: nsXBLStreamListener::nsXBLStreamListener(nsIDocument* aBoundDocument, michael@0: nsIXMLContentSink* aSink, michael@0: nsIDocument* aBindingDocument) michael@0: : mSink(aSink), mBindingDocument(aBindingDocument) michael@0: { michael@0: /* member initializers and constructor code */ michael@0: mBoundDocument = do_GetWeakReference(aBoundDocument); michael@0: } michael@0: michael@0: nsXBLStreamListener::~nsXBLStreamListener() michael@0: { michael@0: for (uint32_t i = 0; i < mBindingRequests.Length(); i++) { michael@0: nsXBLBindingRequest* req = mBindingRequests.ElementAt(i); michael@0: delete req; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXBLStreamListener::OnDataAvailable(nsIRequest *request, nsISupports* aCtxt, michael@0: nsIInputStream* aInStr, michael@0: uint64_t aSourceOffset, uint32_t aCount) michael@0: { michael@0: if (mInner) michael@0: return mInner->OnDataAvailable(request, aCtxt, aInStr, aSourceOffset, aCount); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXBLStreamListener::OnStartRequest(nsIRequest* request, nsISupports* aCtxt) michael@0: { michael@0: // Make sure we don't hold on to the sink and binding document past this point michael@0: nsCOMPtr sink; michael@0: mSink.swap(sink); michael@0: nsCOMPtr doc; michael@0: mBindingDocument.swap(doc); michael@0: michael@0: nsCOMPtr channel = do_QueryInterface(request); michael@0: NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED); michael@0: michael@0: nsCOMPtr group; michael@0: request->GetLoadGroup(getter_AddRefs(group)); michael@0: michael@0: nsresult rv = doc->StartDocumentLoad("loadAsInteractiveData", michael@0: channel, michael@0: group, michael@0: nullptr, michael@0: getter_AddRefs(mInner), michael@0: true, michael@0: sink); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Make sure to add ourselves as a listener after StartDocumentLoad, michael@0: // since that resets the event listners on the document. michael@0: doc->AddEventListener(NS_LITERAL_STRING("load"), this, false); michael@0: michael@0: return mInner->OnStartRequest(request, aCtxt); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXBLStreamListener::OnStopRequest(nsIRequest* request, nsISupports* aCtxt, nsresult aStatus) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: if (mInner) { michael@0: rv = mInner->OnStopRequest(request, aCtxt, aStatus); michael@0: } michael@0: michael@0: // Don't hold onto the inner listener; holding onto it can create a cycle michael@0: // with the document michael@0: mInner = nullptr; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: nsXBLStreamListener::HasRequest(nsIURI* aURI, nsIContent* aElt) michael@0: { michael@0: // XXX Could be more efficient. michael@0: uint32_t count = mBindingRequests.Length(); michael@0: for (uint32_t i = 0; i < count; i++) { michael@0: nsXBLBindingRequest* req = mBindingRequests.ElementAt(i); michael@0: bool eq; michael@0: if (req->mBoundElement == aElt && michael@0: NS_SUCCEEDED(req->mBindingURI->Equals(aURI, &eq)) && eq) michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLStreamListener::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: uint32_t i; michael@0: uint32_t count = mBindingRequests.Length(); michael@0: michael@0: // Get the binding document; note that we don't hold onto it in this object michael@0: // to avoid creating a cycle michael@0: Event* event = aEvent->InternalDOMEvent(); michael@0: EventTarget* target = event->GetCurrentTarget(); michael@0: nsCOMPtr bindingDocument = do_QueryInterface(target); michael@0: NS_ASSERTION(bindingDocument, "Event not targeted at document?!"); michael@0: michael@0: // See if we're still alive. michael@0: nsCOMPtr doc(do_QueryReferent(mBoundDocument)); michael@0: if (!doc) { michael@0: NS_WARNING("XBL load did not complete until after document went away! Modal dialog bug?\n"); michael@0: } michael@0: else { michael@0: // We have to do a flush prior to notification of the document load. michael@0: // This has to happen since the HTML content sink can be holding on michael@0: // to notifications related to our children (e.g., if you bind to the michael@0: // tag) that result in duplication of content. michael@0: // We need to get the sink's notifications flushed and then make the binding michael@0: // ready. michael@0: if (count > 0) { michael@0: nsXBLBindingRequest* req = mBindingRequests.ElementAt(0); michael@0: nsIDocument* document = req->mBoundElement->GetCurrentDoc(); michael@0: if (document) michael@0: document->FlushPendingNotifications(Flush_ContentAndNotify); michael@0: } michael@0: michael@0: // Remove ourselves from the set of pending docs. michael@0: nsBindingManager *bindingManager = doc->BindingManager(); michael@0: nsIURI* documentURI = bindingDocument->GetDocumentURI(); michael@0: bindingManager->RemoveLoadingDocListener(documentURI); michael@0: michael@0: if (!bindingDocument->GetRootElement()) { michael@0: // FIXME: How about an error console warning? michael@0: NS_WARNING("XBL doc with no root element - this usually shouldn't happen"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Put our doc info in the doc table. michael@0: nsBindingManager *xblDocBindingManager = bindingDocument->BindingManager(); michael@0: nsRefPtr info = michael@0: xblDocBindingManager->GetXBLDocumentInfo(documentURI); michael@0: xblDocBindingManager->RemoveXBLDocumentInfo(info); // Break the self-imposed cycle. michael@0: if (!info) { michael@0: if (nsXBLService::IsChromeOrResourceURI(documentURI)) { michael@0: NS_WARNING("An XBL file is malformed. Did you forget the XBL namespace on the bindings tag?"); michael@0: } michael@0: nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, michael@0: NS_LITERAL_CSTRING("XBL"), nullptr, michael@0: nsContentUtils::eXBL_PROPERTIES, michael@0: "MalformedXBL", michael@0: nullptr, 0, documentURI); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // If the doc is a chrome URI, then we put it into the XUL cache. michael@0: #ifdef MOZ_XUL michael@0: if (nsXBLService::IsChromeOrResourceURI(documentURI)) { michael@0: nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance(); michael@0: if (cache && cache->IsEnabled()) michael@0: cache->PutXBLDocumentInfo(info); michael@0: } michael@0: #endif michael@0: michael@0: bindingManager->PutXBLDocumentInfo(info); michael@0: michael@0: // Notify all pending requests that their bindings are michael@0: // ready and can be installed. michael@0: for (i = 0; i < count; i++) { michael@0: nsXBLBindingRequest* req = mBindingRequests.ElementAt(i); michael@0: req->DocumentLoaded(bindingDocument); michael@0: } michael@0: } michael@0: michael@0: target->RemoveEventListener(NS_LITERAL_STRING("load"), this, false); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // Implementation ///////////////////////////////////////////////////////////////// michael@0: michael@0: // Static member variable initialization michael@0: bool nsXBLService::gAllowDataURIs = false; michael@0: michael@0: // Implement our nsISupports methods michael@0: NS_IMPL_ISUPPORTS(nsXBLService, nsISupportsWeakReference) michael@0: michael@0: void michael@0: nsXBLService::Init() michael@0: { michael@0: gInstance = new nsXBLService(); michael@0: NS_ADDREF(gInstance); michael@0: } michael@0: michael@0: // Constructors/Destructors michael@0: nsXBLService::nsXBLService(void) michael@0: { michael@0: Preferences::AddBoolVarCache(&gAllowDataURIs, "layout.debug.enable_data_xbl"); michael@0: } michael@0: michael@0: nsXBLService::~nsXBLService(void) michael@0: { michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: nsXBLService::IsChromeOrResourceURI(nsIURI* aURI) michael@0: { michael@0: bool isChrome = false; michael@0: bool isResource = false; michael@0: if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) && michael@0: NS_SUCCEEDED(aURI->SchemeIs("resource", &isResource))) michael@0: return (isChrome || isResource); michael@0: return false; michael@0: } michael@0: michael@0: michael@0: // This function loads a particular XBL file and installs all of the bindings michael@0: // onto the element. michael@0: nsresult michael@0: nsXBLService::LoadBindings(nsIContent* aContent, nsIURI* aURL, michael@0: nsIPrincipal* aOriginPrincipal, michael@0: nsXBLBinding** aBinding, bool* aResolveStyle) michael@0: { michael@0: NS_PRECONDITION(aOriginPrincipal, "Must have an origin principal"); michael@0: michael@0: *aBinding = nullptr; michael@0: *aResolveStyle = false; michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr document = aContent->OwnerDoc(); michael@0: michael@0: nsAutoCString urlspec; michael@0: if (nsContentUtils::GetWrapperSafeScriptFilename(document, aURL, urlspec)) { michael@0: // Block an attempt to load a binding that has special wrapper michael@0: // automation needs. michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsXBLBinding *binding = aContent->GetXBLBinding(); michael@0: if (binding) { michael@0: if (binding->MarkedForDeath()) { michael@0: FlushStyleBindings(aContent); michael@0: binding = nullptr; michael@0: } michael@0: else { michael@0: // See if the URIs match. michael@0: if (binding->PrototypeBinding()->CompareBindingURI(aURL)) michael@0: return NS_OK; michael@0: FlushStyleBindings(aContent); michael@0: binding = nullptr; michael@0: } michael@0: } michael@0: michael@0: bool ready; michael@0: nsRefPtr newBinding; michael@0: if (NS_FAILED(rv = GetBinding(aContent, aURL, false, aOriginPrincipal, michael@0: &ready, getter_AddRefs(newBinding)))) { michael@0: return rv; michael@0: } michael@0: michael@0: if (!newBinding) { michael@0: #ifdef DEBUG michael@0: nsAutoCString spec; michael@0: aURL->GetSpec(spec); michael@0: 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); michael@0: NS_ERROR(str.get()); michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (::IsAncestorBinding(document, aURL, aContent)) { michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: // We loaded a style binding. It goes on the end. michael@0: if (binding) { michael@0: // Get the last binding that is in the append layer. michael@0: binding->RootBinding()->SetBaseBinding(newBinding); michael@0: } michael@0: else { michael@0: // Install the binding on the content node. michael@0: aContent->SetXBLBinding(newBinding); michael@0: } michael@0: michael@0: { michael@0: nsAutoScriptBlocker scriptBlocker; michael@0: michael@0: // Set the binding's bound element. michael@0: newBinding->SetBoundElement(aContent); michael@0: michael@0: // Tell the binding to build the anonymous content. michael@0: newBinding->GenerateAnonymousContent(); michael@0: michael@0: // Tell the binding to install event handlers michael@0: newBinding->InstallEventHandlers(); michael@0: michael@0: // Set up our properties michael@0: rv = newBinding->InstallImplementation(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Figure out if we have any scoped sheets. If so, we do a second resolve. michael@0: *aResolveStyle = newBinding->HasStyleSheets(); michael@0: michael@0: newBinding.swap(*aBinding); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLService::FlushStyleBindings(nsIContent* aContent) michael@0: { michael@0: nsCOMPtr document = aContent->OwnerDoc(); michael@0: michael@0: nsXBLBinding *binding = aContent->GetXBLBinding(); michael@0: if (binding) { michael@0: // Clear out the script references. michael@0: binding->ChangeDocument(document, nullptr); michael@0: michael@0: aContent->SetXBLBinding(nullptr); // Flush old style bindings michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // AttachGlobalKeyHandler michael@0: // michael@0: // Creates a new key handler and prepares to listen to key events on the given michael@0: // event receiver (either a document or an content node). If the receiver is content, michael@0: // then extra work needs to be done to hook it up to the document (XXX WHY??) michael@0: // michael@0: nsresult michael@0: nsXBLService::AttachGlobalKeyHandler(EventTarget* aTarget) michael@0: { michael@0: // check if the receiver is a content node (not a document), and hook michael@0: // it to the document if that is the case. michael@0: nsCOMPtr piTarget = aTarget; michael@0: nsCOMPtr contentNode(do_QueryInterface(aTarget)); michael@0: if (contentNode) { michael@0: // Only attach if we're really in a document michael@0: nsCOMPtr doc = contentNode->GetCurrentDoc(); michael@0: if (doc) michael@0: piTarget = doc; // We're a XUL keyset. Attach to our document. michael@0: } michael@0: michael@0: EventListenerManager* manager = piTarget->GetOrCreateListenerManager(); michael@0: michael@0: if (!piTarget || !manager) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // the listener already exists, so skip this michael@0: if (contentNode && contentNode->GetProperty(nsGkAtoms::listener)) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr elt(do_QueryInterface(contentNode)); michael@0: michael@0: // Create the key handler michael@0: nsRefPtr handler = michael@0: NS_NewXBLWindowKeyHandler(elt, piTarget); michael@0: michael@0: // listen to these events michael@0: manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keydown"), michael@0: TrustedEventsAtSystemGroupBubble()); michael@0: manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keyup"), michael@0: TrustedEventsAtSystemGroupBubble()); michael@0: manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keypress"), michael@0: TrustedEventsAtSystemGroupBubble()); michael@0: michael@0: // The capturing listener is only used for XUL keysets to properly handle michael@0: // shortcut keys in a multi-process environment. michael@0: manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keydown"), michael@0: TrustedEventsAtSystemGroupCapture()); michael@0: manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keyup"), michael@0: TrustedEventsAtSystemGroupCapture()); michael@0: manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keypress"), michael@0: TrustedEventsAtSystemGroupCapture()); michael@0: michael@0: if (contentNode) michael@0: return contentNode->SetProperty(nsGkAtoms::listener, michael@0: handler.forget().take(), michael@0: nsPropertyTable::SupportsDtorFunc, true); michael@0: michael@0: // The reference to the handler will be maintained by the event target, michael@0: // and, if there is a content node, the property. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // DetachGlobalKeyHandler michael@0: // michael@0: // Removes a key handler added by DeatchGlobalKeyHandler. michael@0: // michael@0: nsresult michael@0: nsXBLService::DetachGlobalKeyHandler(EventTarget* aTarget) michael@0: { michael@0: nsCOMPtr piTarget = aTarget; michael@0: nsCOMPtr contentNode(do_QueryInterface(aTarget)); michael@0: if (!contentNode) // detaching is only supported for content nodes michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Only attach if we're really in a document michael@0: nsCOMPtr doc = contentNode->GetCurrentDoc(); michael@0: if (doc) michael@0: piTarget = do_QueryInterface(doc); michael@0: michael@0: EventListenerManager* manager = piTarget->GetOrCreateListenerManager(); michael@0: michael@0: if (!piTarget || !manager) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsIDOMEventListener* handler = michael@0: static_cast(contentNode->GetProperty(nsGkAtoms::listener)); michael@0: if (!handler) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keydown"), michael@0: TrustedEventsAtSystemGroupBubble()); michael@0: manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keyup"), michael@0: TrustedEventsAtSystemGroupBubble()); michael@0: manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keypress"), michael@0: TrustedEventsAtSystemGroupBubble()); michael@0: michael@0: manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keydown"), michael@0: TrustedEventsAtSystemGroupCapture()); michael@0: manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keyup"), michael@0: TrustedEventsAtSystemGroupCapture()); michael@0: manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keypress"), michael@0: TrustedEventsAtSystemGroupCapture()); michael@0: michael@0: contentNode->DeleteProperty(nsGkAtoms::listener); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Internal helper methods //////////////////////////////////////////////////////////////// michael@0: michael@0: nsresult michael@0: nsXBLService::BindingReady(nsIContent* aBoundElement, michael@0: nsIURI* aURI, michael@0: bool* aIsReady) michael@0: { michael@0: // Don't do a security check here; we know this binding is set to go. michael@0: return GetBinding(aBoundElement, aURI, true, nullptr, aIsReady, nullptr); michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLService::GetBinding(nsIContent* aBoundElement, nsIURI* aURI, michael@0: bool aPeekOnly, nsIPrincipal* aOriginPrincipal, michael@0: bool* aIsReady, nsXBLBinding** aResult) michael@0: { michael@0: // More than 6 binding URIs are rare, see bug 55070 comment 18. michael@0: nsAutoTArray uris; michael@0: return GetBinding(aBoundElement, aURI, aPeekOnly, aOriginPrincipal, aIsReady, michael@0: aResult, uris); michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLService::GetBinding(nsIContent* aBoundElement, nsIURI* aURI, michael@0: bool aPeekOnly, nsIPrincipal* aOriginPrincipal, michael@0: bool* aIsReady, nsXBLBinding** aResult, michael@0: nsTArray& aDontExtendURIs) michael@0: { michael@0: NS_ASSERTION(aPeekOnly || aResult, michael@0: "Must have non-null out param if not just peeking to see " michael@0: "whether the binding is ready"); michael@0: michael@0: if (aResult) michael@0: *aResult = nullptr; michael@0: michael@0: if (!aURI) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsAutoCString ref; michael@0: aURI->GetRef(ref); michael@0: michael@0: nsCOMPtr boundDocument = aBoundElement->OwnerDoc(); michael@0: michael@0: nsRefPtr docInfo; michael@0: nsresult rv = LoadBindingDocumentInfo(aBoundElement, boundDocument, aURI, michael@0: aOriginPrincipal, michael@0: false, getter_AddRefs(docInfo)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!docInfo) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsXBLPrototypeBinding* protoBinding = docInfo->GetPrototypeBinding(ref); michael@0: michael@0: if (!protoBinding) { michael@0: #ifdef DEBUG michael@0: nsAutoCString uriSpec; michael@0: aURI->GetSpec(uriSpec); michael@0: nsAutoCString doc; michael@0: boundDocument->GetDocumentURI()->GetSpec(doc); michael@0: nsAutoCString message("Unable to locate an XBL binding for URI "); michael@0: message += uriSpec; michael@0: message += " in document "; michael@0: message += doc; michael@0: NS_WARNING(message.get()); michael@0: #endif michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(aDontExtendURIs.AppendElement(protoBinding->BindingURI()), michael@0: NS_ERROR_OUT_OF_MEMORY); michael@0: nsCOMPtr altBindingURI = protoBinding->AlternateBindingURI(); michael@0: if (altBindingURI) { michael@0: NS_ENSURE_TRUE(aDontExtendURIs.AppendElement(altBindingURI), michael@0: NS_ERROR_OUT_OF_MEMORY); michael@0: } michael@0: michael@0: // Our prototype binding must have all its resources loaded. michael@0: bool ready = protoBinding->LoadResources(); michael@0: if (!ready) { michael@0: // Add our bound element to the protos list of elts that should michael@0: // be notified when the stylesheets and scripts finish loading. michael@0: protoBinding->AddResourceListener(aBoundElement); michael@0: return NS_ERROR_FAILURE; // The binding isn't ready yet. michael@0: } michael@0: michael@0: rv = protoBinding->ResolveBaseBinding(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsIURI* baseBindingURI; michael@0: nsXBLPrototypeBinding* baseProto = protoBinding->GetBasePrototype(); michael@0: if (baseProto) { michael@0: baseBindingURI = baseProto->BindingURI(); michael@0: } michael@0: else { michael@0: baseBindingURI = protoBinding->GetBaseBindingURI(); michael@0: if (baseBindingURI) { michael@0: uint32_t count = aDontExtendURIs.Length(); michael@0: for (uint32_t index = 0; index < count; ++index) { michael@0: bool equal; michael@0: rv = aDontExtendURIs[index]->Equals(baseBindingURI, &equal); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (equal) { michael@0: nsAutoCString spec, basespec; michael@0: protoBinding->BindingURI()->GetSpec(spec); michael@0: NS_ConvertUTF8toUTF16 protoSpec(spec); michael@0: baseBindingURI->GetSpec(basespec); michael@0: NS_ConvertUTF8toUTF16 baseSpecUTF16(basespec); michael@0: const char16_t* params[] = { protoSpec.get(), baseSpecUTF16.get() }; michael@0: nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, michael@0: NS_LITERAL_CSTRING("XBL"), nullptr, michael@0: nsContentUtils::eXBL_PROPERTIES, michael@0: "CircularExtendsBinding", michael@0: params, ArrayLength(params), michael@0: boundDocument->GetDocumentURI()); michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsRefPtr baseBinding; michael@0: if (baseBindingURI) { michael@0: nsIContent* child = protoBinding->GetBindingElement(); michael@0: rv = GetBinding(aBoundElement, baseBindingURI, aPeekOnly, michael@0: child->NodePrincipal(), aIsReady, michael@0: getter_AddRefs(baseBinding), aDontExtendURIs); michael@0: if (NS_FAILED(rv)) michael@0: return rv; // We aren't ready yet. michael@0: } michael@0: michael@0: *aIsReady = true; michael@0: michael@0: if (!aPeekOnly) { michael@0: // Make a new binding michael@0: nsXBLBinding *newBinding = new nsXBLBinding(protoBinding); michael@0: NS_ENSURE_TRUE(newBinding, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: if (baseBinding) { michael@0: if (!baseProto) { michael@0: protoBinding->SetBasePrototype(baseBinding->PrototypeBinding()); michael@0: } michael@0: newBinding->SetBaseBinding(baseBinding); michael@0: } michael@0: michael@0: NS_ADDREF(*aResult = newBinding); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static bool SchemeIs(nsIURI* aURI, const char* aScheme) michael@0: { michael@0: nsCOMPtr baseURI = NS_GetInnermostURI(aURI); michael@0: NS_ENSURE_TRUE(baseURI, false); michael@0: michael@0: bool isScheme = false; michael@0: return NS_SUCCEEDED(baseURI->SchemeIs(aScheme, &isScheme)) && isScheme; michael@0: } michael@0: michael@0: static bool michael@0: IsSystemOrChromeURLPrincipal(nsIPrincipal* aPrincipal) michael@0: { michael@0: if (nsContentUtils::IsSystemPrincipal(aPrincipal)) { michael@0: return true; michael@0: } michael@0: michael@0: nsCOMPtr uri; michael@0: aPrincipal->GetURI(getter_AddRefs(uri)); michael@0: NS_ENSURE_TRUE(uri, false); michael@0: michael@0: bool isChrome = false; michael@0: return NS_SUCCEEDED(uri->SchemeIs("chrome", &isChrome)) && isChrome; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLService::LoadBindingDocumentInfo(nsIContent* aBoundElement, michael@0: nsIDocument* aBoundDocument, michael@0: nsIURI* aBindingURI, michael@0: nsIPrincipal* aOriginPrincipal, michael@0: bool aForceSyncLoad, michael@0: nsXBLDocumentInfo** aResult) michael@0: { michael@0: NS_PRECONDITION(aBindingURI, "Must have a binding URI"); michael@0: NS_PRECONDITION(!aOriginPrincipal || aBoundDocument, michael@0: "If we're doing a security check, we better have a document!"); michael@0: michael@0: nsresult rv; michael@0: if (aOriginPrincipal) { michael@0: // Security check - Enforce same-origin policy, except to chrome. michael@0: // We have to be careful to not pass aContent as the context here. michael@0: // Otherwise, if there is a JS-implemented content policy, we will attempt michael@0: // to wrap the content node, which will try to load XBL bindings for it, if michael@0: // any. Since we're not done loading this binding yet, that will reenter michael@0: // this method and we'll end up creating a binding and then immediately michael@0: // clobbering it in our table. That makes things very confused, leading to michael@0: // misbehavior and crashes. michael@0: rv = nsContentUtils:: michael@0: CheckSecurityBeforeLoad(aBindingURI, aOriginPrincipal, michael@0: nsIScriptSecurityManager::ALLOW_CHROME, michael@0: gAllowDataURIs, michael@0: nsIContentPolicy::TYPE_XBL, michael@0: aBoundDocument); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_XBL_BLOCKED); michael@0: michael@0: if (!IsSystemOrChromeURLPrincipal(aOriginPrincipal)) { michael@0: // Also make sure that we're same-origin with the bound document michael@0: // except if the stylesheet has the system principal. michael@0: if (!(gAllowDataURIs && SchemeIs(aBindingURI, "data")) && michael@0: !SchemeIs(aBindingURI, "chrome")) { michael@0: rv = aBoundDocument->NodePrincipal()->CheckMayLoad(aBindingURI, michael@0: true, false); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_XBL_BLOCKED); michael@0: } michael@0: michael@0: // Finally check if this document is allowed to use XBL at all. michael@0: NS_ENSURE_TRUE(aBoundDocument->AllowXULXBL(), michael@0: NS_ERROR_XBL_BLOCKED); michael@0: } michael@0: } michael@0: michael@0: *aResult = nullptr; michael@0: nsRefPtr info; michael@0: michael@0: nsCOMPtr documentURI; michael@0: rv = aBindingURI->CloneIgnoringRef(getter_AddRefs(documentURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: #ifdef MOZ_XUL michael@0: // We've got a file. Check our XBL document cache. michael@0: nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance(); michael@0: bool useXULCache = cache && cache->IsEnabled(); michael@0: michael@0: if (useXULCache) { michael@0: // The first line of defense is the chrome cache. michael@0: // This cache crosses the entire product, so that any XBL bindings that are michael@0: // part of chrome will be reused across all XUL documents. michael@0: info = cache->GetXBLDocumentInfo(documentURI); michael@0: } michael@0: #endif michael@0: michael@0: if (!info) { michael@0: // The second line of defense is the binding manager's document table. michael@0: nsBindingManager *bindingManager = nullptr; michael@0: michael@0: if (aBoundDocument) { michael@0: bindingManager = aBoundDocument->BindingManager(); michael@0: info = bindingManager->GetXBLDocumentInfo(documentURI); michael@0: if (aBoundDocument->IsStaticDocument() && michael@0: IsChromeOrResourceURI(aBindingURI)) { michael@0: aForceSyncLoad = true; michael@0: } michael@0: } michael@0: michael@0: nsINodeInfo *ni = nullptr; michael@0: if (aBoundElement) michael@0: ni = aBoundElement->NodeInfo(); michael@0: michael@0: if (!info && bindingManager && michael@0: (!ni || !(ni->Equals(nsGkAtoms::scrollbar, kNameSpaceID_XUL) || michael@0: ni->Equals(nsGkAtoms::thumb, kNameSpaceID_XUL) || michael@0: ((ni->Equals(nsGkAtoms::input) || michael@0: ni->Equals(nsGkAtoms::select)) && michael@0: aBoundElement->IsHTML()))) && !aForceSyncLoad) { michael@0: // The third line of defense is to investigate whether or not the michael@0: // document is currently being loaded asynchronously. If so, there's no michael@0: // document yet, but we need to glom on our request so that it will be michael@0: // processed whenever the doc does finish loading. michael@0: nsCOMPtr listener; michael@0: if (bindingManager) michael@0: listener = bindingManager->GetLoadingDocListener(documentURI); michael@0: if (listener) { michael@0: nsXBLStreamListener* xblListener = michael@0: static_cast(listener.get()); michael@0: // Create a new load observer. michael@0: if (!xblListener->HasRequest(aBindingURI, aBoundElement)) { michael@0: nsXBLBindingRequest* req = new nsXBLBindingRequest(aBindingURI, aBoundElement); michael@0: xblListener->AddRequest(req); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: #ifdef MOZ_XUL michael@0: // Next, look in the startup cache michael@0: bool useStartupCache = useXULCache && IsChromeOrResourceURI(documentURI); michael@0: if (!info && useStartupCache) { michael@0: rv = nsXBLDocumentInfo::ReadPrototypeBindings(documentURI, getter_AddRefs(info)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: cache->PutXBLDocumentInfo(info); michael@0: michael@0: if (bindingManager) { michael@0: // Cache it in our binding manager's document table. michael@0: bindingManager->PutXBLDocumentInfo(info); michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: if (!info) { michael@0: // Finally, if all lines of defense fail, we go and fetch the binding michael@0: // document. michael@0: michael@0: // Always load chrome synchronously michael@0: bool chrome; michael@0: if (NS_SUCCEEDED(documentURI->SchemeIs("chrome", &chrome)) && chrome) michael@0: aForceSyncLoad = true; michael@0: michael@0: nsCOMPtr document; michael@0: FetchBindingDocument(aBoundElement, aBoundDocument, documentURI, michael@0: aBindingURI, aForceSyncLoad, getter_AddRefs(document)); michael@0: michael@0: if (document) { michael@0: nsBindingManager *xblDocBindingManager = document->BindingManager(); michael@0: info = xblDocBindingManager->GetXBLDocumentInfo(documentURI); michael@0: if (!info) { michael@0: NS_ERROR("An XBL file is malformed. Did you forget the XBL namespace on the bindings tag?"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: xblDocBindingManager->RemoveXBLDocumentInfo(info); // Break the self-imposed cycle. michael@0: michael@0: // If the doc is a chrome URI, then we put it into the XUL cache. michael@0: #ifdef MOZ_XUL michael@0: if (useStartupCache) { michael@0: cache->PutXBLDocumentInfo(info); michael@0: michael@0: // now write the bindings into the startup cache michael@0: info->WritePrototypeBindings(); michael@0: } michael@0: #endif michael@0: michael@0: if (bindingManager) { michael@0: // Also put it in our binding manager's document table. michael@0: bindingManager->PutXBLDocumentInfo(info); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: info.forget(aResult); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLService::FetchBindingDocument(nsIContent* aBoundElement, nsIDocument* aBoundDocument, michael@0: nsIURI* aDocumentURI, nsIURI* aBindingURI, michael@0: bool aForceSyncLoad, nsIDocument** aResult) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: // Initialize our out pointer to nullptr michael@0: *aResult = nullptr; michael@0: michael@0: // Now we have to synchronously load the binding file. michael@0: // Create an XML content sink and a parser. michael@0: nsCOMPtr loadGroup; michael@0: if (aBoundDocument) michael@0: loadGroup = aBoundDocument->GetDocumentLoadGroup(); michael@0: michael@0: // We really shouldn't have to force a sync load for anything here... could michael@0: // we get away with not doing that? Not sure. michael@0: if (IsChromeOrResourceURI(aDocumentURI)) michael@0: aForceSyncLoad = true; michael@0: michael@0: // Create document and contentsink and set them up. michael@0: nsCOMPtr doc; michael@0: rv = NS_NewXMLDocument(getter_AddRefs(doc)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr xblSink; michael@0: rv = NS_NewXBLContentSink(getter_AddRefs(xblSink), doc, aDocumentURI, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Open channel michael@0: nsCOMPtr channel; michael@0: rv = NS_NewChannel(getter_AddRefs(channel), aDocumentURI, nullptr, loadGroup); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr sameOriginChecker = nsContentUtils::GetSameOriginChecker(); michael@0: NS_ENSURE_TRUE(sameOriginChecker, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: channel->SetNotificationCallbacks(sameOriginChecker); michael@0: michael@0: if (!aForceSyncLoad) { michael@0: // We can be asynchronous michael@0: nsXBLStreamListener* xblListener = michael@0: new nsXBLStreamListener(aBoundDocument, xblSink, doc); michael@0: NS_ENSURE_TRUE(xblListener,NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: // Add ourselves to the list of loading docs. michael@0: nsBindingManager *bindingManager; michael@0: if (aBoundDocument) michael@0: bindingManager = aBoundDocument->BindingManager(); michael@0: else michael@0: bindingManager = nullptr; michael@0: michael@0: if (bindingManager) michael@0: bindingManager->PutLoadingDocListener(aDocumentURI, xblListener); michael@0: michael@0: // Add our request. michael@0: nsXBLBindingRequest* req = new nsXBLBindingRequest(aBindingURI, michael@0: aBoundElement); michael@0: xblListener->AddRequest(req); michael@0: michael@0: // Now kick off the async read. michael@0: rv = channel->AsyncOpen(xblListener, nullptr); michael@0: if (NS_FAILED(rv)) { michael@0: // Well, we won't be getting a load. Make sure to clean up our stuff! michael@0: if (bindingManager) { michael@0: bindingManager->RemoveLoadingDocListener(aDocumentURI); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr listener; michael@0: rv = doc->StartDocumentLoad("loadAsInteractiveData", michael@0: channel, michael@0: loadGroup, michael@0: nullptr, michael@0: getter_AddRefs(listener), michael@0: true, michael@0: xblSink); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Now do a blocking synchronous parse of the file. michael@0: nsCOMPtr in; michael@0: rv = channel->Open(getter_AddRefs(in)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = nsSyncLoadService::PushSyncStreamToListener(in, listener, channel); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: doc.swap(*aResult); michael@0: michael@0: return NS_OK; michael@0: }