tor-browser

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

ObservableArrayProxyHandler.cpp (12203B)


      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/ObservableArrayProxyHandler.h"
      8 
      9 #include "js/Conversions.h"
     10 #include "js/Object.h"
     11 #include "js/friend/ErrorMessages.h"
     12 #include "jsapi.h"
     13 #include "mozilla/ErrorResult.h"
     14 #include "mozilla/dom/JSSlots.h"
     15 #include "mozilla/dom/ProxyHandlerUtils.h"
     16 #include "mozilla/dom/ToJSValue.h"
     17 #include "nsDebug.h"
     18 #include "nsJSUtils.h"
     19 
     20 namespace mozilla::dom {
     21 
     22 const char ObservableArrayProxyHandler::family = 0;
     23 
     24 bool ObservableArrayProxyHandler::defineProperty(
     25    JSContext* aCx, JS::Handle<JSObject*> aProxy,
     26    JS::Handle<JS::PropertyKey> aId, JS::Handle<JS::PropertyDescriptor> aDesc,
     27    JS::ObjectOpResult& aResult) const {
     28  if (aId.get() == s_length_id) {
     29    if (aDesc.isAccessorDescriptor()) {
     30      return aResult.failNotDataDescriptor();
     31    }
     32    if (aDesc.hasConfigurable() && aDesc.configurable()) {
     33      return aResult.failInvalidDescriptor();
     34    }
     35    if (aDesc.hasEnumerable() && aDesc.enumerable()) {
     36      return aResult.failInvalidDescriptor();
     37    }
     38    if (aDesc.hasWritable() && !aDesc.writable()) {
     39      return aResult.failInvalidDescriptor();
     40    }
     41    if (aDesc.hasValue()) {
     42      JS::Rooted<JSObject*> backingListObj(aCx);
     43      if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
     44        return false;
     45      }
     46 
     47      return SetLength(aCx, aProxy, backingListObj, aDesc.value(), aResult);
     48    }
     49    return aResult.succeed();
     50  }
     51  uint32_t index = GetArrayIndexFromId(aId);
     52  if (IsArrayIndex(index)) {
     53    if (aDesc.isAccessorDescriptor()) {
     54      return aResult.failNotDataDescriptor();
     55    }
     56    if (aDesc.hasConfigurable() && !aDesc.configurable()) {
     57      return aResult.failInvalidDescriptor();
     58    }
     59    if (aDesc.hasEnumerable() && !aDesc.enumerable()) {
     60      return aResult.failInvalidDescriptor();
     61    }
     62    if (aDesc.hasWritable() && !aDesc.writable()) {
     63      return aResult.failInvalidDescriptor();
     64    }
     65    if (aDesc.hasValue()) {
     66      JS::Rooted<JSObject*> backingListObj(aCx);
     67      if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
     68        return false;
     69      }
     70 
     71      return SetIndexedValue(aCx, aProxy, backingListObj, index, aDesc.value(),
     72                             aResult);
     73    }
     74    return aResult.succeed();
     75  }
     76 
     77  return ForwardingProxyHandler::defineProperty(aCx, aProxy, aId, aDesc,
     78                                                aResult);
     79 }
     80 
     81 bool ObservableArrayProxyHandler::delete_(JSContext* aCx,
     82                                          JS::Handle<JSObject*> aProxy,
     83                                          JS::Handle<JS::PropertyKey> aId,
     84                                          JS::ObjectOpResult& aResult) const {
     85  if (aId.get() == s_length_id) {
     86    return aResult.failCantDelete();
     87  }
     88  uint32_t index = GetArrayIndexFromId(aId);
     89  if (IsArrayIndex(index)) {
     90    JS::Rooted<JSObject*> backingListObj(aCx);
     91    if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
     92      return false;
     93    }
     94 
     95    uint32_t oldLen = 0;
     96    if (!JS::GetArrayLength(aCx, backingListObj, &oldLen)) {
     97      return false;
     98    }
     99 
    100    // We do not follow the spec (step 3.3 in
    101    // https://webidl.spec.whatwg.org/#es-observable-array-deleteProperty)
    102    // is because `oldLen - 1` could be `-1` if the backing list is empty, but
    103    // `oldLen` is `uint32_t` in practice. See also
    104    // https://github.com/whatwg/webidl/issues/1049.
    105    if (oldLen != index + 1) {
    106      return aResult.failBadIndex();
    107    }
    108 
    109    JS::Rooted<JS::Value> value(aCx);
    110    if (!JS_GetElement(aCx, backingListObj, index, &value)) {
    111      return false;
    112    }
    113 
    114    if (!OnDeleteItem(aCx, aProxy, value, index)) {
    115      return false;
    116    }
    117 
    118    if (!JS::SetArrayLength(aCx, backingListObj, index)) {
    119      return false;
    120    }
    121 
    122    return aResult.succeed();
    123  }
    124  return ForwardingProxyHandler::delete_(aCx, aProxy, aId, aResult);
    125 }
    126 
    127 bool ObservableArrayProxyHandler::get(JSContext* aCx,
    128                                      JS::Handle<JSObject*> aProxy,
    129                                      JS::Handle<JS::Value> aReceiver,
    130                                      JS::Handle<JS::PropertyKey> aId,
    131                                      JS::MutableHandle<JS::Value> aVp) const {
    132  JS::Rooted<JSObject*> backingListObj(aCx);
    133  if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
    134    return false;
    135  }
    136 
    137  uint32_t length = 0;
    138  if (!JS::GetArrayLength(aCx, backingListObj, &length)) {
    139    return false;
    140  }
    141 
    142  if (aId.get() == s_length_id) {
    143    return ToJSValue(aCx, length, aVp);
    144  }
    145  uint32_t index = GetArrayIndexFromId(aId);
    146  if (IsArrayIndex(index)) {
    147    if (index >= length) {
    148      aVp.setUndefined();
    149      return true;
    150    }
    151    return JS_GetElement(aCx, backingListObj, index, aVp);
    152  }
    153  return ForwardingProxyHandler::get(aCx, aProxy, aReceiver, aId, aVp);
    154 }
    155 
    156 bool ObservableArrayProxyHandler::getOwnPropertyDescriptor(
    157    JSContext* aCx, JS::Handle<JSObject*> aProxy,
    158    JS::Handle<JS::PropertyKey> aId,
    159    JS::MutableHandle<Maybe<JS::PropertyDescriptor>> aDesc) const {
    160  JS::Rooted<JSObject*> backingListObj(aCx);
    161  if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
    162    return false;
    163  }
    164 
    165  uint32_t length = 0;
    166  if (!JS::GetArrayLength(aCx, backingListObj, &length)) {
    167    return false;
    168  }
    169 
    170  if (aId.get() == s_length_id) {
    171    JS::Rooted<JS::Value> value(aCx, JS::NumberValue(length));
    172    aDesc.set(Some(JS::PropertyDescriptor::Data(
    173        value, {JS::PropertyAttribute::Writable})));
    174    return true;
    175  }
    176  uint32_t index = GetArrayIndexFromId(aId);
    177  if (IsArrayIndex(index)) {
    178    if (index >= length) {
    179      return true;
    180    }
    181 
    182    JS::Rooted<JS::Value> value(aCx);
    183    if (!JS_GetElement(aCx, backingListObj, index, &value)) {
    184      return false;
    185    }
    186 
    187    aDesc.set(Some(JS::PropertyDescriptor::Data(
    188        value,
    189        {JS::PropertyAttribute::Configurable, JS::PropertyAttribute::Writable,
    190         JS::PropertyAttribute::Enumerable})));
    191    return true;
    192  }
    193  return ForwardingProxyHandler::getOwnPropertyDescriptor(aCx, aProxy, aId,
    194                                                          aDesc);
    195 }
    196 
    197 bool ObservableArrayProxyHandler::has(JSContext* aCx,
    198                                      JS::Handle<JSObject*> aProxy,
    199                                      JS::Handle<JS::PropertyKey> aId,
    200                                      bool* aBp) const {
    201  if (aId.get() == s_length_id) {
    202    *aBp = true;
    203    return true;
    204  }
    205  uint32_t index = GetArrayIndexFromId(aId);
    206  if (IsArrayIndex(index)) {
    207    uint32_t length = 0;
    208    if (!GetBackingListLength(aCx, aProxy, &length)) {
    209      return false;
    210    }
    211 
    212    *aBp = (index < length);
    213    return true;
    214  }
    215  return ForwardingProxyHandler::has(aCx, aProxy, aId, aBp);
    216 }
    217 
    218 bool ObservableArrayProxyHandler::ownPropertyKeys(
    219    JSContext* aCx, JS::Handle<JSObject*> aProxy,
    220    JS::MutableHandleVector<jsid> aProps) const {
    221  uint32_t length = 0;
    222  if (!GetBackingListLength(aCx, aProxy, &length)) {
    223    return false;
    224  }
    225 
    226  for (int32_t i = 0; i < int32_t(length); i++) {
    227    if (!aProps.append(JS::PropertyKey::Int(i))) {
    228      return false;
    229    }
    230  }
    231  return ForwardingProxyHandler::ownPropertyKeys(aCx, aProxy, aProps);
    232 }
    233 
    234 bool ObservableArrayProxyHandler::preventExtensions(
    235    JSContext* aCx, JS::Handle<JSObject*> aProxy,
    236    JS::ObjectOpResult& aResult) const {
    237  return aResult.failCantPreventExtensions();
    238 }
    239 
    240 bool ObservableArrayProxyHandler::set(JSContext* aCx,
    241                                      JS::Handle<JSObject*> aProxy,
    242                                      JS::Handle<JS::PropertyKey> aId,
    243                                      JS::Handle<JS::Value> aV,
    244                                      JS::Handle<JS::Value> aReceiver,
    245                                      JS::ObjectOpResult& aResult) const {
    246  if (aId.get() == s_length_id) {
    247    JS::Rooted<JSObject*> backingListObj(aCx);
    248    if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
    249      return false;
    250    }
    251 
    252    return SetLength(aCx, aProxy, backingListObj, aV, aResult);
    253  }
    254  uint32_t index = GetArrayIndexFromId(aId);
    255  if (IsArrayIndex(index)) {
    256    JS::Rooted<JSObject*> backingListObj(aCx);
    257    if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
    258      return false;
    259    }
    260 
    261    return SetIndexedValue(aCx, aProxy, backingListObj, index, aV, aResult);
    262  }
    263  return ForwardingProxyHandler::set(aCx, aProxy, aId, aV, aReceiver, aResult);
    264 }
    265 
    266 bool ObservableArrayProxyHandler::GetBackingListObject(
    267    JSContext* aCx, JS::Handle<JSObject*> aProxy,
    268    JS::MutableHandle<JSObject*> aBackingListObject) const {
    269  // Retrieve the backing list object from the reserved slot on the proxy
    270  // object. If it doesn't exist yet, create it.
    271  JS::Rooted<JS::Value> slotValue(aCx);
    272  slotValue = js::GetProxyReservedSlot(
    273      aProxy, OBSERVABLE_ARRAY_BACKING_LIST_OBJECT_SLOT);
    274  if (slotValue.isUndefined()) {
    275    JS::Rooted<JSObject*> newBackingListObj(aCx);
    276    newBackingListObj.set(JS::NewArrayObject(aCx, 0));
    277    if (NS_WARN_IF(!newBackingListObj)) {
    278      return false;
    279    }
    280    slotValue = JS::ObjectValue(*newBackingListObj);
    281    js::SetProxyReservedSlot(aProxy, OBSERVABLE_ARRAY_BACKING_LIST_OBJECT_SLOT,
    282                             slotValue);
    283  }
    284  aBackingListObject.set(&slotValue.toObject());
    285  return true;
    286 }
    287 
    288 bool ObservableArrayProxyHandler::GetBackingListLength(
    289    JSContext* aCx, JS::Handle<JSObject*> aProxy, uint32_t* aLength) const {
    290  JS::Rooted<JSObject*> backingListObj(aCx);
    291  if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
    292    return false;
    293  }
    294 
    295  return JS::GetArrayLength(aCx, backingListObj, aLength);
    296 }
    297 
    298 bool ObservableArrayProxyHandler::SetLength(JSContext* aCx,
    299                                            JS::Handle<JSObject*> aProxy,
    300                                            uint32_t aLength) const {
    301  JS::Rooted<JSObject*> backingListObj(aCx);
    302  if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
    303    return false;
    304  }
    305 
    306  JS::ObjectOpResult result;
    307  if (!SetLength(aCx, aProxy, backingListObj, aLength, result)) {
    308    return false;
    309  }
    310 
    311  return result ? true : result.reportError(aCx, aProxy);
    312 }
    313 
    314 bool ObservableArrayProxyHandler::SetLength(JSContext* aCx,
    315                                            JS::Handle<JSObject*> aProxy,
    316                                            JS::Handle<JSObject*> aBackingList,
    317                                            uint32_t aLength,
    318                                            JS::ObjectOpResult& aResult) const {
    319  uint32_t oldLen;
    320  if (!JS::GetArrayLength(aCx, aBackingList, &oldLen)) {
    321    return false;
    322  }
    323 
    324  if (aLength > oldLen) {
    325    return aResult.failBadArrayLength();
    326  }
    327 
    328  bool ok = true;
    329  uint32_t len = oldLen;
    330  for (; len > aLength; len--) {
    331    uint32_t indexToDelete = len - 1;
    332    JS::Rooted<JS::Value> value(aCx);
    333    if (!JS_GetElement(aCx, aBackingList, indexToDelete, &value)) {
    334      ok = false;
    335      break;
    336    }
    337 
    338    if (!OnDeleteItem(aCx, aProxy, value, indexToDelete)) {
    339      ok = false;
    340      break;
    341    }
    342  }
    343 
    344  return JS::SetArrayLength(aCx, aBackingList, len) && ok ? aResult.succeed()
    345                                                          : false;
    346 }
    347 
    348 bool ObservableArrayProxyHandler::SetLength(JSContext* aCx,
    349                                            JS::Handle<JSObject*> aProxy,
    350                                            JS::Handle<JSObject*> aBackingList,
    351                                            JS::Handle<JS::Value> aValue,
    352                                            JS::ObjectOpResult& aResult) const {
    353  uint32_t uint32Len;
    354  if (!ToUint32(aCx, aValue, &uint32Len)) {
    355    return false;
    356  }
    357 
    358  double numberLen;
    359  if (!ToNumber(aCx, aValue, &numberLen)) {
    360    return false;
    361  }
    362 
    363  if (uint32Len != numberLen) {
    364    JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
    365                              JSMSG_BAD_INDEX);
    366    return false;
    367  }
    368 
    369  return SetLength(aCx, aProxy, aBackingList, uint32Len, aResult);
    370 }
    371 
    372 }  // namespace mozilla::dom