DOMJSProxyHandler.cpp (10902B)
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 "mozilla/dom/DOMJSProxyHandler.h" 8 9 #include "WrapperFactory.h" 10 #include "XPCWrapper.h" 11 #include "js/Object.h" // JS::GetCompartment 12 #include "js/PropertyAndElement.h" // JS_AlreadyHasOwnPropertyById, JS_DefineProperty, JS_DefinePropertyById, JS_DeleteProperty, JS_DeletePropertyById 13 #include "js/friend/DOMProxy.h" // JS::DOMProxyShadowsResult, JS::ExpandoAndGeneration, JS::SetDOMProxyInformation 14 #include "jsapi.h" 15 #include "mozilla/dom/BindingUtils.h" 16 #include "nsWrapperCacheInlines.h" 17 #include "xpcprivate.h" 18 #include "xpcpublic.h" 19 20 using namespace JS; 21 22 namespace mozilla::dom { 23 24 jsid s_length_id = JS::PropertyKey::Void(); 25 26 bool DefineStaticJSVals(JSContext* cx) { 27 return AtomizeAndPinJSString(cx, s_length_id, "length"); 28 } 29 30 const char DOMProxyHandler::family = 0; 31 32 JS::DOMProxyShadowsResult DOMProxyShadows(JSContext* cx, 33 JS::Handle<JSObject*> proxy, 34 JS::Handle<jsid> id) { 35 using DOMProxyShadowsResult = JS::DOMProxyShadowsResult; 36 37 JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy)); 38 JS::Value v = js::GetProxyPrivate(proxy); 39 bool isOverrideBuiltins = !v.isObject() && !v.isUndefined(); 40 if (expando) { 41 bool hasOwn; 42 if (!JS_AlreadyHasOwnPropertyById(cx, expando, id, &hasOwn)) 43 return DOMProxyShadowsResult::ShadowCheckFailed; 44 45 if (hasOwn) { 46 return isOverrideBuiltins 47 ? DOMProxyShadowsResult::ShadowsViaIndirectExpando 48 : DOMProxyShadowsResult::ShadowsViaDirectExpando; 49 } 50 } 51 52 if (!isOverrideBuiltins) { 53 // Our expando, if any, didn't shadow, so we're not shadowing at all. 54 return DOMProxyShadowsResult::DoesntShadow; 55 } 56 57 bool hasOwn; 58 if (!GetProxyHandler(proxy)->hasOwn(cx, proxy, id, &hasOwn)) 59 return DOMProxyShadowsResult::ShadowCheckFailed; 60 61 return hasOwn ? DOMProxyShadowsResult::Shadows 62 : DOMProxyShadowsResult::DoesntShadowUnique; 63 } 64 65 // Store the information for the specialized ICs. 66 struct SetDOMProxyInformation { 67 SetDOMProxyInformation() { 68 JS::SetDOMProxyInformation((const void*)&DOMProxyHandler::family, 69 DOMProxyShadows, 70 &RemoteObjectProxyBase::sCrossOriginProxyFamily); 71 } 72 }; 73 74 MOZ_RUNINIT SetDOMProxyInformation gSetDOMProxyInformation; 75 76 static inline void CheckExpandoObject(JSObject* proxy, 77 const JS::Value& expando) { 78 #ifdef DEBUG 79 JSObject* obj = &expando.toObject(); 80 MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarriered(&obj)); 81 MOZ_ASSERT(JS::GetCompartment(proxy) == JS::GetCompartment(obj)); 82 83 // When we create an expando object in EnsureExpandoObject below, we preserve 84 // the wrapper. The wrapper is released when the object is unlinked, but we 85 // should never call these functions after that point. 86 nsISupports* native = UnwrapDOMObject<nsISupports>(proxy); 87 nsWrapperCache* cache; 88 // QueryInterface to nsWrapperCache will not GC. 89 JS::AutoSuppressGCAnalysis suppress; 90 CallQueryInterface(native, &cache); 91 MOZ_ASSERT(cache->PreservingWrapper()); 92 #endif 93 } 94 95 static inline void CheckExpandoAndGeneration( 96 JSObject* proxy, JS::ExpandoAndGeneration* expandoAndGeneration) { 97 #ifdef DEBUG 98 JS::Value value = expandoAndGeneration->expando; 99 if (!value.isUndefined()) CheckExpandoObject(proxy, value); 100 #endif 101 } 102 103 static inline void CheckDOMProxy(JSObject* proxy) { 104 #ifdef DEBUG 105 MOZ_ASSERT(IsDOMProxy(proxy), "expected a DOM proxy object"); 106 MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarriered(&proxy)); 107 nsISupports* native = UnwrapDOMObject<nsISupports>(proxy); 108 nsWrapperCache* cache; 109 // QI to nsWrapperCache cannot GC for very non-obvious reasons; see 110 // https://searchfox.org/mozilla-central/rev/55da592d85c2baf8d8818010c41d9738c97013d2/js/xpconnect/src/XPCWrappedJSClass.cpp#521,545-548 111 JS::AutoSuppressGCAnalysis nogc; 112 CallQueryInterface(native, &cache); 113 MOZ_ASSERT(cache->GetWrapperPreserveColor() == proxy); 114 #endif 115 } 116 117 // static 118 JSObject* DOMProxyHandler::GetAndClearExpandoObject(JSObject* obj) { 119 CheckDOMProxy(obj); 120 121 JS::Value v = js::GetProxyPrivate(obj); 122 if (v.isUndefined()) { 123 return nullptr; 124 } 125 126 if (v.isObject()) { 127 js::SetProxyPrivate(obj, UndefinedValue()); 128 } else { 129 auto* expandoAndGeneration = 130 static_cast<JS::ExpandoAndGeneration*>(v.toPrivate()); 131 v = expandoAndGeneration->expando; 132 if (v.isUndefined()) { 133 return nullptr; 134 } 135 expandoAndGeneration->expando = UndefinedValue(); 136 } 137 138 CheckExpandoObject(obj, v); 139 140 return &v.toObject(); 141 } 142 143 // static 144 JSObject* DOMProxyHandler::EnsureExpandoObject(JSContext* cx, 145 JS::Handle<JSObject*> obj) { 146 CheckDOMProxy(obj); 147 148 JS::Value v = js::GetProxyPrivate(obj); 149 if (v.isObject()) { 150 CheckExpandoObject(obj, v); 151 return &v.toObject(); 152 } 153 154 JS::ExpandoAndGeneration* expandoAndGeneration = nullptr; 155 if (!v.isUndefined()) { 156 expandoAndGeneration = 157 static_cast<JS::ExpandoAndGeneration*>(v.toPrivate()); 158 CheckExpandoAndGeneration(obj, expandoAndGeneration); 159 if (expandoAndGeneration->expando.isObject()) { 160 return &expandoAndGeneration->expando.toObject(); 161 } 162 } 163 164 JS::Rooted<JSObject*> expando( 165 cx, JS_NewObjectWithGivenProto(cx, nullptr, nullptr)); 166 if (!expando) { 167 return nullptr; 168 } 169 170 nsISupports* native = UnwrapDOMObject<nsISupports>(obj); 171 nsWrapperCache* cache; 172 CallQueryInterface(native, &cache); 173 cache->PreserveWrapper(native); 174 175 if (expandoAndGeneration) { 176 expandoAndGeneration->expando.setObject(*expando); 177 return expando; 178 } 179 180 js::SetProxyPrivate(obj, ObjectValue(*expando)); 181 182 return expando; 183 } 184 185 bool DOMProxyHandler::preventExtensions(JSContext* cx, 186 JS::Handle<JSObject*> proxy, 187 JS::ObjectOpResult& result) const { 188 // always extensible per WebIDL 189 return result.failCantPreventExtensions(); 190 } 191 192 bool DOMProxyHandler::isExtensible(JSContext* cx, JS::Handle<JSObject*> proxy, 193 bool* extensible) const { 194 *extensible = true; 195 return true; 196 } 197 198 bool BaseDOMProxyHandler::getOwnPropertyDescriptor( 199 JSContext* cx, Handle<JSObject*> proxy, Handle<jsid> id, 200 MutableHandle<Maybe<PropertyDescriptor>> desc) const { 201 return getOwnPropDescriptor(cx, proxy, id, /* ignoreNamedProps = */ false, 202 desc); 203 } 204 205 bool DOMProxyHandler::defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy, 206 JS::Handle<jsid> id, 207 Handle<PropertyDescriptor> desc, 208 JS::ObjectOpResult& result, 209 bool* done) const { 210 if (xpc::WrapperFactory::IsXrayWrapper(proxy)) { 211 return result.succeed(); 212 } 213 214 JS::Rooted<JSObject*> expando(cx, EnsureExpandoObject(cx, proxy)); 215 if (!expando) { 216 return false; 217 } 218 219 if (!JS_DefinePropertyById(cx, expando, id, desc, result)) { 220 return false; 221 } 222 *done = true; 223 return true; 224 } 225 226 bool DOMProxyHandler::set(JSContext* cx, Handle<JSObject*> proxy, 227 Handle<jsid> id, Handle<JS::Value> v, 228 Handle<JS::Value> receiver, 229 ObjectOpResult& result) const { 230 MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), 231 "Should not have a XrayWrapper here"); 232 bool done; 233 if (!setCustom(cx, proxy, id, v, &done)) { 234 return false; 235 } 236 if (done) { 237 return result.succeed(); 238 } 239 240 // Make sure to ignore our named properties when checking for own 241 // property descriptors for a set. 242 Rooted<Maybe<PropertyDescriptor>> ownDesc(cx); 243 if (!getOwnPropDescriptor(cx, proxy, id, /* ignoreNamedProps = */ true, 244 &ownDesc)) { 245 return false; 246 } 247 248 return js::SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, ownDesc, 249 result); 250 } 251 252 bool DOMProxyHandler::delete_(JSContext* cx, JS::Handle<JSObject*> proxy, 253 JS::Handle<jsid> id, 254 JS::ObjectOpResult& result) const { 255 JS::Rooted<JSObject*> expando(cx); 256 if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && 257 (expando = GetExpandoObject(proxy))) { 258 return JS_DeletePropertyById(cx, expando, id, result); 259 } 260 261 return result.succeed(); 262 } 263 264 bool BaseDOMProxyHandler::ownPropertyKeys( 265 JSContext* cx, JS::Handle<JSObject*> proxy, 266 JS::MutableHandleVector<jsid> props) const { 267 return ownPropNames(cx, proxy, 268 JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props); 269 } 270 271 bool BaseDOMProxyHandler::getPrototypeIfOrdinary( 272 JSContext* cx, JS::Handle<JSObject*> proxy, bool* isOrdinary, 273 JS::MutableHandle<JSObject*> proto) const { 274 *isOrdinary = true; 275 proto.set(GetStaticPrototype(proxy)); 276 return true; 277 } 278 279 bool BaseDOMProxyHandler::getOwnEnumerablePropertyKeys( 280 JSContext* cx, JS::Handle<JSObject*> proxy, 281 JS::MutableHandleVector<jsid> props) const { 282 return ownPropNames(cx, proxy, JSITER_OWNONLY, props); 283 } 284 285 bool DOMProxyHandler::setCustom(JSContext* cx, JS::Handle<JSObject*> proxy, 286 JS::Handle<jsid> id, JS::Handle<JS::Value> v, 287 bool* done) const { 288 *done = false; 289 return true; 290 } 291 292 // static 293 JSObject* DOMProxyHandler::GetExpandoObject(JSObject* obj) { 294 CheckDOMProxy(obj); 295 296 JS::Value v = js::GetProxyPrivate(obj); 297 if (v.isObject()) { 298 CheckExpandoObject(obj, v); 299 return &v.toObject(); 300 } 301 302 if (v.isUndefined()) { 303 return nullptr; 304 } 305 306 auto* expandoAndGeneration = 307 static_cast<JS::ExpandoAndGeneration*>(v.toPrivate()); 308 CheckExpandoAndGeneration(obj, expandoAndGeneration); 309 310 v = expandoAndGeneration->expando; 311 return v.isUndefined() ? nullptr : &v.toObject(); 312 } 313 314 void ShadowingDOMProxyHandler::trace(JSTracer* trc, JSObject* proxy) const { 315 DOMProxyHandler::trace(trc, proxy); 316 317 MOZ_ASSERT(IsDOMProxy(proxy), "expected a DOM proxy object"); 318 JS::Value v = js::GetProxyPrivate(proxy); 319 MOZ_ASSERT(!v.isObject(), "Should not have expando object directly!"); 320 321 // The proxy's private slot is set when we allocate the proxy, 322 // so it cannot be |undefined|. 323 MOZ_ASSERT(!v.isUndefined()); 324 325 auto* expandoAndGeneration = 326 static_cast<JS::ExpandoAndGeneration*>(v.toPrivate()); 327 JS::TraceEdge(trc, &expandoAndGeneration->expando, 328 "Shadowing DOM proxy expando"); 329 } 330 331 } // namespace mozilla::dom