diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /dom/bindings/ObservableArrayProxyHandler.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream/115.8.0esr.tar.xz firefox-esr-upstream/115.8.0esr.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/bindings/ObservableArrayProxyHandler.cpp')
-rw-r--r-- | dom/bindings/ObservableArrayProxyHandler.cpp | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/dom/bindings/ObservableArrayProxyHandler.cpp b/dom/bindings/ObservableArrayProxyHandler.cpp new file mode 100644 index 0000000000..931950a492 --- /dev/null +++ b/dom/bindings/ObservableArrayProxyHandler.cpp @@ -0,0 +1,372 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/ObservableArrayProxyHandler.h" + +#include "jsapi.h" +#include "js/friend/ErrorMessages.h" +#include "js/Conversions.h" +#include "js/Object.h" +#include "mozilla/dom/JSSlots.h" +#include "mozilla/dom/ProxyHandlerUtils.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/ErrorResult.h" +#include "nsDebug.h" +#include "nsJSUtils.h" + +namespace mozilla::dom { + +const char ObservableArrayProxyHandler::family = 0; + +bool ObservableArrayProxyHandler::defineProperty( + JSContext* aCx, JS::Handle<JSObject*> aProxy, + JS::Handle<JS::PropertyKey> aId, JS::Handle<JS::PropertyDescriptor> aDesc, + JS::ObjectOpResult& aResult) const { + if (aId.get() == s_length_id) { + if (aDesc.isAccessorDescriptor()) { + return aResult.failNotDataDescriptor(); + } + if (aDesc.hasConfigurable() && aDesc.configurable()) { + return aResult.failInvalidDescriptor(); + } + if (aDesc.hasEnumerable() && aDesc.enumerable()) { + return aResult.failInvalidDescriptor(); + } + if (aDesc.hasWritable() && !aDesc.writable()) { + return aResult.failInvalidDescriptor(); + } + if (aDesc.hasValue()) { + JS::Rooted<JSObject*> backingListObj(aCx); + if (!GetBackingListObject(aCx, aProxy, &backingListObj)) { + return false; + } + + return SetLength(aCx, aProxy, backingListObj, aDesc.value(), aResult); + } + return aResult.succeed(); + } + uint32_t index = GetArrayIndexFromId(aId); + if (IsArrayIndex(index)) { + if (aDesc.isAccessorDescriptor()) { + return aResult.failNotDataDescriptor(); + } + if (aDesc.hasConfigurable() && !aDesc.configurable()) { + return aResult.failInvalidDescriptor(); + } + if (aDesc.hasEnumerable() && !aDesc.enumerable()) { + return aResult.failInvalidDescriptor(); + } + if (aDesc.hasWritable() && !aDesc.writable()) { + return aResult.failInvalidDescriptor(); + } + if (aDesc.hasValue()) { + JS::Rooted<JSObject*> backingListObj(aCx); + if (!GetBackingListObject(aCx, aProxy, &backingListObj)) { + return false; + } + + return SetIndexedValue(aCx, aProxy, backingListObj, index, aDesc.value(), + aResult); + } + return aResult.succeed(); + } + + return ForwardingProxyHandler::defineProperty(aCx, aProxy, aId, aDesc, + aResult); +} + +bool ObservableArrayProxyHandler::delete_(JSContext* aCx, + JS::Handle<JSObject*> aProxy, + JS::Handle<JS::PropertyKey> aId, + JS::ObjectOpResult& aResult) const { + if (aId.get() == s_length_id) { + return aResult.failCantDelete(); + } + uint32_t index = GetArrayIndexFromId(aId); + if (IsArrayIndex(index)) { + JS::Rooted<JSObject*> backingListObj(aCx); + if (!GetBackingListObject(aCx, aProxy, &backingListObj)) { + return false; + } + + uint32_t oldLen = 0; + if (!JS::GetArrayLength(aCx, backingListObj, &oldLen)) { + return false; + } + + // We do not follow the spec (step 3.3 in + // https://webidl.spec.whatwg.org/#es-observable-array-deleteProperty) + // is because `oldLen - 1` could be `-1` if the backing list is empty, but + // `oldLen` is `uint32_t` in practice. See also + // https://github.com/whatwg/webidl/issues/1049. + if (oldLen != index + 1) { + return aResult.failBadIndex(); + } + + JS::Rooted<JS::Value> value(aCx); + if (!JS_GetElement(aCx, backingListObj, index, &value)) { + return false; + } + + if (!OnDeleteItem(aCx, aProxy, value, index)) { + return false; + } + + if (!JS::SetArrayLength(aCx, backingListObj, index)) { + return false; + } + + return aResult.succeed(); + } + return ForwardingProxyHandler::delete_(aCx, aProxy, aId, aResult); +} + +bool ObservableArrayProxyHandler::get(JSContext* aCx, + JS::Handle<JSObject*> aProxy, + JS::Handle<JS::Value> aReceiver, + JS::Handle<JS::PropertyKey> aId, + JS::MutableHandle<JS::Value> aVp) const { + JS::Rooted<JSObject*> backingListObj(aCx); + if (!GetBackingListObject(aCx, aProxy, &backingListObj)) { + return false; + } + + uint32_t length = 0; + if (!JS::GetArrayLength(aCx, backingListObj, &length)) { + return false; + } + + if (aId.get() == s_length_id) { + return ToJSValue(aCx, length, aVp); + } + uint32_t index = GetArrayIndexFromId(aId); + if (IsArrayIndex(index)) { + if (index >= length) { + aVp.setUndefined(); + return true; + } + return JS_GetElement(aCx, backingListObj, index, aVp); + } + return ForwardingProxyHandler::get(aCx, aProxy, aReceiver, aId, aVp); +} + +bool ObservableArrayProxyHandler::getOwnPropertyDescriptor( + JSContext* aCx, JS::Handle<JSObject*> aProxy, + JS::Handle<JS::PropertyKey> aId, + JS::MutableHandle<Maybe<JS::PropertyDescriptor>> aDesc) const { + JS::Rooted<JSObject*> backingListObj(aCx); + if (!GetBackingListObject(aCx, aProxy, &backingListObj)) { + return false; + } + + uint32_t length = 0; + if (!JS::GetArrayLength(aCx, backingListObj, &length)) { + return false; + } + + if (aId.get() == s_length_id) { + JS::Rooted<JS::Value> value(aCx, JS::NumberValue(length)); + aDesc.set(Some(JS::PropertyDescriptor::Data( + value, {JS::PropertyAttribute::Writable}))); + return true; + } + uint32_t index = GetArrayIndexFromId(aId); + if (IsArrayIndex(index)) { + if (index >= length) { + return true; + } + + JS::Rooted<JS::Value> value(aCx); + if (!JS_GetElement(aCx, backingListObj, index, &value)) { + return false; + } + + aDesc.set(Some(JS::PropertyDescriptor::Data( + value, + {JS::PropertyAttribute::Configurable, JS::PropertyAttribute::Writable, + JS::PropertyAttribute::Enumerable}))); + return true; + } + return ForwardingProxyHandler::getOwnPropertyDescriptor(aCx, aProxy, aId, + aDesc); +} + +bool ObservableArrayProxyHandler::has(JSContext* aCx, + JS::Handle<JSObject*> aProxy, + JS::Handle<JS::PropertyKey> aId, + bool* aBp) const { + if (aId.get() == s_length_id) { + *aBp = true; + return true; + } + uint32_t index = GetArrayIndexFromId(aId); + if (IsArrayIndex(index)) { + uint32_t length = 0; + if (!GetBackingListLength(aCx, aProxy, &length)) { + return false; + } + + *aBp = (index < length); + return true; + } + return ForwardingProxyHandler::has(aCx, aProxy, aId, aBp); +} + +bool ObservableArrayProxyHandler::ownPropertyKeys( + JSContext* aCx, JS::Handle<JSObject*> aProxy, + JS::MutableHandleVector<jsid> aProps) const { + uint32_t length = 0; + if (!GetBackingListLength(aCx, aProxy, &length)) { + return false; + } + + for (int32_t i = 0; i < int32_t(length); i++) { + if (!aProps.append(JS::PropertyKey::Int(i))) { + return false; + } + } + return ForwardingProxyHandler::ownPropertyKeys(aCx, aProxy, aProps); +} + +bool ObservableArrayProxyHandler::preventExtensions( + JSContext* aCx, JS::Handle<JSObject*> aProxy, + JS::ObjectOpResult& aResult) const { + return aResult.failCantPreventExtensions(); +} + +bool ObservableArrayProxyHandler::set(JSContext* aCx, + JS::Handle<JSObject*> aProxy, + JS::Handle<JS::PropertyKey> aId, + JS::Handle<JS::Value> aV, + JS::Handle<JS::Value> aReceiver, + JS::ObjectOpResult& aResult) const { + if (aId.get() == s_length_id) { + JS::Rooted<JSObject*> backingListObj(aCx); + if (!GetBackingListObject(aCx, aProxy, &backingListObj)) { + return false; + } + + return SetLength(aCx, aProxy, backingListObj, aV, aResult); + } + uint32_t index = GetArrayIndexFromId(aId); + if (IsArrayIndex(index)) { + JS::Rooted<JSObject*> backingListObj(aCx); + if (!GetBackingListObject(aCx, aProxy, &backingListObj)) { + return false; + } + + return SetIndexedValue(aCx, aProxy, backingListObj, index, aV, aResult); + } + return ForwardingProxyHandler::set(aCx, aProxy, aId, aV, aReceiver, aResult); +} + +bool ObservableArrayProxyHandler::GetBackingListObject( + JSContext* aCx, JS::Handle<JSObject*> aProxy, + JS::MutableHandle<JSObject*> aBackingListObject) const { + // Retrieve the backing list object from the reserved slot on the proxy + // object. If it doesn't exist yet, create it. + JS::Rooted<JS::Value> slotValue(aCx); + slotValue = js::GetProxyReservedSlot( + aProxy, OBSERVABLE_ARRAY_BACKING_LIST_OBJECT_SLOT); + if (slotValue.isUndefined()) { + JS::Rooted<JSObject*> newBackingListObj(aCx); + newBackingListObj.set(JS::NewArrayObject(aCx, 0)); + if (NS_WARN_IF(!newBackingListObj)) { + return false; + } + slotValue = JS::ObjectValue(*newBackingListObj); + js::SetProxyReservedSlot(aProxy, OBSERVABLE_ARRAY_BACKING_LIST_OBJECT_SLOT, + slotValue); + } + aBackingListObject.set(&slotValue.toObject()); + return true; +} + +bool ObservableArrayProxyHandler::GetBackingListLength( + JSContext* aCx, JS::Handle<JSObject*> aProxy, uint32_t* aLength) const { + JS::Rooted<JSObject*> backingListObj(aCx); + if (!GetBackingListObject(aCx, aProxy, &backingListObj)) { + return false; + } + + return JS::GetArrayLength(aCx, backingListObj, aLength); +} + +bool ObservableArrayProxyHandler::SetLength(JSContext* aCx, + JS::Handle<JSObject*> aProxy, + uint32_t aLength) const { + JS::Rooted<JSObject*> backingListObj(aCx); + if (!GetBackingListObject(aCx, aProxy, &backingListObj)) { + return false; + } + + JS::ObjectOpResult result; + if (!SetLength(aCx, aProxy, backingListObj, aLength, result)) { + return false; + } + + return result ? true : result.reportError(aCx, aProxy); +} + +bool ObservableArrayProxyHandler::SetLength(JSContext* aCx, + JS::Handle<JSObject*> aProxy, + JS::Handle<JSObject*> aBackingList, + uint32_t aLength, + JS::ObjectOpResult& aResult) const { + uint32_t oldLen; + if (!JS::GetArrayLength(aCx, aBackingList, &oldLen)) { + return false; + } + + if (aLength > oldLen) { + return aResult.failBadArrayLength(); + } + + bool ok = true; + uint32_t len = oldLen; + for (; len > aLength; len--) { + uint32_t indexToDelete = len - 1; + JS::Rooted<JS::Value> value(aCx); + if (!JS_GetElement(aCx, aBackingList, indexToDelete, &value)) { + ok = false; + break; + } + + if (!OnDeleteItem(aCx, aProxy, value, indexToDelete)) { + ok = false; + break; + } + } + + return JS::SetArrayLength(aCx, aBackingList, len) && ok ? aResult.succeed() + : false; +} + +bool ObservableArrayProxyHandler::SetLength(JSContext* aCx, + JS::Handle<JSObject*> aProxy, + JS::Handle<JSObject*> aBackingList, + JS::Handle<JS::Value> aValue, + JS::ObjectOpResult& aResult) const { + uint32_t uint32Len; + if (!ToUint32(aCx, aValue, &uint32Len)) { + return false; + } + + double numberLen; + if (!ToNumber(aCx, aValue, &numberLen)) { + return false; + } + + if (uint32Len != numberLen) { + JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, + JSMSG_BAD_INDEX); + return false; + } + + return SetLength(aCx, aProxy, aBackingList, uint32Len, aResult); +} + +} // namespace mozilla::dom |