tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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