WebIDLGlobalNameHash.cpp (10824B)
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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "WebIDLGlobalNameHash.h" 8 9 #include "WrapperFactory.h" 10 #include "js/Class.h" 11 #include "js/GCAPI.h" 12 #include "js/Id.h" 13 #include "js/Object.h" // JS::GetClass, JS::GetReservedSlot 14 #include "js/Wrapper.h" 15 #include "jsapi.h" 16 #include "jsfriendapi.h" 17 #include "mozilla/ErrorResult.h" 18 #include "mozilla/Maybe.h" 19 #include "mozilla/dom/BindingNames.h" 20 #include "mozilla/dom/DOMJSClass.h" 21 #include "mozilla/dom/Exceptions.h" 22 #include "mozilla/dom/JSSlots.h" 23 #include "mozilla/dom/PrototypeList.h" 24 #include "mozilla/dom/ProxyHandlerUtils.h" 25 #include "mozilla/dom/RegisterBindings.h" 26 #include "nsGlobalWindowInner.h" 27 #include "nsTHashtable.h" 28 29 namespace mozilla::dom { 30 31 static JSObject* FindNamedConstructorForXray( 32 JSContext* aCx, JS::Handle<jsid> aId, const WebIDLNameTableEntry* aEntry) { 33 JSObject* interfaceObject = 34 GetPerInterfaceObjectHandle(aCx, aEntry->mConstructorId, aEntry->mCreate, 35 DefineInterfaceProperty::No); 36 if (!interfaceObject) { 37 return nullptr; 38 } 39 40 if (IsInterfaceObject(interfaceObject)) { 41 // This is a call over Xrays, so we will actually use the return value 42 // (instead of just having it defined on the global now). Check for named 43 // constructors with this id, in case that's what the caller is asking for. 44 for (unsigned slot = INTERFACE_OBJECT_FIRST_LEGACY_FACTORY_FUNCTION; 45 slot < INTERFACE_OBJECT_MAX_SLOTS; ++slot) { 46 const JS::Value& v = js::GetFunctionNativeReserved(interfaceObject, slot); 47 if (!v.isObject()) { 48 break; 49 } 50 JSObject* constructor = &v.toObject(); 51 if (JS_GetMaybePartialFunctionId(JS_GetObjectFunction(constructor)) == 52 aId.toString()) { 53 return constructor; 54 } 55 } 56 } 57 58 // None of the legacy factory functions match, so the caller must want the 59 // interface object itself. 60 return interfaceObject; 61 } 62 63 /* static */ 64 bool WebIDLGlobalNameHash::DefineIfEnabled( 65 JSContext* aCx, JS::Handle<JSObject*> aObj, JS::Handle<jsid> aId, 66 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc, 67 bool* aFound) { 68 MOZ_ASSERT(aId.isString(), "Check for string id before calling this!"); 69 70 const WebIDLNameTableEntry* entry = GetEntry(aId.toLinearString()); 71 if (!entry) { 72 *aFound = false; 73 return true; 74 } 75 76 *aFound = true; 77 78 ConstructorEnabled checkEnabledForScope = entry->mEnabled; 79 // We do the enabled check on the current Realm of aCx, but for the 80 // actual object we pass in the underlying object in the Xray case. That 81 // way the callee can decide whether to allow access based on the caller 82 // or the window being touched. 83 // 84 // Using aCx to represent the current Realm for CheckedUnwrapDynamic 85 // purposes is OK here, because that's the Realm where we plan to do 86 // our property-defining. 87 JS::Rooted<JSObject*> global( 88 aCx, 89 js::CheckedUnwrapDynamic(aObj, aCx, /* stopAtWindowProxy = */ false)); 90 if (!global) { 91 return Throw(aCx, NS_ERROR_DOM_SECURITY_ERR); 92 } 93 94 { 95 // It's safe to pass "&global" here, because we've already unwrapped it, but 96 // for general sanity better to not have debug code even having the 97 // appearance of mutating things that opt code uses. 98 #ifdef DEBUG 99 JS::Rooted<JSObject*> temp(aCx, global); 100 DebugOnly<nsGlobalWindowInner*> win; 101 MOZ_ASSERT(NS_SUCCEEDED( 102 UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT(Window, &temp, win, aCx))); 103 #endif 104 } 105 106 if (checkEnabledForScope && !checkEnabledForScope(aCx, global)) { 107 return true; 108 } 109 110 // The DOM constructor resolve machinery interacts with Xrays in tricky 111 // ways, and there are some asymmetries that are important to understand. 112 // 113 // In the regular (non-Xray) case, we only want to resolve constructors 114 // once (so that if they're deleted, they don't reappear). We do this by 115 // stashing the constructor in a slot on the global, such that we can see 116 // during resolve whether we've created it already. This is rather 117 // memory-intensive, so we don't try to maintain these semantics when 118 // manipulating a global over Xray (so the properties just re-resolve if 119 // they've been deleted). 120 // 121 // Unfortunately, there's a bit of an impedance-mismatch between the Xray 122 // and non-Xray machinery. The Xray machinery wants an API that returns a 123 // JS::PropertyDescriptor, so that the resolve hook doesn't have to get 124 // snared up with trying to define a property on the Xray holder. At the 125 // same time, the DefineInterface callbacks are set up to define things 126 // directly on the global. And re-jiggering them to return property 127 // descriptors is tricky, because some DefineInterface callbacks define 128 // multiple things (like the Image() alias for HTMLImageElement). 129 // 130 // So the setup is as-follows: 131 // 132 // * The resolve function takes a JS::PropertyDescriptor, but in the 133 // non-Xray case, callees may define things directly on the global, and 134 // set the value on the property descriptor to |undefined| to indicate 135 // that there's nothing more for the caller to do. We assert against 136 // this behavior in the Xray case. 137 // 138 // * We make sure that we do a non-Xray resolve first, so that all the 139 // slots are set up. In the Xray case, this means unwrapping and doing 140 // a non-Xray resolve before doing the Xray resolve. 141 // 142 // This all could use some grand refactoring, but for now we just limp 143 // along. 144 if (xpc::WrapperFactory::IsXrayWrapper(aObj)) { 145 JS::Rooted<JSObject*> constructor(aCx); 146 { 147 JSAutoRealm ar(aCx, global); 148 constructor = FindNamedConstructorForXray(aCx, aId, entry); 149 } 150 if (NS_WARN_IF(!constructor)) { 151 return Throw(aCx, NS_ERROR_FAILURE); 152 } 153 if (!JS_WrapObject(aCx, &constructor)) { 154 return Throw(aCx, NS_ERROR_FAILURE); 155 } 156 157 aDesc.set(mozilla::Some(JS::PropertyDescriptor::Data( 158 JS::ObjectValue(*constructor), {JS::PropertyAttribute::Configurable, 159 JS::PropertyAttribute::Writable}))); 160 return true; 161 } 162 163 // We've already checked whether the interface is enabled (see 164 // checkEnabledForScope above), so it's fine to pass 165 // DefineInterfaceProperty::Always here. 166 JS::Rooted<JSObject*> interfaceObject( 167 aCx, 168 GetPerInterfaceObjectHandle(aCx, entry->mConstructorId, entry->mCreate, 169 DefineInterfaceProperty::Always)); 170 if (NS_WARN_IF(!interfaceObject)) { 171 return Throw(aCx, NS_ERROR_FAILURE); 172 } 173 174 // We've already defined the property. We indicate this to the caller 175 // by filling a property descriptor with JS::UndefinedValue() as the 176 // value. We still have to fill in a property descriptor, though, so 177 // that the caller knows the property is in fact on this object. 178 aDesc.set( 179 mozilla::Some(JS::PropertyDescriptor::Data(JS::UndefinedValue(), {}))); 180 return true; 181 } 182 183 /* static */ 184 bool WebIDLGlobalNameHash::MayResolve(jsid aId) { 185 return GetEntry(aId.toLinearString()) != nullptr; 186 } 187 188 /* static */ 189 bool WebIDLGlobalNameHash::GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj, 190 NameType aNameType, 191 JS::MutableHandleVector<jsid> aNames) { 192 // aObj is always a Window here, so GetProtoAndIfaceCache on it is safe. 193 ProtoAndIfaceCache* cache = GetProtoAndIfaceCache(aObj); 194 for (size_t i = 0; i < sCount; ++i) { 195 const WebIDLNameTableEntry& entry = sEntries[i]; 196 // If aNameType is not AllNames, only include things whose entry slot in the 197 // ProtoAndIfaceCache is null. 198 if ((aNameType == AllNames || 199 !cache->HasEntryInSlot(entry.mConstructorId)) && 200 (!entry.mEnabled || entry.mEnabled(aCx, aObj))) { 201 JSString* str = JS_AtomizeStringN(aCx, BindingName(entry.mNameOffset), 202 entry.mNameLength); 203 if (!str || !aNames.append(JS::PropertyKey::NonIntAtom(str))) { 204 return false; 205 } 206 } 207 } 208 209 return true; 210 } 211 212 /* static */ 213 bool WebIDLGlobalNameHash::ResolveForSystemGlobal(JSContext* aCx, 214 JS::Handle<JSObject*> aObj, 215 JS::Handle<jsid> aId, 216 bool* aResolvedp) { 217 MOZ_ASSERT(JS_IsGlobalObject(aObj)); 218 219 // First we try to resolve standard classes. 220 if (!JS_ResolveStandardClass(aCx, aObj, aId, aResolvedp)) { 221 return false; 222 } 223 if (*aResolvedp) { 224 return true; 225 } 226 227 // We don't resolve any non-string entries. 228 if (!aId.isString()) { 229 return true; 230 } 231 232 // XXX(nika): In the Window case, we unwrap our global object here to handle 233 // XRays. I don't think we ever create xrays to system globals, so I believe 234 // we can skip this step. 235 MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(aObj), "Xrays not supported!"); 236 237 // Look up the corresponding entry in the name table, and resolve if enabled. 238 const WebIDLNameTableEntry* entry = GetEntry(aId.toLinearString()); 239 if (entry && (!entry->mEnabled || entry->mEnabled(aCx, aObj))) { 240 // We've already checked whether the interface is enabled (see 241 // entry->mEnabled above), so it's fine to pass 242 // DefineInterfaceProperty::Always here. 243 if (NS_WARN_IF(!GetPerInterfaceObjectHandle( 244 aCx, entry->mConstructorId, entry->mCreate, 245 DefineInterfaceProperty::Always))) { 246 return Throw(aCx, NS_ERROR_FAILURE); 247 } 248 249 *aResolvedp = true; 250 } 251 return true; 252 } 253 254 /* static */ 255 bool WebIDLGlobalNameHash::NewEnumerateSystemGlobal( 256 JSContext* aCx, JS::Handle<JSObject*> aObj, 257 JS::MutableHandleVector<jsid> aProperties, bool aEnumerableOnly) { 258 MOZ_ASSERT(JS_IsGlobalObject(aObj)); 259 260 if (!JS_NewEnumerateStandardClasses(aCx, aObj, aProperties, 261 aEnumerableOnly)) { 262 return false; 263 } 264 265 // All properties defined on our global are non-enumerable, so we can skip 266 // remaining properties. 267 if (aEnumerableOnly) { 268 return true; 269 } 270 271 // Enumerate all entries & add enabled ones. 272 for (size_t i = 0; i < sCount; ++i) { 273 const WebIDLNameTableEntry& entry = sEntries[i]; 274 if (!entry.mEnabled || entry.mEnabled(aCx, aObj)) { 275 JSString* str = JS_AtomizeStringN(aCx, BindingName(entry.mNameOffset), 276 entry.mNameLength); 277 if (!str || !aProperties.append(JS::PropertyKey::NonIntAtom(str))) { 278 return false; 279 } 280 } 281 } 282 return true; 283 } 284 285 } // namespace mozilla::dom