gecko-dev/js/xpconnect/src/XPCWrappedNativeScope.cpp
2019-04-04 02:07:53 +00:00

546 lines
19 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/. */
/* Class used to manage the wrapped native objects within a JS scope. */
#include "xpcprivate.h"
#include "XPCWrapper.h"
#include "nsContentUtils.h"
#include "nsCycleCollectionNoteRootCallback.h"
#include "ExpandedPrincipal.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Preferences.h"
#include "mozilla/Unused.h"
#include "nsIXULRuntime.h"
#include "mozJSComponentLoader.h"
#include "mozilla/dom/BindingUtils.h"
using namespace mozilla;
using namespace xpc;
using namespace JS;
/***************************************************************************/
static XPCWrappedNativeScopeList& AllScopes() {
return XPCJSRuntime::Get()->GetWrappedNativeScopes();
}
static bool RemoteXULForbidsXBLScopeForPrincipal(nsIPrincipal* aPrincipal) {
// AllowXULXBLForPrincipal will return true for system principal, but we
// don't want that here.
MOZ_ASSERT(nsContentUtils::IsInitialized());
if (aPrincipal->IsSystemPrincipal()) {
return false;
}
// If this domain isn't whitelisted, we're done.
if (!nsContentUtils::AllowXULXBLForPrincipal(aPrincipal)) {
return false;
}
// Check the pref to determine how we should behave.
return !Preferences::GetBool("dom.use_xbl_scopes_for_remote_xul", false);
}
static bool RemoteXULForbidsXBLScope(HandleObject aFirstGlobal) {
MOZ_ASSERT(aFirstGlobal);
// Certain singleton sandoxes are created very early in startup - too early
// to call into AllowXULXBLForPrincipal. We never create XBL scopes for
// sandboxes anway, and certainly not for these singleton scopes. So we just
// short-circuit here.
if (IsSandbox(aFirstGlobal)) {
return false;
}
nsIPrincipal* principal = xpc::GetObjectPrincipal(aFirstGlobal);
return RemoteXULForbidsXBLScopeForPrincipal(principal);
}
XPCWrappedNativeScope::XPCWrappedNativeScope(JS::Compartment* aCompartment,
JS::HandleObject aFirstGlobal)
: mWrappedNativeMap(Native2WrappedNativeMap::newMap(XPC_NATIVE_MAP_LENGTH)),
mWrappedNativeProtoMap(
ClassInfo2WrappedNativeProtoMap::newMap(XPC_NATIVE_PROTO_MAP_LENGTH)),
mComponents(nullptr),
mCompartment(aCompartment) {
#ifdef DEBUG
for (XPCWrappedNativeScope* cur : AllScopes()) {
MOZ_ASSERT(aCompartment != cur->Compartment(), "dup object");
}
#endif
AllScopes().insertBack(this);
MOZ_COUNT_CTOR(XPCWrappedNativeScope);
// Determine whether we would allow an XBL scope in this situation.
// In addition to being pref-controlled, we also disable XBL scopes for
// remote XUL domains, _except_ if we have an additional pref override set.
//
// Note that we can't quite remove this yet, even though we never actually
// use XBL scopes, because some code (including the security manager) uses
// this boolean to make decisions that we rely on in our test infrastructure.
mAllowContentXBLScope = !RemoteXULForbidsXBLScope(aFirstGlobal);
}
bool XPCWrappedNativeScope::GetComponentsJSObject(JSContext* cx,
JS::MutableHandleObject obj) {
if (!mComponents) {
bool system = AccessCheck::isChrome(mCompartment);
mComponents =
system ? new nsXPCComponents(this) : new nsXPCComponentsBase(this);
}
RootedValue val(cx);
xpcObjectHelper helper(mComponents);
bool ok = XPCConvert::NativeInterface2JSObject(cx, &val, helper, nullptr,
false, nullptr);
if (NS_WARN_IF(!ok)) {
return false;
}
if (NS_WARN_IF(!val.isObject())) {
return false;
}
// The call to wrap() here is necessary even though the object is same-
// compartment, because it applies our security wrapper.
obj.set(&val.toObject());
if (NS_WARN_IF(!JS_WrapObject(cx, obj))) {
return false;
}
return true;
}
void XPCWrappedNativeScope::ForcePrivilegedComponents() {
nsCOMPtr<nsIXPCComponents> c = do_QueryInterface(mComponents);
if (!c) {
mComponents = new nsXPCComponents(this);
}
}
static bool DefineSubcomponentProperty(JSContext* aCx, HandleObject aGlobal,
nsISupports* aSubcomponent,
const nsID* aIID,
unsigned int aStringIndex) {
RootedValue subcompVal(aCx);
xpcObjectHelper helper(aSubcomponent);
if (!XPCConvert::NativeInterface2JSObject(aCx, &subcompVal, helper, aIID,
false, nullptr))
return false;
if (NS_WARN_IF(!subcompVal.isObject())) {
return false;
}
RootedId id(aCx, XPCJSContext::Get()->GetStringID(aStringIndex));
return JS_DefinePropertyById(aCx, aGlobal, id, subcompVal, 0);
}
bool XPCWrappedNativeScope::AttachComponentsObject(JSContext* aCx) {
RootedObject components(aCx);
if (!GetComponentsJSObject(aCx, &components)) {
return false;
}
RootedObject global(aCx, CurrentGlobalOrNull(aCx));
// The global Components property is non-configurable if it's a full
// nsXPCComponents object. That way, if it's an nsXPCComponentsBase,
// enableUniversalXPConnect can upgrade it later.
unsigned attrs = JSPROP_READONLY | JSPROP_RESOLVING;
nsCOMPtr<nsIXPCComponents> c = do_QueryInterface(mComponents);
if (c) {
attrs |= JSPROP_PERMANENT;
}
RootedId id(aCx,
XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS));
if (!JS_DefinePropertyById(aCx, global, id, components, attrs)) {
return false;
}
// _iid can be nullptr if the object implements classinfo.
#define DEFINE_SUBCOMPONENT_PROPERTY(_comp, _type, _iid, _id) \
nsCOMPtr<nsIXPCComponents_##_type> obj##_type; \
if (NS_FAILED(_comp->Get##_type(getter_AddRefs(obj##_type)))) return false; \
if (!DefineSubcomponentProperty(aCx, global, obj##_type, _iid, \
XPCJSContext::IDX_##_id)) \
return false;
DEFINE_SUBCOMPONENT_PROPERTY(mComponents, Interfaces, nullptr, CI)
DEFINE_SUBCOMPONENT_PROPERTY(mComponents, Results, nullptr, CR)
if (!c) {
return true;
}
DEFINE_SUBCOMPONENT_PROPERTY(c, Classes, nullptr, CC)
DEFINE_SUBCOMPONENT_PROPERTY(c, Utils, &NS_GET_IID(nsIXPCComponents_Utils),
CU)
#undef DEFINE_SUBCOMPONENT_PROPERTY
return true;
}
JSObject* XPCWrappedNativeScope::EnsureContentXBLScope(JSContext* cx) {
JS::RootedObject global(cx, CurrentGlobalOrNull(cx));
MOZ_ASSERT(js::IsObjectInContextCompartment(global, cx));
MOZ_ASSERT(!IsContentXBLScope());
MOZ_ASSERT(strcmp(js::GetObjectClass(global)->name,
"nsXBLPrototypeScript compilation scope"));
// We can probably remove EnsureContentXBLScope and clean up all its callers,
// but a bunch (all?) of those callers will just go away when we remove XBL
// support, so it's simpler to just leave it here as a no-op.
return global;
}
bool XPCWrappedNativeScope::XBLScopeStateMatches(nsIPrincipal* aPrincipal) {
return mAllowContentXBLScope ==
!RemoteXULForbidsXBLScopeForPrincipal(aPrincipal);
}
bool XPCWrappedNativeScope::AllowContentXBLScope(Realm* aRealm) {
// We only disallow XBL scopes in remote XUL situations.
MOZ_ASSERT_IF(!mAllowContentXBLScope, nsContentUtils::AllowXULXBLForPrincipal(
xpc::GetRealmPrincipal(aRealm)));
return mAllowContentXBLScope;
}
namespace xpc {
JSObject* GetXBLScope(JSContext* cx, JSObject* contentScopeArg) {
JS::RootedObject contentScope(cx, contentScopeArg);
JSAutoRealm ar(cx, contentScope);
XPCWrappedNativeScope* nativeScope = ObjectScope(contentScope);
RootedObject scope(cx, nativeScope->EnsureContentXBLScope(cx));
NS_ENSURE_TRUE(scope, nullptr); // See bug 858642.
scope = js::UncheckedUnwrap(scope);
JS::ExposeObjectToActiveJS(scope);
return scope;
}
JSObject* GetUAWidgetScope(JSContext* cx, JSObject* contentScopeArg) {
JS::RootedObject contentScope(cx, contentScopeArg);
JSAutoRealm ar(cx, contentScope);
nsIPrincipal* principal = GetObjectPrincipal(contentScope);
if (nsContentUtils::IsSystemPrincipal(principal)) {
return JS::GetNonCCWObjectGlobal(contentScope);
}
return GetUAWidgetScope(cx, principal);
}
JSObject* GetUAWidgetScope(JSContext* cx, nsIPrincipal* principal) {
RootedObject scope(cx, XPCJSRuntime::Get()->GetUAWidgetScope(cx, principal));
NS_ENSURE_TRUE(scope, nullptr); // See bug 858642.
scope = js::UncheckedUnwrap(scope);
JS::ExposeObjectToActiveJS(scope);
return scope;
}
bool AllowContentXBLScope(JS::Realm* realm) {
JS::Compartment* comp = GetCompartmentForRealm(realm);
XPCWrappedNativeScope* scope = CompartmentPrivate::Get(comp)->GetScope();
MOZ_ASSERT(scope);
return scope->AllowContentXBLScope(realm);
}
} /* namespace xpc */
XPCWrappedNativeScope::~XPCWrappedNativeScope() {
MOZ_COUNT_DTOR(XPCWrappedNativeScope);
// We can do additional cleanup assertions here...
MOZ_ASSERT(0 == mWrappedNativeMap->Count(), "scope has non-empty map");
delete mWrappedNativeMap;
MOZ_ASSERT(0 == mWrappedNativeProtoMap->Count(), "scope has non-empty map");
delete mWrappedNativeProtoMap;
// This should not be necessary, since the Components object should die
// with the scope but just in case.
if (mComponents) {
mComponents->mScope = nullptr;
}
// XXX we should assert that we are dead or that xpconnect has shutdown
// XXX might not want to do this at xpconnect shutdown time???
mComponents = nullptr;
MOZ_RELEASE_ASSERT(!mXrayExpandos.initialized());
mCompartment = nullptr;
}
// static
void XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(XPCJSRuntime* xpcrt,
JSTracer* trc) {
// Do JS::TraceEdge for all wrapped natives with external references, as
// well as any DOM expando objects.
//
// Note: the GC can call this from a JS helper thread. We don't use
// AllScopes() because that asserts we're on the main thread.
for (XPCWrappedNativeScope* cur : xpcrt->GetWrappedNativeScopes()) {
for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
XPCWrappedNative* wrapper = entry->value;
if (wrapper->HasExternalReference() && !wrapper->IsWrapperExpired()) {
wrapper->TraceSelf(trc);
}
}
}
}
// static
void XPCWrappedNativeScope::SuspectAllWrappers(
nsCycleCollectionNoteRootCallback& cb) {
for (XPCWrappedNativeScope* cur : AllScopes()) {
for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
static_cast<Native2WrappedNativeMap::Entry*>(i.Get())->value->Suspect(cb);
}
}
}
void XPCWrappedNativeScope::UpdateWeakPointersAfterGC() {
// Sweep waivers.
if (mWaiverWrapperMap) {
mWaiverWrapperMap->Sweep();
}
if (!js::IsCompartmentZoneSweepingOrCompacting(mCompartment)) {
return;
}
if (!js::CompartmentHasLiveGlobal(mCompartment)) {
GetWrappedNativeMap()->Clear();
mWrappedNativeProtoMap->Clear();
// The fields below are traced only if there's a live global in the
// compartment, see TraceXPCGlobal. The compartment has no live globals so
// clear these pointers here.
if (mXrayExpandos.initialized()) {
mXrayExpandos.destroy();
}
mIDProto = nullptr;
mIIDProto = nullptr;
mCIDProto = nullptr;
return;
}
// Sweep mWrappedNativeMap for dying flat JS objects. Moving has already
// been handled by XPCWrappedNative::FlatJSObjectMoved.
for (auto iter = GetWrappedNativeMap()->Iter(); !iter.Done(); iter.Next()) {
auto entry = static_cast<Native2WrappedNativeMap::Entry*>(iter.Get());
XPCWrappedNative* wrapper = entry->value;
JSObject* obj = wrapper->GetFlatJSObjectPreserveColor();
JS_UpdateWeakPointerAfterGCUnbarriered(&obj);
MOZ_ASSERT(!obj || obj == wrapper->GetFlatJSObjectPreserveColor());
MOZ_ASSERT_IF(obj, js::GetObjectCompartment(obj) == mCompartment);
if (!obj) {
iter.Remove();
}
}
// Sweep mWrappedNativeProtoMap for dying prototype JSObjects. Moving has
// already been handled by XPCWrappedNativeProto::JSProtoObjectMoved.
for (auto i = mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) {
auto entry = static_cast<ClassInfo2WrappedNativeProtoMap::Entry*>(i.Get());
JSObject* obj = entry->value->GetJSProtoObjectPreserveColor();
JS_UpdateWeakPointerAfterGCUnbarriered(&obj);
MOZ_ASSERT_IF(obj, js::GetObjectCompartment(obj) == mCompartment);
MOZ_ASSERT(!obj || obj == entry->value->GetJSProtoObjectPreserveColor());
if (!obj) {
i.Remove();
}
}
}
// static
void XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs() {
for (XPCWrappedNativeScope* cur : AllScopes()) {
for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
entry->value->SweepTearOffs();
}
}
}
// static
void XPCWrappedNativeScope::SystemIsBeingShutDown() {
// We're forcibly killing scopes, rather than allowing them to go away
// when they're ready. As such, we need to do some cleanup before they
// can safely be destroyed.
for (XPCWrappedNativeScope* cur : AllScopes()) {
// Give the Components object a chance to try to clean up.
if (cur->mComponents) {
cur->mComponents->SystemIsBeingShutDown();
}
// Null out these pointers to prevent ~ObjectPtr assertion failures if we
// leaked things at shutdown.
cur->mIDProto = nullptr;
cur->mIIDProto = nullptr;
cur->mCIDProto = nullptr;
// Similarly, destroy mXrayExpandos to prevent assertion failures.
if (cur->mXrayExpandos.initialized()) {
cur->mXrayExpandos.destroy();
}
// Walk the protos first. Wrapper shutdown can leave dangling
// proto pointers in the proto map.
for (auto i = cur->mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) {
auto entry =
static_cast<ClassInfo2WrappedNativeProtoMap::Entry*>(i.Get());
entry->value->SystemIsBeingShutDown();
i.Remove();
}
for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
XPCWrappedNative* wrapper = entry->value;
if (wrapper->IsValid()) {
wrapper->SystemIsBeingShutDown();
}
i.Remove();
}
CompartmentPrivate* priv = CompartmentPrivate::Get(cur->Compartment());
priv->SystemIsBeingShutDown();
}
}
/***************************************************************************/
JSObject* XPCWrappedNativeScope::GetExpandoChain(HandleObject target) {
MOZ_ASSERT(ObjectScope(target) == this);
if (!mXrayExpandos.initialized()) {
return nullptr;
}
return mXrayExpandos.lookup(target);
}
JSObject* XPCWrappedNativeScope::DetachExpandoChain(HandleObject target) {
MOZ_ASSERT(ObjectScope(target) == this);
if (!mXrayExpandos.initialized()) {
return nullptr;
}
return mXrayExpandos.removeValue(target);
}
bool XPCWrappedNativeScope::SetExpandoChain(JSContext* cx, HandleObject target,
HandleObject chain) {
MOZ_ASSERT(ObjectScope(target) == this);
MOZ_ASSERT(js::IsObjectInContextCompartment(target, cx));
MOZ_ASSERT_IF(chain, ObjectScope(chain) == this);
if (!mXrayExpandos.initialized() && !mXrayExpandos.init(cx)) {
return false;
}
return mXrayExpandos.put(cx, target, chain);
}
/***************************************************************************/
// static
void XPCWrappedNativeScope::DebugDumpAllScopes(int16_t depth) {
#ifdef DEBUG
depth--;
// get scope count.
int count = 0;
for (XPCWrappedNativeScope* cur : AllScopes()) {
mozilla::Unused << cur;
count++;
}
XPC_LOG_ALWAYS(("chain of %d XPCWrappedNativeScope(s)", count));
XPC_LOG_INDENT();
if (depth) {
for (XPCWrappedNativeScope* cur : AllScopes()) {
cur->DebugDump(depth);
}
}
XPC_LOG_OUTDENT();
#endif
}
void XPCWrappedNativeScope::DebugDump(int16_t depth) {
#ifdef DEBUG
depth--;
XPC_LOG_ALWAYS(("XPCWrappedNativeScope @ %p", this));
XPC_LOG_INDENT();
XPC_LOG_ALWAYS(("next @ %p", getNext()));
XPC_LOG_ALWAYS(("mComponents @ %p", mComponents.get()));
XPC_LOG_ALWAYS(("mCompartment @ %p", mCompartment));
XPC_LOG_ALWAYS(("mWrappedNativeMap @ %p with %d wrappers(s)",
mWrappedNativeMap, mWrappedNativeMap->Count()));
// iterate contexts...
if (depth && mWrappedNativeMap->Count()) {
XPC_LOG_INDENT();
for (auto i = mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
entry->value->DebugDump(depth);
}
XPC_LOG_OUTDENT();
}
XPC_LOG_ALWAYS(("mWrappedNativeProtoMap @ %p with %d protos(s)",
mWrappedNativeProtoMap, mWrappedNativeProtoMap->Count()));
// iterate contexts...
if (depth && mWrappedNativeProtoMap->Count()) {
XPC_LOG_INDENT();
for (auto i = mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) {
auto entry =
static_cast<ClassInfo2WrappedNativeProtoMap::Entry*>(i.Get());
entry->value->DebugDump(depth);
}
XPC_LOG_OUTDENT();
}
XPC_LOG_OUTDENT();
#endif
}
void XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(
JSContext* cx, ScopeSizeInfo* scopeSizeInfo) {
for (XPCWrappedNativeScope* cur : AllScopes()) {
cur->AddSizeOfIncludingThis(cx, scopeSizeInfo);
}
}
void XPCWrappedNativeScope::AddSizeOfIncludingThis(
JSContext* cx, ScopeSizeInfo* scopeSizeInfo) {
scopeSizeInfo->mScopeAndMapSize += scopeSizeInfo->mMallocSizeOf(this);
scopeSizeInfo->mScopeAndMapSize +=
mWrappedNativeMap->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf);
scopeSizeInfo->mScopeAndMapSize +=
mWrappedNativeProtoMap->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf);
auto realmCb = [](JSContext*, void* aData, JS::Handle<JS::Realm*> aRealm) {
auto* scopeSizeInfo = static_cast<ScopeSizeInfo*>(aData);
JSObject* global = GetRealmGlobalOrNull(aRealm);
if (global && dom::HasProtoAndIfaceCache(global)) {
dom::ProtoAndIfaceCache* cache = dom::GetProtoAndIfaceCache(global);
scopeSizeInfo->mProtoAndIfaceCacheSize +=
cache->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf);
}
};
IterateRealmsInCompartment(cx, Compartment(), scopeSizeInfo, realmCb);
// There are other XPCWrappedNativeScope members that could be measured;
// the above ones have been seen by DMD to be worth measuring. More stuff
// may be added later.
}