gecko/dom/xbl/nsXBLDocumentInfo.cpp

595 lines
17 KiB
C++
Raw Normal View History

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=80: */
2012-05-21 04:12:37 -07:00
/* 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/DebugOnly.h"
#include "nsXBLDocumentInfo.h"
#include "nsHashtable.h"
#include "nsIDocument.h"
#include "nsXBLPrototypeBinding.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIScriptGlobalObject.h"
#include "nsIScriptContext.h"
#include "nsIDOMDocument.h"
#include "nsIDOMScriptObjectFactory.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "nsIURI.h"
#include "nsIConsoleService.h"
#include "nsIScriptError.h"
#include "nsIChromeRegistry.h"
#include "nsIPrincipal.h"
#include "nsJSPrincipals.h"
#include "nsIScriptSecurityManager.h"
#include "nsContentUtils.h"
#include "nsCxPusher.h"
#include "nsDOMJSUtils.h"
#include "mozilla/Services.h"
#include "xpcpublic.h"
#include "mozilla/scache/StartupCache.h"
#include "mozilla/scache/StartupCacheUtils.h"
#include "nsCCUncollectableMarker.h"
Bug 742217. Reduce the use of nested namespaces in our binding code. r=peterv,bent In the new setup, all per-interface DOM binding files are exported into mozilla/dom. General files not specific to an interface are also exported into mozilla/dom. In terms of namespaces, most things now live in mozilla::dom. Each interface Foo that has generated code has a mozilla::dom::FooBinding namespace for said generated code (and possibly a mozilla::bindings::FooBinding_workers if there's separate codegen for workers). IDL enums are a bit weird: since the name of the enum and the names of its entries all end up in the same namespace, we still generate a C++ namespace with the name of the IDL enum type with "Values" appended to it, with a ::valuelist inside for the actual C++ enum. We then typedef EnumFooValues::valuelist to EnumFoo. That makes it a bit more difficult to refer to the values, but means that values from different enums don't collide with each other. The enums with the proto and constructor IDs in them now live under the mozilla::dom::prototypes and mozilla::dom::constructors namespaces respectively. Again, this lets us deal sanely with the whole "enum value names are flattened into the namespace the enum is in" deal. The main benefit of this setup (and the reason "Binding" got appended to the per-interface namespaces) is that this way "using mozilla::dom" should Just Work for consumers and still allow C++ code to sanely use the IDL interface names for concrete classes, which is fairly desirable. --HG-- rename : dom/bindings/Utils.cpp => dom/bindings/BindingUtils.cpp rename : dom/bindings/Utils.h => dom/bindings/BindingUtils.h
2012-05-02 21:35:38 -07:00
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/URL.h"
using namespace mozilla;
using namespace mozilla::scache;
using namespace mozilla::dom;
static const char kXBLCachePrefix[] = "xblcache";
class nsXBLDocGlobalObject : public nsISupports
{
public:
nsXBLDocGlobalObject(nsXBLDocumentInfo *aGlobalObjectOwner);
// nsISupports interface
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsXBLDocGlobalObject)
JSObject *GetCompilationGlobal();
void UnmarkCompilationGlobal();
void Destroy();
nsIPrincipal* GetPrincipal();
void ClearGlobalObjectOwner();
static const JSClass gSharedGlobalClass;
protected:
virtual ~nsXBLDocGlobalObject();
JS::Heap<JSObject*> mJSObject;
nsXBLDocumentInfo* mGlobalObjectOwner; // weak reference
bool mDestroyed; // Probably not necessary, but let's be safe.
};
static void
nsXBLDocGlobalObject_finalize(JSFreeOp *fop, JSObject *obj)
{
nsISupports *nativeThis = (nsISupports*)JS_GetPrivate(obj);
nsXBLDocGlobalObject* dgo = static_cast<nsXBLDocGlobalObject*>(nativeThis);
if (dgo)
dgo->Destroy();
// The addref was part of JSObject construction. Note that this effectively
// just calls release later on.
nsContentUtils::DeferredFinalize(nativeThis);
}
static bool
nsXBLDocGlobalObject_resolve(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id)
{
bool did_resolve = false;
return JS_ResolveStandardClass(cx, obj, id, &did_resolve);
}
const JSClass nsXBLDocGlobalObject::gSharedGlobalClass = {
"nsXBLPrototypeScript compilation scope",
JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS |
JSCLASS_IMPLEMENTS_BARRIERS | JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0),
JS_PropertyStub, JS_DeletePropertyStub,
JS_PropertyStub, JS_StrictPropertyStub,
JS_EnumerateStub, nsXBLDocGlobalObject_resolve,
JS_ConvertStub, nsXBLDocGlobalObject_finalize,
nullptr, nullptr, nullptr, nullptr
};
//----------------------------------------------------------------------
//
// nsXBLDocGlobalObject
//
nsXBLDocGlobalObject::nsXBLDocGlobalObject(nsXBLDocumentInfo *aGlobalObjectOwner)
: mJSObject(nullptr)
, mGlobalObjectOwner(aGlobalObjectOwner) // weak reference
, mDestroyed(false)
{
}
nsXBLDocGlobalObject::~nsXBLDocGlobalObject()
{
MOZ_ASSERT(!mJSObject);
}
NS_IMPL_CYCLE_COLLECTION_CLASS(nsXBLDocGlobalObject)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXBLDocGlobalObject)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXBLDocGlobalObject)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJSObject)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXBLDocGlobalObject)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXBLDocGlobalObject)
tmp->Destroy();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXBLDocGlobalObject)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXBLDocGlobalObject)
void
nsXBLDocGlobalObject::ClearGlobalObjectOwner()
{
mGlobalObjectOwner = nullptr;
}
void
nsXBLDocGlobalObject::UnmarkCompilationGlobal()
{
if (mJSObject) {
JS::ExposeObjectToActiveJS(mJSObject);
}
}
JSObject *
nsXBLDocGlobalObject::GetCompilationGlobal()
{
// The prototype document has its own special secret script object
// that can be used to compile scripts and event handlers.
if (mJSObject || mDestroyed) {
// We've been initialized before - what we have is what you get.
return mJSObject;
}
AutoSafeJSContext cx;
JS::CompartmentOptions options;
options.setZone(JS::SystemZone)
.setInvisibleToDebugger(true);
mJSObject = JS_NewGlobalObject(cx, &gSharedGlobalClass,
nsJSPrincipals::get(GetPrincipal()),
JS::DontFireOnNewGlobalHook,
options);
if (!mJSObject)
return nullptr;
mozilla::HoldJSObjects(this);
// Set the location information for the new global, so that tools like
// about:memory may use that information
nsIURI *ownerURI = mGlobalObjectOwner->DocumentURI();
xpc::SetLocationForGlobal(mJSObject, ownerURI);
// Add an owning reference from JS back to us. This'll be
// released when the JSObject is finalized.
::JS_SetPrivate(mJSObject, this);
NS_ADDREF(this);
return mJSObject;
}
void
nsXBLDocGlobalObject::Destroy()
{
// Maintain indempotence.
mDestroyed = true;
if (!mJSObject)
return;
mJSObject = nullptr;
mozilla::DropJSObjects(this);
}
nsIPrincipal*
nsXBLDocGlobalObject::GetPrincipal()
{
if (!mGlobalObjectOwner) {
// XXXbz this should really save the principal when
// ClearGlobalObjectOwner() happens.
return nullptr;
}
nsRefPtr<nsXBLDocumentInfo> docInfo =
static_cast<nsXBLDocumentInfo*>(mGlobalObjectOwner);
nsCOMPtr<nsIDocument> document = docInfo->GetDocument();
if (!document)
return nullptr;
return document->NodePrincipal();
}
/* Implementation file */
static bool
TraverseProtos(nsHashKey *aKey, void *aData, void* aClosure)
{
nsCycleCollectionTraversalCallback *cb =
static_cast<nsCycleCollectionTraversalCallback*>(aClosure);
nsXBLPrototypeBinding *proto = static_cast<nsXBLPrototypeBinding*>(aData);
proto->Traverse(*cb);
return kHashEnumerateNext;
}
static bool
UnlinkProtoJSObjects(nsHashKey *aKey, void *aData, void* aClosure)
{
nsXBLPrototypeBinding *proto = static_cast<nsXBLPrototypeBinding*>(aData);
proto->UnlinkJSObjects();
return kHashEnumerateNext;
}
struct ProtoTracer
{
const TraceCallbacks &mCallbacks;
void *mClosure;
};
static bool
TraceProtos(nsHashKey *aKey, void *aData, void* aClosure)
{
ProtoTracer* closure = static_cast<ProtoTracer*>(aClosure);
nsXBLPrototypeBinding *proto = static_cast<nsXBLPrototypeBinding*>(aData);
proto->Trace(closure->mCallbacks, closure->mClosure);
return kHashEnumerateNext;
}
NS_IMPL_CYCLE_COLLECTION_CLASS(nsXBLDocumentInfo)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXBLDocumentInfo)
if (tmp->mBindingTable) {
tmp->mBindingTable->Enumerate(UnlinkProtoJSObjects, nullptr);
}
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobalObject)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXBLDocumentInfo)
if (tmp->mDocument &&
nsCCUncollectableMarker::InGeneration(cb, tmp->mDocument->GetMarkedCCGeneration())) {
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
return NS_SUCCESS_INTERRUPTED_TRAVERSE;
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
if (tmp->mBindingTable) {
tmp->mBindingTable->Enumerate(TraverseProtos, &cb);
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobalObject)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXBLDocumentInfo)
if (tmp->mBindingTable) {
ProtoTracer closure = { aCallbacks, aClosure };
tmp->mBindingTable->Enumerate(TraceProtos, &closure);
}
NS_IMPL_CYCLE_COLLECTION_TRACE_END
static void
UnmarkXBLJSObject(void* aP, const char* aName, void* aClosure)
{
JS::ExposeObjectToActiveJS(static_cast<JSObject*>(aP));
}
static bool
UnmarkProtos(nsHashKey* aKey, void* aData, void* aClosure)
{
nsXBLPrototypeBinding* proto = static_cast<nsXBLPrototypeBinding*>(aData);
proto->Trace(TraceCallbackFunc(UnmarkXBLJSObject), nullptr);
return kHashEnumerateNext;
}
void
nsXBLDocumentInfo::MarkInCCGeneration(uint32_t aGeneration)
{
if (mDocument) {
mDocument->MarkUncollectableForCCGeneration(aGeneration);
}
// Unmark any JS we hold
if (mBindingTable) {
mBindingTable->Enumerate(UnmarkProtos, nullptr);
}
if (mGlobalObject) {
mGlobalObject->UnmarkCompilationGlobal();
}
}
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXBLDocumentInfo)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXBLDocumentInfo)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXBLDocumentInfo)
nsXBLDocumentInfo::nsXBLDocumentInfo(nsIDocument* aDocument)
: mDocument(aDocument),
mScriptAccess(true),
mIsChrome(false),
mBindingTable(nullptr),
mFirstBinding(nullptr)
{
nsIURI* uri = aDocument->GetDocumentURI();
if (IsChromeURI(uri)) {
// Cache whether or not this chrome XBL can execute scripts.
nsCOMPtr<nsIXULChromeRegistry> reg =
mozilla::services::GetXULChromeRegistryService();
if (reg) {
bool allow = true;
reg->AllowScriptsForPackage(uri, &allow);
mScriptAccess = allow;
}
mIsChrome = true;
} else {
// If this binding isn't running with system principal, then it's running
// from a remote-XUL whitelisted domain. This is already a not-really-
// supported configuration (among other things, we don't use XBL scopes in
// that configuration for compatibility reasons). But we should still at
// least make an effort to prevent binding code from running if content
// script is disabled or if the source domain is blacklisted (since the
// source domain for remote XBL must always be the same as the source domain
// of the bound content).
//
// If we just ask the binding document if script is enabled, it will
// discover that it has no inner window, and return false. So instead, we
// short-circuit the normal compartment-managed script-disabling machinery,
// and query the policy for the URI directly.
bool allow;
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
nsresult rv = ssm->PolicyAllowsScript(uri, &allow);
mScriptAccess = NS_SUCCEEDED(rv) && allow;
}
}
nsXBLDocumentInfo::~nsXBLDocumentInfo()
{
/* destructor code */
if (mGlobalObject) {
mGlobalObject->ClearGlobalObjectOwner(); // just in case
}
if (mBindingTable) {
delete mBindingTable;
mBindingTable = nullptr;
mozilla::DropJSObjects(this);
}
}
nsXBLPrototypeBinding*
nsXBLDocumentInfo::GetPrototypeBinding(const nsACString& aRef)
{
if (!mBindingTable)
return nullptr;
if (aRef.IsEmpty()) {
// Return our first binding
return mFirstBinding;
}
const nsPromiseFlatCString& flat = PromiseFlatCString(aRef);
nsCStringKey key(flat.get());
return static_cast<nsXBLPrototypeBinding*>(mBindingTable->Get(&key));
}
static bool
DeletePrototypeBinding(nsHashKey* aKey, void* aData, void* aClosure)
{
nsXBLPrototypeBinding* binding = static_cast<nsXBLPrototypeBinding*>(aData);
delete binding;
return true;
}
nsresult
nsXBLDocumentInfo::SetPrototypeBinding(const nsACString& aRef, nsXBLPrototypeBinding* aBinding)
{
if (!mBindingTable) {
mBindingTable = new nsObjectHashtable(nullptr, nullptr, DeletePrototypeBinding, nullptr);
mozilla::HoldJSObjects(this);
}
const nsPromiseFlatCString& flat = PromiseFlatCString(aRef);
nsCStringKey key(flat.get());
NS_ENSURE_STATE(!mBindingTable->Get(&key));
mBindingTable->Put(&key, aBinding);
return NS_OK;
}
void
nsXBLDocumentInfo::RemovePrototypeBinding(const nsACString& aRef)
{
if (mBindingTable) {
// Use a flat string to avoid making a copy.
const nsPromiseFlatCString& flat = PromiseFlatCString(aRef);
nsCStringKey key(flat);
mBindingTable->Remove(&key);
}
}
// Callback to enumerate over the bindings from this document and write them
// out to the cache.
bool
WriteBinding(nsHashKey *aKey, void *aData, void* aClosure)
{
nsXBLPrototypeBinding* binding = static_cast<nsXBLPrototypeBinding *>(aData);
binding->Write((nsIObjectOutputStream*)aClosure);
return kHashEnumerateNext;
}
// static
nsresult
nsXBLDocumentInfo::ReadPrototypeBindings(nsIURI* aURI, nsXBLDocumentInfo** aDocInfo)
{
*aDocInfo = nullptr;
nsAutoCString spec(kXBLCachePrefix);
nsresult rv = PathifyURI(aURI, spec);
NS_ENSURE_SUCCESS(rv, rv);
StartupCache* startupCache = StartupCache::GetSingleton();
NS_ENSURE_TRUE(startupCache, NS_ERROR_FAILURE);
nsAutoArrayPtr<char> buf;
uint32_t len;
rv = startupCache->GetBuffer(spec.get(), getter_Transfers(buf), &len);
// GetBuffer will fail if the binding is not in the cache.
if (NS_FAILED(rv))
return rv;
nsCOMPtr<nsIObjectInputStream> stream;
rv = NewObjectInputStreamFromBuffer(buf, len, getter_AddRefs(stream));
NS_ENSURE_SUCCESS(rv, rv);
buf.forget();
// The file compatibility.ini stores the build id. This is checked in
// nsAppRunner.cpp and will delete the cache if a different build is
// present. However, we check that the version matches here to be safe.
uint32_t version;
rv = stream->Read32(&version);
NS_ENSURE_SUCCESS(rv, rv);
if (version != XBLBinding_Serialize_Version) {
// The version that exists is different than expected, likely created with a
// different build, so invalidate the cache.
startupCache->InvalidateCache();
return NS_ERROR_NOT_AVAILABLE;
}
nsCOMPtr<nsIPrincipal> principal;
nsContentUtils::GetSecurityManager()->
GetSystemPrincipal(getter_AddRefs(principal));
nsCOMPtr<nsIDOMDocument> domdoc;
rv = NS_NewXBLDocument(getter_AddRefs(domdoc), aURI, nullptr, principal);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domdoc);
NS_ASSERTION(doc, "Must have a document!");
nsRefPtr<nsXBLDocumentInfo> docInfo = new nsXBLDocumentInfo(doc);
while (1) {
uint8_t flags;
nsresult rv = stream->Read8(&flags);
NS_ENSURE_SUCCESS(rv, rv);
if (flags == XBLBinding_Serialize_NoMoreBindings)
break;
nsXBLPrototypeBinding* binding = new nsXBLPrototypeBinding();
rv = binding->Read(stream, docInfo, doc, flags);
if (NS_FAILED(rv)) {
delete binding;
return rv;
}
}
docInfo.swap(*aDocInfo);
return NS_OK;
}
nsresult
nsXBLDocumentInfo::WritePrototypeBindings()
{
// Only write out bindings with the system principal
if (!nsContentUtils::IsSystemPrincipal(mDocument->NodePrincipal()))
return NS_OK;
nsAutoCString spec(kXBLCachePrefix);
nsresult rv = PathifyURI(DocumentURI(), spec);
NS_ENSURE_SUCCESS(rv, rv);
StartupCache* startupCache = StartupCache::GetSingleton();
NS_ENSURE_TRUE(startupCache, rv);
nsCOMPtr<nsIObjectOutputStream> stream;
nsCOMPtr<nsIStorageStream> storageStream;
rv = NewObjectOutputWrappedStorageStream(getter_AddRefs(stream),
getter_AddRefs(storageStream),
true);
NS_ENSURE_SUCCESS(rv, rv);
rv = stream->Write32(XBLBinding_Serialize_Version);
NS_ENSURE_SUCCESS(rv, rv);
if (mBindingTable)
mBindingTable->Enumerate(WriteBinding, stream);
// write a end marker at the end
rv = stream->Write8(XBLBinding_Serialize_NoMoreBindings);
NS_ENSURE_SUCCESS(rv, rv);
stream->Close();
NS_ENSURE_SUCCESS(rv, rv);
uint32_t len;
nsAutoArrayPtr<char> buf;
rv = NewBufferFromStorageStream(storageStream, getter_Transfers(buf), &len);
NS_ENSURE_SUCCESS(rv, rv);
return startupCache->PutBuffer(spec.get(), buf, len);
}
void
nsXBLDocumentInfo::SetFirstPrototypeBinding(nsXBLPrototypeBinding* aBinding)
{
mFirstBinding = aBinding;
}
bool FlushScopedSkinSheets(nsHashKey* aKey, void* aData, void* aClosure)
{
nsXBLPrototypeBinding* proto = (nsXBLPrototypeBinding*)aData;
proto->FlushSkinSheets();
return true;
}
void
nsXBLDocumentInfo::FlushSkinStylesheets()
{
if (mBindingTable)
mBindingTable->Enumerate(FlushScopedSkinSheets);
}
JSObject*
nsXBLDocumentInfo::GetCompilationGlobal()
{
EnsureGlobalObject();
return mGlobalObject->GetCompilationGlobal();
}
void
nsXBLDocumentInfo::EnsureGlobalObject()
{
if (!mGlobalObject) {
mGlobalObject = new nsXBLDocGlobalObject(this);
}
}
#ifdef DEBUG
void
AssertInCompilationScope()
{
AutoJSContext cx;
MOZ_ASSERT(JS_GetClass(JS::CurrentGlobalOrNull(cx)) ==
&nsXBLDocGlobalObject::gSharedGlobalClass);
}
#endif