XPCWrappedNativeScope.cpp (17071B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* Class used to manage the wrapped native objects within a JS scope. */ 8 9 #include "AccessCheck.h" 10 #include "xpcprivate.h" 11 #include "XPCWrapper.h" 12 #include "nsContentUtils.h" 13 #include "nsCycleCollectionNoteRootCallback.h" 14 #include "ExpandedPrincipal.h" 15 #include "mozilla/BasePrincipal.h" 16 #include "mozilla/Preferences.h" 17 #include "XPCMaps.h" 18 #include "js/Object.h" // JS::GetCompartment 19 #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById 20 #include "js/RealmIterators.h" 21 #include "mozJSModuleLoader.h" 22 23 #include "mozilla/dom/BindingUtils.h" 24 25 using namespace mozilla; 26 using namespace xpc; 27 using namespace JS; 28 29 /***************************************************************************/ 30 31 static XPCWrappedNativeScopeList& AllScopes() { 32 return XPCJSRuntime::Get()->GetWrappedNativeScopes(); 33 } 34 35 static bool RemoteXULForbidsXBLScopeForPrincipal(nsIPrincipal* aPrincipal) { 36 // AllowXULXBLForPrincipal will return true for system principal, but we 37 // don't want that here. 38 MOZ_ASSERT(nsContentUtils::IsInitialized()); 39 if (aPrincipal->IsSystemPrincipal()) { 40 return false; 41 } 42 43 // If this domain isn't whitelisted, we're done. 44 if (!nsContentUtils::AllowXULXBLForPrincipal(aPrincipal)) { 45 return false; 46 } 47 48 // Check the pref to determine how we should behave. 49 return !Preferences::GetBool("dom.use_xbl_scopes_for_remote_xul", false); 50 } 51 52 static bool RemoteXULForbidsXBLScope(HandleObject aFirstGlobal) { 53 MOZ_ASSERT(aFirstGlobal); 54 55 // Certain singleton sandoxes are created very early in startup - too early 56 // to call into AllowXULXBLForPrincipal. We never create XBL scopes for 57 // sandboxes anway, and certainly not for these singleton scopes. So we just 58 // short-circuit here. 59 if (IsSandbox(aFirstGlobal)) { 60 return false; 61 } 62 63 nsIPrincipal* principal = xpc::GetObjectPrincipal(aFirstGlobal); 64 return RemoteXULForbidsXBLScopeForPrincipal(principal); 65 } 66 67 XPCWrappedNativeScope::XPCWrappedNativeScope(JS::Compartment* aCompartment, 68 JS::HandleObject aFirstGlobal) 69 : mWrappedNativeMap(mozilla::MakeUnique<Native2WrappedNativeMap>()), 70 mWrappedNativeProtoMap( 71 mozilla::MakeUnique<ClassInfo2WrappedNativeProtoMap>()), 72 mComponents(nullptr), 73 mCompartment(aCompartment) { 74 #ifdef DEBUG 75 for (XPCWrappedNativeScope* cur : AllScopes()) { 76 MOZ_ASSERT(aCompartment != cur->Compartment(), "dup object"); 77 } 78 #endif 79 80 AllScopes().insertBack(this); 81 82 MOZ_COUNT_CTOR(XPCWrappedNativeScope); 83 84 // Determine whether we would allow an XBL scope in this situation. 85 // In addition to being pref-controlled, we also disable XBL scopes for 86 // remote XUL domains, _except_ if we have an additional pref override set. 87 // 88 // Note that we can't quite remove this yet, even though we never actually 89 // use XBL scopes, because the security manager uses this boolean to make 90 // decisions that we rely on in our test infrastructure. 91 // 92 // FIXME(emilio): Now that the security manager is the only caller probably 93 // should be renamed, but what's a good name for this? 94 mAllowContentXBLScope = !RemoteXULForbidsXBLScope(aFirstGlobal); 95 } 96 97 bool XPCWrappedNativeScope::GetComponentsJSObject(JSContext* cx, 98 JS::MutableHandleObject obj) { 99 if (!mComponents) { 100 bool system = AccessCheck::isChrome(mCompartment); 101 MOZ_RELEASE_ASSERT(system, "How did we get a non-system Components?"); 102 mComponents = new nsXPCComponents(this); 103 } 104 105 RootedValue val(cx); 106 xpcObjectHelper helper(mComponents); 107 bool ok = XPCConvert::NativeInterface2JSObject(cx, &val, helper, nullptr, 108 false, nullptr); 109 if (NS_WARN_IF(!ok)) { 110 return false; 111 } 112 113 if (NS_WARN_IF(!val.isObject())) { 114 return false; 115 } 116 117 obj.set(&val.toObject()); 118 return true; 119 } 120 121 static bool DefineSubcomponentProperty(JSContext* aCx, HandleObject aGlobal, 122 nsISupports* aSubcomponent, 123 const nsID* aIID, 124 unsigned int aStringIndex) { 125 RootedValue subcompVal(aCx); 126 xpcObjectHelper helper(aSubcomponent); 127 if (!XPCConvert::NativeInterface2JSObject(aCx, &subcompVal, helper, aIID, 128 false, nullptr)) 129 return false; 130 if (NS_WARN_IF(!subcompVal.isObject())) { 131 return false; 132 } 133 RootedId id(aCx, XPCJSContext::Get()->GetStringID(aStringIndex)); 134 return JS_DefinePropertyById(aCx, aGlobal, id, subcompVal, 0); 135 } 136 137 bool XPCWrappedNativeScope::AttachComponentsObject(JSContext* aCx) { 138 RootedObject components(aCx); 139 if (!GetComponentsJSObject(aCx, &components)) { 140 return false; 141 } 142 143 RootedObject global(aCx, CurrentGlobalOrNull(aCx)); 144 145 const unsigned attrs = JSPROP_READONLY | JSPROP_RESOLVING | JSPROP_PERMANENT; 146 147 RootedId id(aCx, 148 XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS)); 149 if (!JS_DefinePropertyById(aCx, global, id, components, attrs)) { 150 return false; 151 } 152 153 // _iid can be nullptr if the object implements classinfo. 154 #define DEFINE_SUBCOMPONENT_PROPERTY(_comp, _type, _iid, _id) \ 155 nsCOMPtr<nsIXPCComponents_##_type> obj##_type; \ 156 if (NS_FAILED(_comp->Get##_type(getter_AddRefs(obj##_type)))) return false; \ 157 if (!DefineSubcomponentProperty(aCx, global, obj##_type, _iid, \ 158 XPCJSContext::IDX_##_id)) \ 159 return false; 160 161 DEFINE_SUBCOMPONENT_PROPERTY(mComponents, Interfaces, nullptr, CI) 162 DEFINE_SUBCOMPONENT_PROPERTY(mComponents, Results, nullptr, CR) 163 164 DEFINE_SUBCOMPONENT_PROPERTY(mComponents, Classes, nullptr, CC) 165 DEFINE_SUBCOMPONENT_PROPERTY(mComponents, Utils, 166 &NS_GET_IID(nsIXPCComponents_Utils), CU) 167 168 #undef DEFINE_SUBCOMPONENT_PROPERTY 169 170 return true; 171 } 172 173 bool XPCWrappedNativeScope::AttachJSServices(JSContext* aCx) { 174 RootedObject global(aCx, CurrentGlobalOrNull(aCx)); 175 return mozJSModuleLoader::Get()->DefineJSServices(aCx, global); 176 } 177 178 bool XPCWrappedNativeScope::XBLScopeStateMatches(nsIPrincipal* aPrincipal) { 179 return mAllowContentXBLScope == 180 !RemoteXULForbidsXBLScopeForPrincipal(aPrincipal); 181 } 182 183 bool XPCWrappedNativeScope::AllowContentXBLScope(Realm* aRealm) { 184 // We only disallow XBL scopes in remote XUL situations. 185 MOZ_ASSERT_IF(!mAllowContentXBLScope, nsContentUtils::AllowXULXBLForPrincipal( 186 xpc::GetRealmPrincipal(aRealm))); 187 return mAllowContentXBLScope; 188 } 189 190 namespace xpc { 191 JSObject* GetUAWidgetScope(JSContext* cx, JSObject* contentScopeArg) { 192 JS::RootedObject contentScope(cx, contentScopeArg); 193 JSAutoRealm ar(cx, contentScope); 194 nsIPrincipal* principal = GetObjectPrincipal(contentScope); 195 196 if (principal->IsSystemPrincipal()) { 197 return JS::GetNonCCWObjectGlobal(contentScope); 198 } 199 200 return GetUAWidgetScope(cx, principal); 201 } 202 203 JSObject* GetUAWidgetScope(JSContext* cx, nsIPrincipal* principal) { 204 RootedObject scope(cx, XPCJSRuntime::Get()->GetUAWidgetScope(cx, principal)); 205 NS_ENSURE_TRUE(scope, nullptr); // See bug 858642. 206 207 scope = js::UncheckedUnwrap(scope); 208 JS::ExposeObjectToActiveJS(scope); 209 return scope; 210 } 211 212 bool AllowContentXBLScope(JS::Realm* realm) { 213 JS::Compartment* comp = GetCompartmentForRealm(realm); 214 XPCWrappedNativeScope* scope = CompartmentPrivate::Get(comp)->GetScope(); 215 MOZ_ASSERT(scope); 216 return scope->AllowContentXBLScope(realm); 217 } 218 219 } /* namespace xpc */ 220 221 XPCWrappedNativeScope::~XPCWrappedNativeScope() { 222 MOZ_COUNT_DTOR(XPCWrappedNativeScope); 223 224 // We can do additional cleanup assertions here... 225 226 MOZ_ASSERT(0 == mWrappedNativeMap->Count(), "scope has non-empty map"); 227 228 MOZ_ASSERT(0 == mWrappedNativeProtoMap->Count(), "scope has non-empty map"); 229 230 // This should not be necessary, since the Components object should die 231 // with the scope but just in case. 232 if (mComponents) { 233 mComponents->mScope = nullptr; 234 } 235 236 // XXX we should assert that we are dead or that xpconnect has shutdown 237 // XXX might not want to do this at xpconnect shutdown time??? 238 mComponents = nullptr; 239 240 MOZ_RELEASE_ASSERT(!mXrayExpandos.initialized()); 241 242 mCompartment = nullptr; 243 } 244 245 // static 246 void XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(XPCJSRuntime* xpcrt, 247 JSTracer* trc) { 248 // Do JS::TraceEdge for all wrapped natives with external references, as 249 // well as any DOM expando objects. 250 // 251 // Note: the GC can call this from a JS helper thread. We don't use 252 // AllScopes() because that asserts we're on the main thread. 253 254 for (XPCWrappedNativeScope* cur : xpcrt->GetWrappedNativeScopes()) { 255 for (auto i = cur->mWrappedNativeMap->Iter(); !i.done(); i.next()) { 256 XPCWrappedNative* wrapper = i.get().value(); 257 if (wrapper->HasExternalReference() && !wrapper->IsWrapperExpired()) { 258 wrapper->TraceSelf(trc); 259 } 260 } 261 } 262 } 263 264 // static 265 void XPCWrappedNativeScope::SuspectAllWrappers( 266 nsCycleCollectionNoteRootCallback& cb) { 267 for (XPCWrappedNativeScope* cur : AllScopes()) { 268 for (auto i = cur->mWrappedNativeMap->Iter(); !i.done(); i.next()) { 269 i.get().value()->Suspect(cb); 270 } 271 } 272 } 273 274 void XPCWrappedNativeScope::UpdateWeakPointersAfterGC(JSTracer* trc) { 275 // Sweep waivers. 276 if (mWaiverWrapperMap) { 277 mWaiverWrapperMap->UpdateWeakPointers(trc); 278 } 279 280 if (!js::IsCompartmentZoneSweepingOrCompacting(mCompartment)) { 281 return; 282 } 283 284 if (!js::CompartmentHasLiveGlobal(mCompartment)) { 285 GetWrappedNativeMap()->Clear(); 286 mWrappedNativeProtoMap->Clear(); 287 288 // The fields below are traced only if there's a live global in the 289 // compartment, see TraceXPCGlobal. The compartment has no live globals so 290 // clear these pointers here. 291 if (mXrayExpandos.initialized()) { 292 mXrayExpandos.destroy(); 293 } 294 mIDProto = nullptr; 295 mIIDProto = nullptr; 296 mCIDProto = nullptr; 297 return; 298 } 299 300 // Sweep mWrappedNativeMap for dying flat JS objects. Moving has already 301 // been handled by XPCWrappedNative::FlatJSObjectMoved. 302 for (auto iter = GetWrappedNativeMap()->ModIter(); !iter.done(); 303 iter.next()) { 304 XPCWrappedNative* wrapper = iter.get().value(); 305 JSObject* obj = wrapper->GetFlatJSObjectPreserveColor(); 306 if (JS_UpdateWeakPointerAfterGCUnbarriered(trc, &obj)) { 307 MOZ_ASSERT(obj == wrapper->GetFlatJSObjectPreserveColor()); 308 MOZ_ASSERT(JS::GetCompartment(obj) == mCompartment); 309 } else { 310 iter.remove(); 311 } 312 } 313 314 // Sweep mWrappedNativeProtoMap for dying prototype JSObjects. Moving has 315 // already been handled by XPCWrappedNativeProto::JSProtoObjectMoved. 316 for (auto i = mWrappedNativeProtoMap->ModIter(); !i.done(); i.next()) { 317 XPCWrappedNativeProto* proto = i.get().value(); 318 JSObject* obj = proto->GetJSProtoObjectPreserveColor(); 319 if (JS_UpdateWeakPointerAfterGCUnbarriered(trc, &obj)) { 320 MOZ_ASSERT(JS::GetCompartment(obj) == mCompartment); 321 MOZ_ASSERT(obj == proto->GetJSProtoObjectPreserveColor()); 322 } else { 323 i.remove(); 324 } 325 } 326 } 327 328 // static 329 void XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs() { 330 for (XPCWrappedNativeScope* cur : AllScopes()) { 331 for (auto i = cur->mWrappedNativeMap->Iter(); !i.done(); i.next()) { 332 i.get().value()->SweepTearOffs(); 333 } 334 } 335 } 336 337 // static 338 void XPCWrappedNativeScope::SystemIsBeingShutDown() { 339 // We're forcibly killing scopes, rather than allowing them to go away 340 // when they're ready. As such, we need to do some cleanup before they 341 // can safely be destroyed. 342 343 for (XPCWrappedNativeScope* cur : AllScopes()) { 344 // Give the Components object a chance to try to clean up. 345 if (cur->mComponents) { 346 cur->mComponents->SystemIsBeingShutDown(); 347 } 348 349 // Null out these pointers to prevent ~ObjectPtr assertion failures if we 350 // leaked things at shutdown. 351 cur->mIDProto = nullptr; 352 cur->mIIDProto = nullptr; 353 cur->mCIDProto = nullptr; 354 355 // Similarly, destroy mXrayExpandos to prevent assertion failures. 356 if (cur->mXrayExpandos.initialized()) { 357 cur->mXrayExpandos.destroy(); 358 } 359 360 // Walk the protos first. Wrapper shutdown can leave dangling 361 // proto pointers in the proto map. 362 for (auto i = cur->mWrappedNativeProtoMap->ModIter(); !i.done(); i.next()) { 363 i.get().value()->SystemIsBeingShutDown(); 364 i.remove(); 365 } 366 for (auto i = cur->mWrappedNativeMap->ModIter(); !i.done(); i.next()) { 367 i.get().value()->SystemIsBeingShutDown(); 368 i.remove(); 369 } 370 371 CompartmentPrivate* priv = CompartmentPrivate::Get(cur->Compartment()); 372 priv->SystemIsBeingShutDown(); 373 } 374 } 375 376 /***************************************************************************/ 377 378 JSObject* XPCWrappedNativeScope::GetExpandoChain(HandleObject target) { 379 MOZ_ASSERT(ObjectScope(target) == this); 380 if (!mXrayExpandos.initialized()) { 381 return nullptr; 382 } 383 return mXrayExpandos.lookup(target); 384 } 385 386 JSObject* XPCWrappedNativeScope::DetachExpandoChain(HandleObject target) { 387 MOZ_ASSERT(ObjectScope(target) == this); 388 if (!mXrayExpandos.initialized()) { 389 return nullptr; 390 } 391 return mXrayExpandos.removeValue(target); 392 } 393 394 bool XPCWrappedNativeScope::SetExpandoChain(JSContext* cx, HandleObject target, 395 HandleObject chain) { 396 MOZ_ASSERT(ObjectScope(target) == this); 397 MOZ_ASSERT(js::IsObjectInContextCompartment(target, cx)); 398 MOZ_ASSERT_IF(chain, ObjectScope(chain) == this); 399 if (!mXrayExpandos.initialized() && !mXrayExpandos.init(cx)) { 400 return false; 401 } 402 return mXrayExpandos.put(cx, target, chain); 403 } 404 405 /***************************************************************************/ 406 407 // static 408 void XPCWrappedNativeScope::DebugDumpAllScopes(int16_t depth) { 409 #ifdef DEBUG 410 depth--; 411 412 // get scope count. 413 int count = 0; 414 for (XPCWrappedNativeScope* cur : AllScopes()) { 415 (void)cur; 416 count++; 417 } 418 419 XPC_LOG_ALWAYS(("chain of %d XPCWrappedNativeScope(s)", count)); 420 XPC_LOG_INDENT(); 421 if (depth) { 422 for (XPCWrappedNativeScope* cur : AllScopes()) { 423 cur->DebugDump(depth); 424 } 425 } 426 XPC_LOG_OUTDENT(); 427 #endif 428 } 429 430 void XPCWrappedNativeScope::DebugDump(int16_t depth) { 431 #ifdef DEBUG 432 depth--; 433 XPC_LOG_ALWAYS(("XPCWrappedNativeScope @ %p", this)); 434 XPC_LOG_INDENT(); 435 XPC_LOG_ALWAYS(("next @ %p", getNext())); 436 XPC_LOG_ALWAYS(("mComponents @ %p", mComponents.get())); 437 XPC_LOG_ALWAYS(("mCompartment @ %p", mCompartment)); 438 439 XPC_LOG_ALWAYS(("mWrappedNativeMap @ %p with %d wrappers(s)", 440 mWrappedNativeMap.get(), mWrappedNativeMap->Count())); 441 // iterate contexts... 442 if (depth && mWrappedNativeMap->Count()) { 443 XPC_LOG_INDENT(); 444 for (auto i = mWrappedNativeMap->Iter(); !i.done(); i.next()) { 445 i.get().value()->DebugDump(depth); 446 } 447 XPC_LOG_OUTDENT(); 448 } 449 450 XPC_LOG_ALWAYS(("mWrappedNativeProtoMap @ %p with %d protos(s)", 451 mWrappedNativeProtoMap.get(), 452 mWrappedNativeProtoMap->Count())); 453 // iterate contexts... 454 if (depth && mWrappedNativeProtoMap->Count()) { 455 XPC_LOG_INDENT(); 456 for (auto i = mWrappedNativeProtoMap->Iter(); !i.done(); i.next()) { 457 i.get().value()->DebugDump(depth); 458 } 459 XPC_LOG_OUTDENT(); 460 } 461 XPC_LOG_OUTDENT(); 462 #endif 463 } 464 465 void XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis( 466 JSContext* cx, ScopeSizeInfo* scopeSizeInfo) { 467 for (XPCWrappedNativeScope* cur : AllScopes()) { 468 cur->AddSizeOfIncludingThis(cx, scopeSizeInfo); 469 } 470 } 471 472 void XPCWrappedNativeScope::AddSizeOfIncludingThis( 473 JSContext* cx, ScopeSizeInfo* scopeSizeInfo) { 474 scopeSizeInfo->mScopeAndMapSize += scopeSizeInfo->mMallocSizeOf(this); 475 scopeSizeInfo->mScopeAndMapSize += 476 mWrappedNativeMap->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf); 477 scopeSizeInfo->mScopeAndMapSize += 478 mWrappedNativeProtoMap->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf); 479 480 auto realmCb = [](JSContext*, void* aData, JS::Realm* aRealm, 481 const JS::AutoRequireNoGC& nogc) { 482 auto* scopeSizeInfo = static_cast<ScopeSizeInfo*>(aData); 483 JSObject* global = GetRealmGlobalOrNull(aRealm); 484 if (global && dom::HasProtoAndIfaceCache(global)) { 485 dom::ProtoAndIfaceCache* cache = dom::GetProtoAndIfaceCache(global); 486 scopeSizeInfo->mProtoAndIfaceCacheSize += 487 cache->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf); 488 } 489 }; 490 IterateRealmsInCompartment(cx, Compartment(), scopeSizeInfo, realmCb); 491 492 // There are other XPCWrappedNativeScope members that could be measured; 493 // the above ones have been seen by DMD to be worth measuring. More stuff 494 // may be added later. 495 }