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/. */
7 #include "mozilla/dom/ObservableArrayProxyHandler.h"
10 #include "js/friend/ErrorMessages.h"
11 #include "js/Conversions.h"
12 #include "js/Object.h"
13 #include "mozilla/dom/JSSlots.h"
14 #include "mozilla/dom/ProxyHandlerUtils.h"
15 #include "mozilla/dom/ToJSValue.h"
16 #include "mozilla/ErrorResult.h"
18 #include "nsJSUtils.h"
20 namespace mozilla::dom
{
22 const char ObservableArrayProxyHandler::family
= 0;
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();
32 if (aDesc
.hasConfigurable() && aDesc
.configurable()) {
33 return aResult
.failInvalidDescriptor();
35 if (aDesc
.hasEnumerable() && aDesc
.enumerable()) {
36 return aResult
.failInvalidDescriptor();
38 if (aDesc
.hasWritable() && !aDesc
.writable()) {
39 return aResult
.failInvalidDescriptor();
41 if (aDesc
.hasValue()) {
42 JS::Rooted
<JSObject
*> backingListObj(aCx
);
43 if (!GetBackingListObject(aCx
, aProxy
, &backingListObj
)) {
47 return SetLength(aCx
, aProxy
, backingListObj
, aDesc
.value(), aResult
);
49 return aResult
.succeed();
51 uint32_t index
= GetArrayIndexFromId(aId
);
52 if (IsArrayIndex(index
)) {
53 if (aDesc
.isAccessorDescriptor()) {
54 return aResult
.failNotDataDescriptor();
56 if (aDesc
.hasConfigurable() && !aDesc
.configurable()) {
57 return aResult
.failInvalidDescriptor();
59 if (aDesc
.hasEnumerable() && !aDesc
.enumerable()) {
60 return aResult
.failInvalidDescriptor();
62 if (aDesc
.hasWritable() && !aDesc
.writable()) {
63 return aResult
.failInvalidDescriptor();
65 if (aDesc
.hasValue()) {
66 JS::Rooted
<JSObject
*> backingListObj(aCx
);
67 if (!GetBackingListObject(aCx
, aProxy
, &backingListObj
)) {
71 return SetIndexedValue(aCx
, aProxy
, backingListObj
, index
, aDesc
.value(),
74 return aResult
.succeed();
77 return ForwardingProxyHandler::defineProperty(aCx
, aProxy
, aId
, aDesc
,
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();
88 uint32_t index
= GetArrayIndexFromId(aId
);
89 if (IsArrayIndex(index
)) {
90 JS::Rooted
<JSObject
*> backingListObj(aCx
);
91 if (!GetBackingListObject(aCx
, aProxy
, &backingListObj
)) {
96 if (!JS::GetArrayLength(aCx
, backingListObj
, &oldLen
)) {
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();
109 JS::Rooted
<JS::Value
> value(aCx
);
110 if (!JS_GetElement(aCx
, backingListObj
, index
, &value
)) {
114 if (!OnDeleteItem(aCx
, aProxy
, value
, index
)) {
118 if (!JS::SetArrayLength(aCx
, backingListObj
, index
)) {
122 return aResult
.succeed();
124 return ForwardingProxyHandler::delete_(aCx
, aProxy
, aId
, aResult
);
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
)) {
138 if (!JS::GetArrayLength(aCx
, backingListObj
, &length
)) {
142 if (aId
.get() == s_length_id
) {
143 return ToJSValue(aCx
, length
, aVp
);
145 uint32_t index
= GetArrayIndexFromId(aId
);
146 if (IsArrayIndex(index
)) {
147 if (index
>= length
) {
151 return JS_GetElement(aCx
, backingListObj
, index
, aVp
);
153 return ForwardingProxyHandler::get(aCx
, aProxy
, aReceiver
, aId
, aVp
);
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
)) {
166 if (!JS::GetArrayLength(aCx
, backingListObj
, &length
)) {
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
})));
176 uint32_t index
= GetArrayIndexFromId(aId
);
177 if (IsArrayIndex(index
)) {
178 if (index
>= length
) {
182 JS::Rooted
<JS::Value
> value(aCx
);
183 if (!JS_GetElement(aCx
, backingListObj
, index
, &value
)) {
187 aDesc
.set(Some(JS::PropertyDescriptor::Data(
189 {JS::PropertyAttribute::Configurable
, JS::PropertyAttribute::Writable
,
190 JS::PropertyAttribute::Enumerable
})));
193 return ForwardingProxyHandler::getOwnPropertyDescriptor(aCx
, aProxy
, aId
,
197 bool ObservableArrayProxyHandler::has(JSContext
* aCx
,
198 JS::Handle
<JSObject
*> aProxy
,
199 JS::Handle
<JS::PropertyKey
> aId
,
201 if (aId
.get() == s_length_id
) {
205 uint32_t index
= GetArrayIndexFromId(aId
);
206 if (IsArrayIndex(index
)) {
208 if (!GetBackingListLength(aCx
, aProxy
, &length
)) {
212 *aBp
= (index
< length
);
215 return ForwardingProxyHandler::has(aCx
, aProxy
, aId
, aBp
);
218 bool ObservableArrayProxyHandler::ownPropertyKeys(
219 JSContext
* aCx
, JS::Handle
<JSObject
*> aProxy
,
220 JS::MutableHandleVector
<jsid
> aProps
) const {
222 if (!GetBackingListLength(aCx
, aProxy
, &length
)) {
226 for (int32_t i
= 0; i
< int32_t(length
); i
++) {
227 if (!aProps
.append(JS::PropertyKey::Int(i
))) {
231 return ForwardingProxyHandler::ownPropertyKeys(aCx
, aProxy
, aProps
);
234 bool ObservableArrayProxyHandler::preventExtensions(
235 JSContext
* aCx
, JS::Handle
<JSObject
*> aProxy
,
236 JS::ObjectOpResult
& aResult
) const {
237 return aResult
.failCantPreventExtensions();
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
)) {
252 return SetLength(aCx
, aProxy
, backingListObj
, aV
, aResult
);
254 uint32_t index
= GetArrayIndexFromId(aId
);
255 if (IsArrayIndex(index
)) {
256 JS::Rooted
<JSObject
*> backingListObj(aCx
);
257 if (!GetBackingListObject(aCx
, aProxy
, &backingListObj
)) {
261 return SetIndexedValue(aCx
, aProxy
, backingListObj
, index
, aV
, aResult
);
263 return ForwardingProxyHandler::set(aCx
, aProxy
, aId
, aV
, aReceiver
, aResult
);
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
)) {
280 slotValue
= JS::ObjectValue(*newBackingListObj
);
281 js::SetProxyReservedSlot(aProxy
, OBSERVABLE_ARRAY_BACKING_LIST_OBJECT_SLOT
,
284 aBackingListObject
.set(&slotValue
.toObject());
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
)) {
295 return JS::GetArrayLength(aCx
, backingListObj
, aLength
);
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
)) {
306 JS::ObjectOpResult result
;
307 if (!SetLength(aCx
, aProxy
, backingListObj
, aLength
, result
)) {
311 return result
? true : result
.reportError(aCx
, aProxy
);
314 bool ObservableArrayProxyHandler::SetLength(JSContext
* aCx
,
315 JS::Handle
<JSObject
*> aProxy
,
316 JS::Handle
<JSObject
*> aBackingList
,
318 JS::ObjectOpResult
& aResult
) const {
320 if (!JS::GetArrayLength(aCx
, aBackingList
, &oldLen
)) {
324 if (aLength
> oldLen
) {
325 return aResult
.failBadArrayLength();
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
)) {
338 if (!OnDeleteItem(aCx
, aProxy
, value
, indexToDelete
)) {
344 return JS::SetArrayLength(aCx
, aBackingList
, len
) && ok
? aResult
.succeed()
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 {
354 if (!ToUint32(aCx
, aValue
, &uint32Len
)) {
359 if (!ToNumber(aCx
, aValue
, &numberLen
)) {
363 if (uint32Len
!= numberLen
) {
364 JS_ReportErrorNumberASCII(aCx
, js::GetErrorMessage
, nullptr,
369 return SetLength(aCx
, aProxy
, aBackingList
, uint32Len
, aResult
);
372 } // namespace mozilla::dom