michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=80: */ 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/DebugOnly.h" michael@0: michael@0: #include "nsXBLDocumentInfo.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsXBLPrototypeBinding.h" michael@0: #include "nsIScriptObjectPrincipal.h" michael@0: #include "nsIScriptContext.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMScriptObjectFactory.h" michael@0: #include "jsapi.h" michael@0: #include "jsfriendapi.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIConsoleService.h" michael@0: #include "nsIScriptError.h" michael@0: #include "nsIChromeRegistry.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsJSPrincipals.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsDOMJSUtils.h" michael@0: #include "mozilla/Services.h" michael@0: #include "xpcpublic.h" michael@0: #include "mozilla/scache/StartupCache.h" michael@0: #include "mozilla/scache/StartupCacheUtils.h" michael@0: #include "nsCCUncollectableMarker.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: #include "mozilla/dom/URL.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::scache; michael@0: using namespace mozilla::dom; michael@0: michael@0: static const char kXBLCachePrefix[] = "xblcache"; michael@0: michael@0: /* Implementation file */ michael@0: michael@0: static PLDHashOperator michael@0: TraverseProtos(const nsACString &aKey, nsXBLPrototypeBinding *aProto, void* aClosure) michael@0: { michael@0: nsCycleCollectionTraversalCallback *cb = michael@0: static_cast(aClosure); michael@0: aProto->Traverse(*cb); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: UnlinkProtoJSObjects(const nsACString &aKey, nsXBLPrototypeBinding *aProto, void* aClosure) michael@0: { michael@0: aProto->UnlinkJSObjects(); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: struct ProtoTracer michael@0: { michael@0: const TraceCallbacks &mCallbacks; michael@0: void *mClosure; michael@0: }; michael@0: michael@0: static PLDHashOperator michael@0: TraceProtos(const nsACString &aKey, nsXBLPrototypeBinding *aProto, void* aClosure) michael@0: { michael@0: ProtoTracer* closure = static_cast(aClosure); michael@0: aProto->Trace(closure->mCallbacks, closure->mClosure); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsXBLDocumentInfo) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXBLDocumentInfo) michael@0: if (tmp->mBindingTable) { michael@0: tmp->mBindingTable->EnumerateRead(UnlinkProtoJSObjects, nullptr); michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXBLDocumentInfo) michael@0: if (tmp->mDocument && michael@0: nsCCUncollectableMarker::InGeneration(cb, tmp->mDocument->GetMarkedCCGeneration())) { michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: return NS_SUCCESS_INTERRUPTED_TRAVERSE; michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) michael@0: if (tmp->mBindingTable) { michael@0: tmp->mBindingTable->EnumerateRead(TraverseProtos, &cb); michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXBLDocumentInfo) michael@0: if (tmp->mBindingTable) { michael@0: ProtoTracer closure = { aCallbacks, aClosure }; michael@0: tmp->mBindingTable->EnumerateRead(TraceProtos, &closure); michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: static void michael@0: UnmarkXBLJSObject(void* aP, const char* aName, void* aClosure) michael@0: { michael@0: JS::ExposeObjectToActiveJS(static_cast(aP)); michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: UnmarkProtos(const nsACString &aKey, nsXBLPrototypeBinding *aProto, void* aClosure) michael@0: { michael@0: aProto->Trace(TraceCallbackFunc(UnmarkXBLJSObject), nullptr); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: nsXBLDocumentInfo::MarkInCCGeneration(uint32_t aGeneration) michael@0: { michael@0: if (mDocument) { michael@0: mDocument->MarkUncollectableForCCGeneration(aGeneration); michael@0: } michael@0: // Unmark any JS we hold michael@0: if (mBindingTable) { michael@0: mBindingTable->EnumerateRead(UnmarkProtos, nullptr); michael@0: } michael@0: } michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXBLDocumentInfo) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXBLDocumentInfo) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXBLDocumentInfo) michael@0: michael@0: nsXBLDocumentInfo::nsXBLDocumentInfo(nsIDocument* aDocument) michael@0: : mDocument(aDocument), michael@0: mScriptAccess(true), michael@0: mIsChrome(false), michael@0: mFirstBinding(nullptr) michael@0: { michael@0: nsIURI* uri = aDocument->GetDocumentURI(); michael@0: if (IsChromeURI(uri)) { michael@0: // Cache whether or not this chrome XBL can execute scripts. michael@0: nsCOMPtr reg = michael@0: mozilla::services::GetXULChromeRegistryService(); michael@0: if (reg) { michael@0: bool allow = true; michael@0: reg->AllowScriptsForPackage(uri, &allow); michael@0: mScriptAccess = allow; michael@0: } michael@0: mIsChrome = true; michael@0: } else { michael@0: // If this binding isn't running with system principal, then it's running michael@0: // from a remote-XUL whitelisted domain. This is already a not-really- michael@0: // supported configuration (among other things, we don't use XBL scopes in michael@0: // that configuration for compatibility reasons). But we should still at michael@0: // least make an effort to prevent binding code from running if content michael@0: // script is disabled or if the source domain is blacklisted (since the michael@0: // source domain for remote XBL must always be the same as the source domain michael@0: // of the bound content). michael@0: // michael@0: // If we just ask the binding document if script is enabled, it will michael@0: // discover that it has no inner window, and return false. So instead, we michael@0: // short-circuit the normal compartment-managed script-disabling machinery, michael@0: // and query the policy for the URI directly. michael@0: bool allow; michael@0: nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); michael@0: nsresult rv = ssm->PolicyAllowsScript(uri, &allow); michael@0: mScriptAccess = NS_SUCCEEDED(rv) && allow; michael@0: } michael@0: } michael@0: michael@0: nsXBLDocumentInfo::~nsXBLDocumentInfo() michael@0: { michael@0: mozilla::DropJSObjects(this); michael@0: } michael@0: michael@0: nsXBLPrototypeBinding* michael@0: nsXBLDocumentInfo::GetPrototypeBinding(const nsACString& aRef) michael@0: { michael@0: if (!mBindingTable) michael@0: return nullptr; michael@0: michael@0: if (aRef.IsEmpty()) { michael@0: // Return our first binding michael@0: return mFirstBinding; michael@0: } michael@0: michael@0: return mBindingTable->Get(aRef); michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLDocumentInfo::SetPrototypeBinding(const nsACString& aRef, nsXBLPrototypeBinding* aBinding) michael@0: { michael@0: if (!mBindingTable) { michael@0: mBindingTable = new nsClassHashtable(); michael@0: mozilla::HoldJSObjects(this); michael@0: } michael@0: michael@0: NS_ENSURE_STATE(!mBindingTable->Get(aRef)); michael@0: mBindingTable->Put(aRef, aBinding); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsXBLDocumentInfo::RemovePrototypeBinding(const nsACString& aRef) michael@0: { michael@0: if (mBindingTable) { michael@0: nsAutoPtr bindingToRemove; michael@0: mBindingTable->RemoveAndForget(aRef, bindingToRemove); michael@0: michael@0: // We do not want to destroy the binding, so just forget it. michael@0: bindingToRemove.forget(); michael@0: } michael@0: } michael@0: michael@0: // Callback to enumerate over the bindings from this document and write them michael@0: // out to the cache. michael@0: static PLDHashOperator michael@0: WriteBinding(const nsACString &aKey, nsXBLPrototypeBinding *aProto, void* aClosure) michael@0: { michael@0: aProto->Write((nsIObjectOutputStream*)aClosure); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: nsXBLDocumentInfo::ReadPrototypeBindings(nsIURI* aURI, nsXBLDocumentInfo** aDocInfo) michael@0: { michael@0: *aDocInfo = nullptr; michael@0: michael@0: nsAutoCString spec(kXBLCachePrefix); michael@0: nsresult rv = PathifyURI(aURI, spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: StartupCache* startupCache = StartupCache::GetSingleton(); michael@0: NS_ENSURE_TRUE(startupCache, NS_ERROR_FAILURE); michael@0: michael@0: nsAutoArrayPtr buf; michael@0: uint32_t len; michael@0: rv = startupCache->GetBuffer(spec.get(), getter_Transfers(buf), &len); michael@0: // GetBuffer will fail if the binding is not in the cache. michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr stream; michael@0: rv = NewObjectInputStreamFromBuffer(buf, len, getter_AddRefs(stream)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: buf.forget(); michael@0: michael@0: // The file compatibility.ini stores the build id. This is checked in michael@0: // nsAppRunner.cpp and will delete the cache if a different build is michael@0: // present. However, we check that the version matches here to be safe. michael@0: uint32_t version; michael@0: rv = stream->Read32(&version); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (version != XBLBinding_Serialize_Version) { michael@0: // The version that exists is different than expected, likely created with a michael@0: // different build, so invalidate the cache. michael@0: startupCache->InvalidateCache(); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsCOMPtr principal; michael@0: nsContentUtils::GetSecurityManager()-> michael@0: GetSystemPrincipal(getter_AddRefs(principal)); michael@0: michael@0: nsCOMPtr domdoc; michael@0: rv = NS_NewXBLDocument(getter_AddRefs(domdoc), aURI, nullptr, principal); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr doc = do_QueryInterface(domdoc); michael@0: NS_ASSERTION(doc, "Must have a document!"); michael@0: nsRefPtr docInfo = new nsXBLDocumentInfo(doc); michael@0: michael@0: while (1) { michael@0: uint8_t flags; michael@0: nsresult rv = stream->Read8(&flags); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (flags == XBLBinding_Serialize_NoMoreBindings) michael@0: break; michael@0: michael@0: rv = nsXBLPrototypeBinding::ReadNewBinding(stream, docInfo, doc, flags); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: docInfo.swap(*aDocInfo); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLDocumentInfo::WritePrototypeBindings() michael@0: { michael@0: // Only write out bindings with the system principal michael@0: if (!nsContentUtils::IsSystemPrincipal(mDocument->NodePrincipal())) michael@0: return NS_OK; michael@0: michael@0: nsAutoCString spec(kXBLCachePrefix); michael@0: nsresult rv = PathifyURI(DocumentURI(), spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: StartupCache* startupCache = StartupCache::GetSingleton(); michael@0: NS_ENSURE_TRUE(startupCache, rv); michael@0: michael@0: nsCOMPtr stream; michael@0: nsCOMPtr storageStream; michael@0: rv = NewObjectOutputWrappedStorageStream(getter_AddRefs(stream), michael@0: getter_AddRefs(storageStream), michael@0: true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stream->Write32(XBLBinding_Serialize_Version); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (mBindingTable) { michael@0: mBindingTable->EnumerateRead(WriteBinding, stream); michael@0: } michael@0: michael@0: // write a end marker at the end michael@0: rv = stream->Write8(XBLBinding_Serialize_NoMoreBindings); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: stream->Close(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t len; michael@0: nsAutoArrayPtr buf; michael@0: rv = NewBufferFromStorageStream(storageStream, getter_Transfers(buf), &len); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return startupCache->PutBuffer(spec.get(), buf, len); michael@0: } michael@0: michael@0: void michael@0: nsXBLDocumentInfo::SetFirstPrototypeBinding(nsXBLPrototypeBinding* aBinding) michael@0: { michael@0: mFirstBinding = aBinding; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: FlushScopedSkinSheets(const nsACString &aKey, nsXBLPrototypeBinding *aProto, void* aClosure) michael@0: { michael@0: aProto->FlushSkinSheets(); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: nsXBLDocumentInfo::FlushSkinStylesheets() michael@0: { michael@0: if (mBindingTable) { michael@0: mBindingTable->EnumerateRead(FlushScopedSkinSheets, nullptr); michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void michael@0: AssertInCompilationScope() michael@0: { michael@0: AutoJSContext cx; michael@0: // Note - Inverting the order of these operands is a rooting hazard. michael@0: MOZ_ASSERT(xpc::GetCompilationScope() == JS::CurrentGlobalOrNull(cx)); michael@0: } michael@0: #endif