tor-browser

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

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