gecko-dev/dom/xbl/nsXBLService.cpp

1093 lines
35 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 "nsCOMPtr.h"
#include "nsNetUtil.h"
#include "nsXBLService.h"
#include "nsXBLWindowKeyHandler.h"
#include "nsIInputStream.h"
#include "nsNameSpaceManager.h"
#include "nsIURI.h"
#include "nsIDOMElement.h"
#include "nsIURL.h"
#include "nsIChannel.h"
#include "nsXPIDLString.h"
#include "plstr.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "nsIXMLContentSink.h"
#include "nsContentCID.h"
#include "mozilla/dom/XMLDocument.h"
#include "nsGkAtoms.h"
#include "nsIMemory.h"
#include "nsIObserverService.h"
#include "nsIDOMNodeList.h"
#include "nsXBLContentSink.h"
#include "nsXBLBinding.h"
#include "nsXBLPrototypeBinding.h"
#include "nsXBLDocumentInfo.h"
#include "nsCRT.h"
#include "nsContentUtils.h"
#include "nsSyncLoadService.h"
#include "nsContentPolicyUtils.h"
#include "nsTArray.h"
#include "nsError.h"
#include "nsIPresShell.h"
#include "nsIDocumentObserver.h"
#include "nsFrameManager.h"
#include "nsStyleContext.h"
#include "nsIScriptSecurityManager.h"
#include "nsIScriptError.h"
#include "nsXBLSerialize.h"
#ifdef MOZ_XUL
#include "nsXULPrototypeCache.h"
#endif
#include "nsIDOMEventListener.h"
#include "mozilla/Attributes.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/Element.h"
using namespace mozilla;
using namespace mozilla::dom;
#define NS_MAX_XBL_BINDING_RECURSION 20
nsXBLService* nsXBLService::gInstance = nullptr;
static bool
IsAncestorBinding(nsIDocument* aDocument,
nsIURI* aChildBindingURI,
nsIContent* aChild)
{
NS_ASSERTION(aDocument, "expected a document");
NS_ASSERTION(aChildBindingURI, "expected a binding URI");
NS_ASSERTION(aChild, "expected a child content");
uint32_t bindingRecursion = 0;
for (nsIContent *bindingParent = aChild->GetBindingParent();
bindingParent;
bindingParent = bindingParent->GetBindingParent()) {
nsXBLBinding* binding = bindingParent->GetXBLBinding();
if (!binding) {
continue;
}
if (binding->PrototypeBinding()->CompareBindingURI(aChildBindingURI)) {
++bindingRecursion;
if (bindingRecursion < NS_MAX_XBL_BINDING_RECURSION) {
continue;
}
nsAutoCString spec;
aChildBindingURI->GetSpec(spec);
NS_ConvertUTF8toUTF16 bindingURI(spec);
const char16_t* params[] = { bindingURI.get() };
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("XBL"), aDocument,
nsContentUtils::eXBL_PROPERTIES,
"TooDeepBindingRecursion",
params, ArrayLength(params));
return true;
}
}
return false;
}
// Individual binding requests.
class nsXBLBindingRequest
{
public:
nsCOMPtr<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->GetCurrentDoc();
if (!doc)
return;
// Destroy the frames for mBoundElement.
nsIContent* destroyedFramesFor = nullptr;
nsIPresShell* shell = doc->GetShell();
if (shell) {
shell->DestroyFramesFor(mBoundElement, &destroyedFramesFor);
}
MOZ_ASSERT(!mBoundElement->GetPrimaryFrame());
// Get the binding.
bool ready = false;
nsXBLService::GetInstance()->BindingReady(mBoundElement, mBindingURI, &ready);
if (!ready)
return;
// If |mBoundElement| is (in addition to having binding |mBinding|)
// also a descendant of another element with binding |mBinding|,
// then we might have just constructed it due to the
// notification of its parent. (We can know about both if the
// binding loads were triggered from the DOM rather than frame
// construction.) So we have to check both whether the element
// has a primary frame and whether it's in the frame manager maps
// before sending a ContentInserted notification, or bad things
// will happen.
MOZ_ASSERT(shell == doc->GetShell());
if (shell) {
nsIFrame* childFrame = mBoundElement->GetPrimaryFrame();
if (!childFrame) {
// Check to see if it's in the undisplayed content map...
nsFrameManager* fm = shell->FrameManager();
nsStyleContext* sc = fm->GetUndisplayedContent(mBoundElement);
if (!sc) {
// or in the display:contents map.
sc = fm->GetDisplayContentsStyleFor(mBoundElement);
}
if (!sc) {
shell->CreateFramesFor(destroyedFramesFor);
}
}
}
}
nsXBLBindingRequest(nsIURI* aURI, nsIContent* aBoundElement)
: mBindingURI(aURI),
mBoundElement(aBoundElement)
{
}
};
// nsXBLStreamListener, a helper class used for
// asynchronous parsing of URLs
/* Header file */
class nsXBLStreamListener final : public nsIStreamListener,
public nsIDOMEventListener
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSIDOMEVENTLISTENER
nsXBLStreamListener(nsIDocument* aBoundDocument,
nsIXMLContentSink* aSink,
nsIDocument* aBindingDocument);
void AddRequest(nsXBLBindingRequest* aRequest) { mBindingRequests.AppendElement(aRequest); }
bool HasRequest(nsIURI* aURI, nsIContent* aBoundElement);
private:
~nsXBLStreamListener();
nsCOMPtr<nsIStreamListener> mInner;
AutoTArray<nsXBLBindingRequest*, 8> mBindingRequests;
nsCOMPtr<nsIWeakReference> mBoundDocument;
nsCOMPtr<nsIXMLContentSink> mSink; // Only set until OnStartRequest
nsCOMPtr<nsIDocument> mBindingDocument; // Only set until OnStartRequest
};
/* Implementation file */
NS_IMPL_ISUPPORTS(nsXBLStreamListener,
nsIStreamListener,
nsIRequestObserver,
nsIDOMEventListener)
nsXBLStreamListener::nsXBLStreamListener(nsIDocument* aBoundDocument,
nsIXMLContentSink* aSink,
nsIDocument* aBindingDocument)
: mSink(aSink), mBindingDocument(aBindingDocument)
{
/* member initializers and constructor code */
mBoundDocument = do_GetWeakReference(aBoundDocument);
}
nsXBLStreamListener::~nsXBLStreamListener()
{
for (uint32_t i = 0; i < mBindingRequests.Length(); i++) {
nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
delete req;
}
}
NS_IMETHODIMP
nsXBLStreamListener::OnDataAvailable(nsIRequest *request, nsISupports* aCtxt,
nsIInputStream* aInStr,
uint64_t aSourceOffset, uint32_t aCount)
{
if (mInner)
return mInner->OnDataAvailable(request, aCtxt, aInStr, aSourceOffset, aCount);
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsXBLStreamListener::OnStartRequest(nsIRequest* request, nsISupports* aCtxt)
{
// Make sure we don't hold on to the sink and binding document past this point
nsCOMPtr<nsIXMLContentSink> sink;
mSink.swap(sink);
nsCOMPtr<nsIDocument> doc;
mBindingDocument.swap(doc);
nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsILoadGroup> group;
request->GetLoadGroup(getter_AddRefs(group));
nsresult rv = doc->StartDocumentLoad("loadAsInteractiveData",
channel,
group,
nullptr,
getter_AddRefs(mInner),
true,
sink);
NS_ENSURE_SUCCESS(rv, rv);
// Make sure to add ourselves as a listener after StartDocumentLoad,
// since that resets the event listners on the document.
doc->AddEventListener(NS_LITERAL_STRING("load"), this, false);
return mInner->OnStartRequest(request, aCtxt);
}
NS_IMETHODIMP
nsXBLStreamListener::OnStopRequest(nsIRequest* request, nsISupports* aCtxt, nsresult aStatus)
{
nsresult rv = NS_OK;
if (mInner) {
rv = mInner->OnStopRequest(request, aCtxt, aStatus);
}
// Don't hold onto the inner listener; holding onto it can create a cycle
// with the document
mInner = nullptr;
return rv;
}
bool
nsXBLStreamListener::HasRequest(nsIURI* aURI, nsIContent* aElt)
{
// XXX Could be more efficient.
uint32_t count = mBindingRequests.Length();
for (uint32_t i = 0; i < count; i++) {
nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
bool eq;
if (req->mBoundElement == aElt &&
NS_SUCCEEDED(req->mBindingURI->Equals(aURI, &eq)) && eq)
return true;
}
return false;
}
nsresult
nsXBLStreamListener::HandleEvent(nsIDOMEvent* aEvent)
{
nsresult rv = NS_OK;
uint32_t i;
uint32_t count = mBindingRequests.Length();
// Get the binding document; note that we don't hold onto it in this object
// to avoid creating a cycle
Event* event = aEvent->InternalDOMEvent();
EventTarget* target = event->GetCurrentTarget();
nsCOMPtr<nsIDocument> bindingDocument = do_QueryInterface(target);
NS_ASSERTION(bindingDocument, "Event not targeted at document?!");
// See if we're still alive.
nsCOMPtr<nsIDocument> doc(do_QueryReferent(mBoundDocument));
if (!doc) {
NS_WARNING("XBL load did not complete until after document went away! Modal dialog bug?\n");
}
else {
// We have to do a flush prior to notification of the document load.
// This has to happen since the HTML content sink can be holding on
// to notifications related to our children (e.g., if you bind to the
// <body> tag) that result in duplication of content.
// We need to get the sink's notifications flushed and then make the binding
// ready.
if (count > 0) {
nsXBLBindingRequest* req = mBindingRequests.ElementAt(0);
nsIDocument* document = req->mBoundElement->GetCurrentDoc();
if (document)
document->FlushPendingNotifications(Flush_ContentAndNotify);
}
// Remove ourselves from the set of pending docs.
nsBindingManager *bindingManager = doc->BindingManager();
nsIURI* documentURI = bindingDocument->GetDocumentURI();
bindingManager->RemoveLoadingDocListener(documentURI);
if (!bindingDocument->GetRootElement()) {
// FIXME: How about an error console warning?
NS_WARNING("XBL doc with no root element - this usually shouldn't happen");
return NS_ERROR_FAILURE;
}
// Put our doc info in the doc table.
nsBindingManager *xblDocBindingManager = bindingDocument->BindingManager();
RefPtr<nsXBLDocumentInfo> info =
xblDocBindingManager->GetXBLDocumentInfo(documentURI);
xblDocBindingManager->RemoveXBLDocumentInfo(info); // Break the self-imposed cycle.
if (!info) {
if (nsXBLService::IsChromeOrResourceURI(documentURI)) {
NS_WARNING("An XBL file is malformed. Did you forget the XBL namespace on the bindings tag?");
}
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("XBL"), nullptr,
nsContentUtils::eXBL_PROPERTIES,
"MalformedXBL",
nullptr, 0, documentURI);
return NS_ERROR_FAILURE;
}
// If the doc is a chrome URI, then we put it into the XUL cache.
#ifdef MOZ_XUL
if (nsXBLService::IsChromeOrResourceURI(documentURI)) {
nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
if (cache && cache->IsEnabled())
cache->PutXBLDocumentInfo(info);
}
#endif
bindingManager->PutXBLDocumentInfo(info);
// Notify all pending requests that their bindings are
// ready and can be installed.
for (i = 0; i < count; i++) {
nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
req->DocumentLoaded(bindingDocument);
}
}
target->RemoveEventListener(NS_LITERAL_STRING("load"), this, false);
return rv;
}
// Implementation /////////////////////////////////////////////////////////////////
// Implement our nsISupports methods
NS_IMPL_ISUPPORTS(nsXBLService, nsISupportsWeakReference)
void
nsXBLService::Init()
{
gInstance = new nsXBLService();
NS_ADDREF(gInstance);
}
// Constructors/Destructors
nsXBLService::nsXBLService(void)
{
}
nsXBLService::~nsXBLService(void)
{
}
// static
bool
nsXBLService::IsChromeOrResourceURI(nsIURI* aURI)
{
bool isChrome = false;
bool isResource = false;
if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) &&
NS_SUCCEEDED(aURI->SchemeIs("resource", &isResource)))
return (isChrome || isResource);
return false;
}
// This function loads a particular XBL file and installs all of the bindings
// onto the element.
nsresult
nsXBLService::LoadBindings(nsIContent* aContent, nsIURI* aURL,
nsIPrincipal* aOriginPrincipal,
nsXBLBinding** aBinding, bool* aResolveStyle)
{
NS_PRECONDITION(aOriginPrincipal, "Must have an origin principal");
*aBinding = nullptr;
*aResolveStyle = false;
nsresult rv;
nsCOMPtr<nsIDocument> document = aContent->OwnerDoc();
nsAutoCString urlspec;
if (nsContentUtils::GetWrapperSafeScriptFilename(document, aURL, urlspec)) {
// Block an attempt to load a binding that has special wrapper
// automation needs.
return NS_OK;
}
nsXBLBinding *binding = aContent->GetXBLBinding();
if (binding) {
if (binding->MarkedForDeath()) {
FlushStyleBindings(aContent);
binding = nullptr;
}
else {
// See if the URIs match.
if (binding->PrototypeBinding()->CompareBindingURI(aURL))
return NS_OK;
FlushStyleBindings(aContent);
binding = nullptr;
}
}
bool ready;
RefPtr<nsXBLBinding> newBinding;
if (NS_FAILED(rv = GetBinding(aContent, aURL, false, aOriginPrincipal,
&ready, getter_AddRefs(newBinding)))) {
return rv;
}
if (!newBinding) {
#ifdef DEBUG
nsAutoCString spec;
aURL->GetSpec(spec);
nsAutoCString str(NS_LITERAL_CSTRING("Failed to locate XBL binding. XBL is now using id instead of name to reference bindings. Make sure you have switched over. The invalid binding name is: ") + spec);
NS_ERROR(str.get());
#endif
return NS_OK;
}
if (::IsAncestorBinding(document, aURL, aContent)) {
return NS_ERROR_ILLEGAL_VALUE;
}
// We loaded a style binding. It goes on the end.
if (binding) {
// Get the last binding that is in the append layer.
binding->RootBinding()->SetBaseBinding(newBinding);
}
else {
// Install the binding on the content node.
aContent->SetXBLBinding(newBinding);
}
{
nsAutoScriptBlocker scriptBlocker;
// Set the binding's bound element.
newBinding->SetBoundElement(aContent);
// Tell the binding to build the anonymous content.
newBinding->GenerateAnonymousContent();
// Tell the binding to install event handlers
newBinding->InstallEventHandlers();
// Set up our properties
rv = newBinding->InstallImplementation();
NS_ENSURE_SUCCESS(rv, rv);
// Figure out if we have any scoped sheets. If so, we do a second resolve.
*aResolveStyle = newBinding->HasStyleSheets();
newBinding.swap(*aBinding);
}
return NS_OK;
}
nsresult
nsXBLService::FlushStyleBindings(nsIContent* aContent)
{
nsCOMPtr<nsIDocument> document = aContent->OwnerDoc();
nsXBLBinding *binding = aContent->GetXBLBinding();
if (binding) {
// Clear out the script references.
binding->ChangeDocument(document, nullptr);
aContent->SetXBLBinding(nullptr); // Flush old style bindings
}
return NS_OK;
}
//
// AttachGlobalKeyHandler
//
// Creates a new key handler and prepares to listen to key events on the given
// event receiver (either a document or an content node). If the receiver is content,
// then extra work needs to be done to hook it up to the document (XXX WHY??)
//
nsresult
nsXBLService::AttachGlobalKeyHandler(EventTarget* aTarget)
{
// check if the receiver is a content node (not a document), and hook
// it to the document if that is the case.
nsCOMPtr<EventTarget> piTarget = aTarget;
nsCOMPtr<nsIContent> contentNode(do_QueryInterface(aTarget));
if (contentNode) {
// Only attach if we're really in a document
nsCOMPtr<nsIDocument> doc = contentNode->GetCurrentDoc();
if (doc)
piTarget = doc; // We're a XUL keyset. Attach to our document.
}
EventListenerManager* manager = piTarget->GetOrCreateListenerManager();
if (!piTarget || !manager)
return NS_ERROR_FAILURE;
// the listener already exists, so skip this
if (contentNode && contentNode->GetProperty(nsGkAtoms::listener))
return NS_OK;
nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(contentNode));
// Create the key handler
RefPtr<nsXBLWindowKeyHandler> handler =
NS_NewXBLWindowKeyHandler(elt, piTarget);
handler->InstallKeyboardEventListenersTo(manager);
if (contentNode)
return contentNode->SetProperty(nsGkAtoms::listener,
handler.forget().take(),
nsPropertyTable::SupportsDtorFunc, true);
// The reference to the handler will be maintained by the event target,
// and, if there is a content node, the property.
return NS_OK;
}
//
// DetachGlobalKeyHandler
//
// Removes a key handler added by DeatchGlobalKeyHandler.
//
nsresult
nsXBLService::DetachGlobalKeyHandler(EventTarget* aTarget)
{
nsCOMPtr<EventTarget> piTarget = aTarget;
nsCOMPtr<nsIContent> contentNode(do_QueryInterface(aTarget));
if (!contentNode) // detaching is only supported for content nodes
return NS_ERROR_FAILURE;
// Only attach if we're really in a document
nsCOMPtr<nsIDocument> doc = contentNode->GetCurrentDoc();
if (doc)
piTarget = do_QueryInterface(doc);
EventListenerManager* manager = piTarget->GetOrCreateListenerManager();
if (!piTarget || !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 uriSpec;
aURI->GetSpec(uriSpec);
nsAutoCString doc;
boundDocument->GetDocumentURI()->GetSpec(doc);
nsAutoCString message("Unable to locate an XBL binding for URI ");
message += uriSpec;
message += " in document ";
message += doc;
NS_WARNING(message.get());
#endif
return NS_ERROR_FAILURE;
}
// If the binding isn't whitelisted, refuse to apply it to content that
// doesn't subsume it (modulo a few exceptions).
if (!MayBindToContent(protoBinding, aBoundElement, aURI)) {
#ifdef DEBUG
nsAutoCString uriSpec;
aURI->GetSpec(uriSpec);
nsAutoCString message("Permission denied to apply binding ");
message += uriSpec;
message += " to unprivileged content. Set bindToUntrustedContent=true on "
"the binding to override this restriction.";
NS_WARNING(message.get());
#endif
return NS_ERROR_FAILURE;
}
aDontExtendURIs.AppendElement(protoBinding->BindingURI());
nsCOMPtr<nsIURI> altBindingURI = protoBinding->AlternateBindingURI();
if (altBindingURI) {
aDontExtendURIs.AppendElement(altBindingURI);
}
// Our prototype binding must have all its resources loaded.
bool ready = protoBinding->LoadResources();
if (!ready) {
// Add our bound element to the protos list of elts that should
// be notified when the stylesheets and scripts finish loading.
protoBinding->AddResourceListener(aBoundElement);
return NS_ERROR_FAILURE; // The binding isn't ready yet.
}
rv = protoBinding->ResolveBaseBinding();
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> baseBindingURI;
WeakPtr<nsXBLPrototypeBinding> baseProto = protoBinding->GetBasePrototype();
if (baseProto) {
baseBindingURI = baseProto->BindingURI();
}
else {
baseBindingURI = protoBinding->GetBaseBindingURI();
if (baseBindingURI) {
uint32_t count = aDontExtendURIs.Length();
for (uint32_t index = 0; index < count; ++index) {
bool equal;
rv = aDontExtendURIs[index]->Equals(baseBindingURI, &equal);
NS_ENSURE_SUCCESS(rv, rv);
if (equal) {
nsAutoCString spec, basespec;
protoBinding->BindingURI()->GetSpec(spec);
NS_ConvertUTF8toUTF16 protoSpec(spec);
baseBindingURI->GetSpec(basespec);
NS_ConvertUTF8toUTF16 baseSpecUTF16(basespec);
const char16_t* params[] = { protoSpec.get(), baseSpecUTF16.get() };
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("XBL"), nullptr,
nsContentUtils::eXBL_PROPERTIES,
"CircularExtendsBinding",
params, ArrayLength(params),
boundDocument->GetDocumentURI());
return NS_ERROR_ILLEGAL_VALUE;
}
}
}
}
RefPtr<nsXBLBinding> baseBinding;
if (baseBindingURI) {
nsCOMPtr<nsIContent> child = protoBinding->GetBindingElement();
rv = GetBinding(aBoundElement, baseBindingURI, aPeekOnly,
child->NodePrincipal(), aIsReady,
getter_AddRefs(baseBinding), aDontExtendURIs);
if (NS_FAILED(rv))
return rv; // We aren't ready yet.
}
*aIsReady = true;
if (!aPeekOnly) {
// Make a new binding
NS_ENSURE_STATE(protoBinding);
nsXBLBinding *newBinding = new nsXBLBinding(protoBinding);
if (baseBinding) {
if (!baseProto) {
protoBinding->SetBasePrototype(baseBinding->PrototypeBinding());
}
newBinding->SetBaseBinding(baseBinding);
}
NS_ADDREF(*aResult = newBinding);
}
return NS_OK;
}
static bool
IsSystemOrChromeURLPrincipal(nsIPrincipal* aPrincipal)
{
if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
return true;
}
nsCOMPtr<nsIURI> uri;
aPrincipal->GetURI(getter_AddRefs(uri));
NS_ENSURE_TRUE(uri, false);
bool isChrome = false;
return NS_SUCCEEDED(uri->SchemeIs("chrome", &isChrome)) && isChrome;
}
nsresult
nsXBLService::LoadBindingDocumentInfo(nsIContent* aBoundElement,
nsIDocument* aBoundDocument,
nsIURI* aBindingURI,
nsIPrincipal* aOriginPrincipal,
bool aForceSyncLoad,
nsXBLDocumentInfo** aResult)
{
NS_PRECONDITION(aBindingURI, "Must have a binding URI");
NS_PRECONDITION(!aOriginPrincipal || aBoundDocument,
"If we're doing a security check, we better have a document!");
*aResult = nullptr;
// Allow XBL in unprivileged documents if it's specified in a privileged or
// chrome: stylesheet. This allows themes to specify XBL bindings.
if (aOriginPrincipal && !IsSystemOrChromeURLPrincipal(aOriginPrincipal)) {
NS_ENSURE_TRUE(!aBoundDocument || aBoundDocument->AllowXULXBL(),
NS_ERROR_XBL_BLOCKED);
}
RefPtr<nsXBLDocumentInfo> info;
nsCOMPtr<nsIURI> documentURI;
nsresult rv = aBindingURI->CloneIgnoringRef(getter_AddRefs(documentURI));
NS_ENSURE_SUCCESS(rv, rv);
#ifdef MOZ_XUL
// We've got a file. Check our XBL document cache.
nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
bool useXULCache = cache && cache->IsEnabled();
if (useXULCache) {
// The first line of defense is the chrome cache.
// This cache crosses the entire product, so that any XBL bindings that are
// part of chrome will be reused across all XUL documents.
info = cache->GetXBLDocumentInfo(documentURI);
}
#endif
if (!info) {
// The second line of defense is the binding manager's document table.
nsBindingManager *bindingManager = nullptr;
if (aBoundDocument) {
bindingManager = aBoundDocument->BindingManager();
info = bindingManager->GetXBLDocumentInfo(documentURI);
if (aBoundDocument->IsStaticDocument() &&
IsChromeOrResourceURI(aBindingURI)) {
aForceSyncLoad = true;
}
}
NodeInfo *ni = nullptr;
if (aBoundElement)
ni = aBoundElement->NodeInfo();
if (!info && bindingManager &&
(!ni || !(ni->Equals(nsGkAtoms::scrollbar, kNameSpaceID_XUL) ||
ni->Equals(nsGkAtoms::thumb, kNameSpaceID_XUL) ||
((ni->Equals(nsGkAtoms::input) ||
ni->Equals(nsGkAtoms::select)) &&
aBoundElement->IsHTMLElement()))) && !aForceSyncLoad) {
// The third line of defense is to investigate whether or not the
// document is currently being loaded asynchronously. If so, there's no
// document yet, but we need to glom on our request so that it will be
// processed whenever the doc does finish loading.
nsCOMPtr<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
// Next, look in the startup cache
bool useStartupCache = useXULCache && IsChromeOrResourceURI(documentURI);
if (!info && useStartupCache) {
rv = nsXBLDocumentInfo::ReadPrototypeBindings(documentURI, getter_AddRefs(info));
if (NS_SUCCEEDED(rv)) {
cache->PutXBLDocumentInfo(info);
if (bindingManager) {
// Cache it in our binding manager's document table.
bindingManager->PutXBLDocumentInfo(info);
}
}
}
#endif
if (!info) {
// Finally, if all lines of defense fail, we go and fetch the binding
// document.
// Always load chrome synchronously
bool chrome;
if (NS_SUCCEEDED(documentURI->SchemeIs("chrome", &chrome)) && chrome)
aForceSyncLoad = true;
nsCOMPtr<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 (bindingManager) {
// Also put it in our binding manager's document table.
bindingManager->PutXBLDocumentInfo(info);
}
}
}
}
info.forget(aResult);
return NS_OK;
}
nsresult
nsXBLService::FetchBindingDocument(nsIContent* aBoundElement, nsIDocument* aBoundDocument,
nsIURI* aDocumentURI, nsIURI* aBindingURI,
nsIPrincipal* aOriginPrincipal, bool aForceSyncLoad,
nsIDocument** aResult)
{
nsresult rv = NS_OK;
// Initialize our out pointer to nullptr
*aResult = nullptr;
// Now we have to synchronously load the binding file.
// Create an XML content sink and a parser.
nsCOMPtr<nsILoadGroup> loadGroup;
if (aBoundDocument)
loadGroup = aBoundDocument->GetDocumentLoadGroup();
// We really shouldn't have to force a sync load for anything here... could
// we get away with not doing that? Not sure.
if (IsChromeOrResourceURI(aDocumentURI))
aForceSyncLoad = true;
// Create document and contentsink and set them up.
nsCOMPtr<nsIDocument> doc;
rv = NS_NewXMLDocument(getter_AddRefs(doc));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIXMLContentSink> xblSink;
rv = NS_NewXBLContentSink(getter_AddRefs(xblSink), doc, aDocumentURI, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
// Open channel
// Note: There are some cases where aOriginPrincipal and aBoundDocument are purposely
// set to null (to bypass security checks) when calling LoadBindingDocumentInfo() which calls
// FetchBindingDocument(). LoadInfo will end up with no principal or node in those cases,
// so we use systemPrincipal. This achieves the same result of bypassing security checks,
// but it gives the wrong information to potential future consumers of loadInfo.
nsCOMPtr<nsIChannel> channel;
if (aOriginPrincipal) {
// if there is an originPrincipal we should also have aBoundDocument
MOZ_ASSERT(aBoundDocument, "can not create a channel without aBoundDocument");
rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(channel),
aDocumentURI,
aBoundDocument,
aOriginPrincipal,
nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS |
nsILoadInfo::SEC_ALLOW_CHROME,
nsIContentPolicy::TYPE_XBL,
loadGroup);
}
else {
rv = NS_NewChannel(getter_AddRefs(channel),
aDocumentURI,
nsContentUtils::GetSystemPrincipal(),
nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS,
nsIContentPolicy::TYPE_XBL,
loadGroup);
}
NS_ENSURE_SUCCESS(rv, rv);
if (!aForceSyncLoad) {
// We can be asynchronous
nsXBLStreamListener* xblListener =
new nsXBLStreamListener(aBoundDocument, xblSink, doc);
// Add ourselves to the list of loading docs.
nsBindingManager *bindingManager;
if (aBoundDocument)
bindingManager = aBoundDocument->BindingManager();
else
bindingManager = nullptr;
if (bindingManager)
bindingManager->PutLoadingDocListener(aDocumentURI, xblListener);
// Add our request.
nsXBLBindingRequest* req = new nsXBLBindingRequest(aBindingURI,
aBoundElement);
xblListener->AddRequest(req);
// Now kick off the async read.
rv = channel->AsyncOpen2(xblListener);
if (NS_FAILED(rv)) {
// Well, we won't be getting a load. Make sure to clean up our stuff!
if (bindingManager) {
bindingManager->RemoveLoadingDocListener(aDocumentURI);
}
}
return NS_OK;
}
nsCOMPtr<nsIStreamListener> listener;
rv = doc->StartDocumentLoad("loadAsInteractiveData",
channel,
loadGroup,
nullptr,
getter_AddRefs(listener),
true,
xblSink);
NS_ENSURE_SUCCESS(rv, rv);
// Now do a blocking synchronous parse of the file.
nsCOMPtr<nsIInputStream> in;
rv = channel->Open2(getter_AddRefs(in));
NS_ENSURE_SUCCESS(rv, rv);
rv = nsSyncLoadService::PushSyncStreamToListener(in, listener, channel);
NS_ENSURE_SUCCESS(rv, rv);
doc.swap(*aResult);
return NS_OK;
}