gecko-dev/dom/xbl/nsXBLService.cpp

1120 строки
37 KiB
C++

/* -*- 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 "nsIDocument.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(nsIDocument* aDocument, nsIURI* aChildBindingURI,
nsIContent* aChild) {
NS_ASSERTION(aDocument, "expected a document");
NS_ASSERTION(aChildBindingURI, "expected a binding URI");
NS_ASSERTION(aChild, "expected a child content");
uint32_t bindingRecursion = 0;
for (nsIContent* bindingParent = aChild->GetBindingParent(); bindingParent;
bindingParent = bindingParent->GetBindingParent()) {
nsXBLBinding* binding = bindingParent->GetXBLBinding();
if (!binding) {
continue;
}
if (binding->PrototypeBinding()->CompareBindingURI(aChildBindingURI)) {
++bindingRecursion;
if (bindingRecursion < NS_MAX_XBL_BINDING_RECURSION) {
continue;
}
NS_ConvertUTF8toUTF16 bindingURI(aChildBindingURI->GetSpecOrDefault());
const char16_t* params[] = {bindingURI.get()};
nsContentUtils::ReportToConsole(
nsIScriptError::warningFlag, NS_LITERAL_CSTRING("XBL"), aDocument,
nsContentUtils::eXBL_PROPERTIES, "TooDeepBindingRecursion", params,
ArrayLength(params));
return true;
}
}
return false;
}
// Individual binding requests.
class nsXBLBindingRequest {
public:
nsCOMPtr<nsIURI> mBindingURI;
nsCOMPtr<nsIContent> mBoundElement;
void DocumentLoaded(nsIDocument* aBindingDoc) {
// We only need the document here to cause frame construction, so
// we need the current doc, not the owner doc.
nsIDocument* doc = mBoundElement->GetUncomposedDoc();
if (!doc) return;
// 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(nsIDocument* aBoundDocument, nsIXMLContentSink* aSink,
nsIDocument* aBindingDocument);
void AddRequest(nsXBLBindingRequest* aRequest) {
mBindingRequests.AppendElement(aRequest);
}
bool HasRequest(nsIURI* aURI, nsIContent* aBoundElement);
private:
~nsXBLStreamListener();
nsCOMPtr<nsIStreamListener> mInner;
AutoTArray<nsXBLBindingRequest*, 8> mBindingRequests;
nsWeakPtr mBoundDocument;
nsCOMPtr<nsIXMLContentSink> mSink; // Only set until OnStartRequest
nsCOMPtr<nsIDocument> mBindingDocument; // Only set until OnStartRequest
};
/* Implementation file */
NS_IMPL_ISUPPORTS(nsXBLStreamListener, nsIStreamListener, nsIRequestObserver,
nsIDOMEventListener)
nsXBLStreamListener::nsXBLStreamListener(nsIDocument* aBoundDocument,
nsIXMLContentSink* aSink,
nsIDocument* aBindingDocument)
: mSink(aSink), mBindingDocument(aBindingDocument) {
/* member initializers and constructor code */
mBoundDocument = do_GetWeakReference(aBoundDocument);
}
nsXBLStreamListener::~nsXBLStreamListener() {
for (uint32_t i = 0; i < mBindingRequests.Length(); i++) {
nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
delete req;
}
}
NS_IMETHODIMP
nsXBLStreamListener::OnDataAvailable(nsIRequest* request, nsISupports* aCtxt,
nsIInputStream* aInStr,
uint64_t aSourceOffset, uint32_t aCount) {
if (mInner)
return mInner->OnDataAvailable(request, aCtxt, aInStr, aSourceOffset,
aCount);
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsXBLStreamListener::OnStartRequest(nsIRequest* request, nsISupports* aCtxt) {
// Make sure we don't hold on to the sink and binding document past this point
nsCOMPtr<nsIXMLContentSink> sink;
mSink.swap(sink);
nsCOMPtr<nsIDocument> doc;
mBindingDocument.swap(doc);
nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsILoadGroup> group;
request->GetLoadGroup(getter_AddRefs(group));
nsresult rv =
doc->StartDocumentLoad("loadAsInteractiveData", channel, group, nullptr,
getter_AddRefs(mInner), true, sink);
NS_ENSURE_SUCCESS(rv, rv);
// Make sure to add ourselves as a listener after StartDocumentLoad,
// since that resets the event listners on the document.
doc->AddEventListener(NS_LITERAL_STRING("load"), this, false);
return mInner->OnStartRequest(request, aCtxt);
}
NS_IMETHODIMP
nsXBLStreamListener::OnStopRequest(nsIRequest* request, nsISupports* aCtxt,
nsresult aStatus) {
nsresult rv = NS_OK;
if (mInner) {
rv = mInner->OnStopRequest(request, aCtxt, aStatus);
}
// Don't hold onto the inner listener; holding onto it can create a cycle
// with the document
mInner = nullptr;
return rv;
}
bool nsXBLStreamListener::HasRequest(nsIURI* aURI, nsIContent* aElt) {
// XXX Could be more efficient.
uint32_t count = mBindingRequests.Length();
for (uint32_t i = 0; i < count; i++) {
nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
bool eq;
if (req->mBoundElement == aElt &&
NS_SUCCEEDED(req->mBindingURI->Equals(aURI, &eq)) && eq)
return true;
}
return false;
}
nsresult nsXBLStreamListener::HandleEvent(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<nsIDocument> bindingDocument = do_QueryInterface(target);
NS_ASSERTION(bindingDocument, "Event not targeted at document?!");
// See if we're still alive.
nsCOMPtr<nsIDocument> doc(do_QueryReferent(mBoundDocument));
if (!doc) {
NS_WARNING(
"XBL load did not complete until after document went away! Modal "
"dialog bug?\n");
} else {
// We have to do a flush prior to notification of the document load.
// This has to happen since the HTML content sink can be holding on
// to notifications related to our children (e.g., if you bind to the
// <body> tag) that result in duplication of content.
// We need to get the sink's notifications flushed and then make the binding
// ready.
if (count > 0) {
nsXBLBindingRequest* req = mBindingRequests.ElementAt(0);
nsIDocument* document = req->mBoundElement->GetUncomposedDoc();
if (document)
document->FlushPendingNotifications(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<nsXBLDocumentInfo> info =
xblDocBindingManager->GetXBLDocumentInfo(documentURI);
xblDocBindingManager->RemoveXBLDocumentInfo(
info); // Break the self-imposed cycle.
if (!info) {
if (nsXBLService::IsChromeOrResourceURI(documentURI)) {
NS_WARNING(
"An XBL file is malformed. Did you forget the XBL namespace on the "
"bindings tag?");
}
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("XBL"), nullptr,
nsContentUtils::eXBL_PROPERTIES,
"MalformedXBL", nullptr, 0, documentURI);
return NS_ERROR_FAILURE;
}
// If the doc is a chrome URI, then we put it into the XUL cache.
#ifdef MOZ_XUL
if (nsXBLService::IsChromeOrResourceURI(documentURI)) {
nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
if (cache && cache->IsEnabled()) cache->PutXBLDocumentInfo(info);
}
#endif
bindingManager->PutXBLDocumentInfo(info);
// Notify all pending requests that their bindings are
// ready and can be installed.
for (i = 0; i < count; i++) {
nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
req->DocumentLoaded(bindingDocument);
}
}
target->RemoveEventListener(NS_LITERAL_STRING("load"), this, false);
return rv;
}
// Implementation //////////////////////////////////////////////////////////////
// Implement our nsISupports methods
NS_IMPL_ISUPPORTS(nsXBLService, nsISupportsWeakReference)
void nsXBLService::Init() {
gInstance = new nsXBLService();
NS_ADDREF(gInstance);
}
// Constructors/Destructors
nsXBLService::nsXBLService(void) {}
nsXBLService::~nsXBLService(void) {}
// static
bool nsXBLService::IsChromeOrResourceURI(nsIURI* aURI) {
bool isChrome = false;
bool isResource = false;
if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) &&
NS_SUCCEEDED(aURI->SchemeIs("resource", &isResource)))
return (isChrome || isResource);
return false;
}
// 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<nsIURI> 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 only the whitelisted bindings are 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, assert that:
//
// a) The binding is XMLPrettyPrint (since it may be bound to any XML)
// b) The binding is bound to one of the whitelisted element.
//
// 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() &&
!aURL->GetSpecOrDefault().EqualsLiteral(
"chrome://global/content/xml/XMLPrettyPrint.xml#prettyprint")) {
nsAtom* tag = aElement->NodeInfo()->NameAtom();
MOZ_ASSERT(
// datetimebox
tag == nsGkAtoms::datetimebox ||
// videocontrols
tag == nsGkAtoms::videocontrols ||
// pluginProblem
tag == nsGkAtoms::embed || tag == nsGkAtoms::applet ||
tag == nsGkAtoms::object ||
// xbl-marquee
tag == nsGkAtoms::marquee,
"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<nsIDocument> 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<nsXBLBinding> 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<nsIDocument> 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<EventTarget> piTarget = aTarget;
nsCOMPtr<nsIContent> contentNode(do_QueryInterface(aTarget));
if (contentNode) {
// Only attach if we're really in a document
nsCOMPtr<nsIDocument> doc = contentNode->GetUncomposedDoc();
if (doc) piTarget = doc; // We're a XUL keyset. Attach to our document.
}
if (!piTarget) return NS_ERROR_FAILURE;
EventListenerManager* manager = piTarget->GetOrCreateListenerManager();
if (!manager) return NS_ERROR_FAILURE;
// the listener already exists, so skip this
if (contentNode && contentNode->GetProperty(nsGkAtoms::listener))
return NS_OK;
Element* elt = Element::FromNodeOrNull(contentNode);
// Create the key handler
RefPtr<nsXBLWindowKeyHandler> handler =
NS_NewXBLWindowKeyHandler(elt, piTarget);
handler->InstallKeyboardEventListenersTo(manager);
if (contentNode)
return contentNode->SetProperty(nsGkAtoms::listener,
handler.forget().take(),
nsPropertyTable::SupportsDtorFunc, true);
// The reference to the handler will be maintained by the event target,
// and, if there is a content node, the property.
return NS_OK;
}
//
// DetachGlobalKeyHandler
//
// Removes a key handler added by DeatchGlobalKeyHandler.
//
nsresult nsXBLService::DetachGlobalKeyHandler(EventTarget* aTarget) {
nsCOMPtr<EventTarget> piTarget = aTarget;
nsCOMPtr<nsIContent> contentNode(do_QueryInterface(aTarget));
if (!contentNode) // detaching is only supported for content nodes
return NS_ERROR_FAILURE;
// Only attach if we're really in a document
nsCOMPtr<nsIDocument> doc = contentNode->GetUncomposedDoc();
if (doc) piTarget = doc;
if (!piTarget) return NS_ERROR_FAILURE;
EventListenerManager* manager = piTarget->GetOrCreateListenerManager();
if (!manager) return NS_ERROR_FAILURE;
nsIDOMEventListener* handler = static_cast<nsIDOMEventListener*>(
contentNode->GetProperty(nsGkAtoms::listener));
if (!handler) return NS_ERROR_FAILURE;
static_cast<nsXBLWindowKeyHandler*>(handler)
->RemoveKeyboardEventListenersFrom(manager);
contentNode->DeleteProperty(nsGkAtoms::listener);
return NS_OK;
}
// Internal helper methods /////////////////////////////////////////////////////
nsresult nsXBLService::BindingReady(nsIContent* aBoundElement, nsIURI* aURI,
bool* aIsReady) {
// Don't do a security check here; we know this binding is set to go.
return GetBinding(aBoundElement, aURI, true, nullptr, aIsReady, nullptr);
}
nsresult nsXBLService::GetBinding(nsIContent* aBoundElement, nsIURI* aURI,
bool aPeekOnly,
nsIPrincipal* aOriginPrincipal,
bool* aIsReady, nsXBLBinding** aResult) {
// More than 6 binding URIs are rare, see bug 55070 comment 18.
AutoTArray<nsCOMPtr<nsIURI>, 6> uris;
return GetBinding(aBoundElement, aURI, aPeekOnly, aOriginPrincipal, aIsReady,
aResult, uris);
}
static bool MayBindToContent(nsXBLPrototypeBinding* aProtoBinding,
nsIContent* aBoundElement, nsIURI* aURI) {
// If this binding explicitly allows untrusted content, we're done.
if (aProtoBinding->BindToUntrustedContent()) {
return true;
}
// We let XUL content and content in XUL documents through, since XUL is
// restricted anyway and we want to minimize remote XUL breakage.
if (aBoundElement->IsXULElement() ||
aBoundElement->OwnerDoc()->IsXULElement()) {
return true;
}
// Similarly, we make an exception for anonymous content (which
// lives in the XBL scope), because it's already protected from content,
// and tends to use a lot of bindings that we wouldn't otherwise need to
// whitelist.
if (aBoundElement->IsInAnonymousSubtree()) {
return true;
}
// Allow if the bound content subsumes the binding.
nsCOMPtr<nsIDocument> bindingDoc =
aProtoBinding->XBLDocumentInfo()->GetDocument();
NS_ENSURE_TRUE(bindingDoc, false);
if (aBoundElement->NodePrincipal()->Subsumes(bindingDoc->NodePrincipal())) {
return true;
}
// One last special case: we need to watch out for in-document data: URI
// bindings from remote-XUL-whitelisted domains (especially tests), because
// they end up with a null principal (rather than inheriting the document's
// principal), which causes them to fail the check above.
if (nsContentUtils::AllowXULXBLForPrincipal(aBoundElement->NodePrincipal())) {
bool isDataURI = false;
nsresult rv = aURI->SchemeIs("data", &isDataURI);
NS_ENSURE_SUCCESS(rv, false);
if (isDataURI) {
return true;
}
}
// Disallow.
return false;
}
nsresult nsXBLService::GetBinding(nsIContent* aBoundElement, nsIURI* aURI,
bool aPeekOnly,
nsIPrincipal* aOriginPrincipal,
bool* aIsReady, nsXBLBinding** aResult,
nsTArray<nsCOMPtr<nsIURI>>& aDontExtendURIs) {
NS_ASSERTION(aPeekOnly || aResult,
"Must have non-null out param if not just peeking to see "
"whether the binding is ready");
if (aResult) *aResult = nullptr;
if (!aURI) return NS_ERROR_FAILURE;
nsAutoCString ref;
aURI->GetRef(ref);
nsCOMPtr<nsIDocument> boundDocument = aBoundElement->OwnerDoc();
RefPtr<nsXBLDocumentInfo> docInfo;
nsresult rv =
LoadBindingDocumentInfo(aBoundElement, boundDocument, aURI,
aOriginPrincipal, false, getter_AddRefs(docInfo));
NS_ENSURE_SUCCESS(rv, rv);
if (!docInfo) return NS_ERROR_FAILURE;
WeakPtr<nsXBLPrototypeBinding> protoBinding =
docInfo->GetPrototypeBinding(ref);
if (!protoBinding) {
#ifdef DEBUG
nsAutoCString message("Unable to locate an XBL binding for URI ");
message += aURI->GetSpecOrDefault();
message += " in document ";
message += boundDocument->GetDocumentURI()->GetSpecOrDefault();
NS_WARNING(message.get());
#endif
return NS_ERROR_FAILURE;
}
// If the binding isn't whitelisted, refuse to apply it to content that
// doesn't subsume it (modulo a few exceptions).
if (!MayBindToContent(protoBinding, aBoundElement, aURI)) {
#ifdef DEBUG
nsAutoCString message("Permission denied to apply binding ");
message += aURI->GetSpecOrDefault();
message +=
" to unprivileged content. Set bindToUntrustedContent=true on "
"the binding to override this restriction.";
NS_WARNING(message.get());
#endif
return NS_ERROR_FAILURE;
}
aDontExtendURIs.AppendElement(protoBinding->BindingURI());
nsCOMPtr<nsIURI> altBindingURI = protoBinding->AlternateBindingURI();
if (altBindingURI) {
aDontExtendURIs.AppendElement(altBindingURI);
}
// Our prototype binding must have all its resources loaded.
bool ready = protoBinding->LoadResources(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<nsIURI> baseBindingURI;
WeakPtr<nsXBLPrototypeBinding> baseProto = protoBinding->GetBasePrototype();
if (baseProto) {
baseBindingURI = baseProto->BindingURI();
} else {
baseBindingURI = protoBinding->GetBaseBindingURI();
if (baseBindingURI) {
uint32_t count = aDontExtendURIs.Length();
for (uint32_t index = 0; index < count; ++index) {
bool equal;
rv = aDontExtendURIs[index]->Equals(baseBindingURI, &equal);
NS_ENSURE_SUCCESS(rv, rv);
if (equal) {
NS_ConvertUTF8toUTF16 protoSpec(
protoBinding->BindingURI()->GetSpecOrDefault());
NS_ConvertUTF8toUTF16 baseSpec(baseBindingURI->GetSpecOrDefault());
const char16_t* params[] = {protoSpec.get(), baseSpec.get()};
nsContentUtils::ReportToConsole(
nsIScriptError::warningFlag, NS_LITERAL_CSTRING("XBL"), nullptr,
nsContentUtils::eXBL_PROPERTIES, "CircularExtendsBinding", params,
ArrayLength(params), boundDocument->GetDocumentURI());
return NS_ERROR_ILLEGAL_VALUE;
}
}
}
}
RefPtr<nsXBLBinding> baseBinding;
if (baseBindingURI) {
nsCOMPtr<nsIContent> child = protoBinding->GetBindingElement();
rv = GetBinding(aBoundElement, baseBindingURI, aPeekOnly,
child->NodePrincipal(), aIsReady,
getter_AddRefs(baseBinding), aDontExtendURIs);
if (NS_FAILED(rv)) return rv; // We aren't ready yet.
}
*aIsReady = true;
if (!aPeekOnly) {
// Make a new binding
NS_ENSURE_STATE(protoBinding);
nsXBLBinding* newBinding = new nsXBLBinding(protoBinding);
if (baseBinding) {
if (!baseProto) {
protoBinding->SetBasePrototype(baseBinding->PrototypeBinding());
}
newBinding->SetBaseBinding(baseBinding);
}
NS_ADDREF(*aResult = newBinding);
}
return NS_OK;
}
nsresult nsXBLService::LoadBindingDocumentInfo(nsIContent* aBoundElement,
nsIDocument* 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<nsXBLDocumentInfo> info;
nsCOMPtr<nsIURI> 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<nsIStreamListener> listener;
if (bindingManager)
listener = bindingManager->GetLoadingDocListener(documentURI);
if (listener) {
nsXBLStreamListener* xblListener =
static_cast<nsXBLStreamListener*>(listener.get());
// Create a new load observer.
if (!xblListener->HasRequest(aBindingURI, aBoundElement)) {
nsXBLBindingRequest* req =
new nsXBLBindingRequest(aBindingURI, aBoundElement);
xblListener->AddRequest(req);
}
return NS_OK;
}
}
#ifdef MOZ_XUL
// The second line of defense is the global nsXULPrototypeCache,
// if it's being used.
nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
bool useXULCache = cache && cache->IsEnabled();
if (!info && useXULCache) {
// This cache crosses the entire product, so that any XBL bindings that are
// part of chrome will be reused across all XUL documents.
info = cache->GetXBLDocumentInfo(documentURI);
}
bool useStartupCache = useXULCache && IsChromeOrResourceURI(documentURI);
if (!info) {
// Next, look in the startup cache
if (!info && useStartupCache) {
rv = nsXBLDocumentInfo::ReadPrototypeBindings(
documentURI, getter_AddRefs(info), 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<nsIDocument> document;
rv = FetchBindingDocument(aBoundElement, aBoundDocument, documentURI,
aBindingURI, aOriginPrincipal, aForceSyncLoad,
getter_AddRefs(document));
NS_ENSURE_SUCCESS(rv, rv);
if (document) {
nsBindingManager* xblDocBindingManager = document->BindingManager();
info = xblDocBindingManager->GetXBLDocumentInfo(documentURI);
if (!info) {
NS_ERROR(
"An XBL file is malformed. Did you forget the XBL namespace on "
"the bindings tag?");
return NS_ERROR_FAILURE;
}
xblDocBindingManager->RemoveXBLDocumentInfo(
info); // Break the self-imposed cycle.
// If the doc is a chrome URI, then we put it into the XUL cache.
#ifdef MOZ_XUL
if (useStartupCache) {
cache->PutXBLDocumentInfo(info);
// now write the bindings into the startup cache
info->WritePrototypeBindings();
}
#endif
}
}
if (info && bindingManager) {
// Cache it in our binding manager's document table. This way,
// we can ensure that if the document has loaded this binding
// before, it can continue to use it even if the XUL prototype
// cache gets flushed. That way, if a flush does occur, we
// don't get into a weird state where we're using different
// XBLDocumentInfos for the same XBL document in a single
// document that has loaded some bindings.
bindingManager->PutXBLDocumentInfo(info);
}
info.forget(aResult);
return NS_OK;
}
nsresult nsXBLService::FetchBindingDocument(
nsIContent* aBoundElement, nsIDocument* aBoundDocument,
nsIURI* aDocumentURI, nsIURI* aBindingURI, nsIPrincipal* aOriginPrincipal,
bool aForceSyncLoad, nsIDocument** aResult) {
nsresult rv = NS_OK;
// Initialize our out pointer to nullptr
*aResult = nullptr;
// Now we have to synchronously load the binding file.
// Create an XML content sink and a parser.
nsCOMPtr<nsILoadGroup> loadGroup;
if (aBoundDocument) loadGroup = aBoundDocument->GetDocumentLoadGroup();
// We really shouldn't have to force a sync load for anything here... could
// we get away with not doing that? Not sure.
if (IsChromeOrResourceURI(aDocumentURI)) aForceSyncLoad = true;
// Create document and contentsink and set them up.
nsCOMPtr<nsIDocument> doc;
rv = NS_NewXMLDocument(getter_AddRefs(doc));
NS_ENSURE_SUCCESS(rv, rv);
// 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<nsIXMLContentSink> xblSink;
rv =
NS_NewXBLContentSink(getter_AddRefs(xblSink), doc, aDocumentURI, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
// Open channel
// Note: There are some cases where aOriginPrincipal and aBoundDocument are
// purposely set to null (to bypass security checks) when calling
// LoadBindingDocumentInfo() which calls FetchBindingDocument(). LoadInfo
// will end up with no principal or node in those cases, so we use
// systemPrincipal. This achieves the same result of bypassing security
// checks, but it gives the wrong information to potential future consumers of
// loadInfo.
nsCOMPtr<nsIChannel> channel;
if (aOriginPrincipal) {
// if there is an originPrincipal we should also have aBoundDocument
MOZ_ASSERT(aBoundDocument,
"can not create a channel without aBoundDocument");
rv = NS_NewChannelWithTriggeringPrincipal(
getter_AddRefs(channel), aDocumentURI, aBoundDocument, aOriginPrincipal,
nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS |
nsILoadInfo::SEC_ALLOW_CHROME,
nsIContentPolicy::TYPE_XBL,
nullptr, // aPerformanceStorage
loadGroup);
} else {
rv = NS_NewChannel(getter_AddRefs(channel), aDocumentURI,
nsContentUtils::GetSystemPrincipal(),
nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS,
nsIContentPolicy::TYPE_XBL,
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->AsyncOpen2(xblListener);
if (NS_FAILED(rv)) {
// Well, we won't be getting a load. Make sure to clean up our stuff!
if (bindingManager) {
bindingManager->RemoveLoadingDocListener(aDocumentURI);
}
}
return NS_OK;
}
nsCOMPtr<nsIStreamListener> listener;
rv = doc->StartDocumentLoad("loadAsInteractiveData", channel, loadGroup,
nullptr, getter_AddRefs(listener), true, xblSink);
NS_ENSURE_SUCCESS(rv, rv);
// Now do a blocking synchronous parse of the file.
nsCOMPtr<nsIInputStream> in;
rv = channel->Open2(getter_AddRefs(in));
NS_ENSURE_SUCCESS(rv, rv);
rv = nsSyncLoadService::PushSyncStreamToListener(in.forget(), listener,
channel);
NS_ENSURE_SUCCESS(rv, rv);
doc.swap(*aResult);
return NS_OK;
}