Bug 1842509 - Remove media.webvtt.regions.enabled pref r=alwu,webidl,smaug,peterv
[gecko.git] / dom / bindings / ObservableArrayProxyHandler.cpp
blob931950a492c3c4913d0c28adc30d5ab3956b5cb6
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"
9 #include "jsapi.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"
17 #include "nsDebug.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)) {
44 return false;
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)) {
68 return false;
71 return SetIndexedValue(aCx, aProxy, backingListObj, index, aDesc.value(),
72 aResult);
74 return aResult.succeed();
77 return ForwardingProxyHandler::defineProperty(aCx, aProxy, aId, aDesc,
78 aResult);
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)) {
92 return false;
95 uint32_t oldLen = 0;
96 if (!JS::GetArrayLength(aCx, backingListObj, &oldLen)) {
97 return false;
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)) {
111 return false;
114 if (!OnDeleteItem(aCx, aProxy, value, index)) {
115 return false;
118 if (!JS::SetArrayLength(aCx, backingListObj, index)) {
119 return false;
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)) {
134 return false;
137 uint32_t length = 0;
138 if (!JS::GetArrayLength(aCx, backingListObj, &length)) {
139 return false;
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) {
148 aVp.setUndefined();
149 return true;
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)) {
162 return false;
165 uint32_t length = 0;
166 if (!JS::GetArrayLength(aCx, backingListObj, &length)) {
167 return false;
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;
176 uint32_t index = GetArrayIndexFromId(aId);
177 if (IsArrayIndex(index)) {
178 if (index >= length) {
179 return true;
182 JS::Rooted<JS::Value> value(aCx);
183 if (!JS_GetElement(aCx, backingListObj, index, &value)) {
184 return false;
187 aDesc.set(Some(JS::PropertyDescriptor::Data(
188 value,
189 {JS::PropertyAttribute::Configurable, JS::PropertyAttribute::Writable,
190 JS::PropertyAttribute::Enumerable})));
191 return true;
193 return ForwardingProxyHandler::getOwnPropertyDescriptor(aCx, aProxy, aId,
194 aDesc);
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;
205 uint32_t index = GetArrayIndexFromId(aId);
206 if (IsArrayIndex(index)) {
207 uint32_t length = 0;
208 if (!GetBackingListLength(aCx, aProxy, &length)) {
209 return false;
212 *aBp = (index < length);
213 return true;
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 {
221 uint32_t length = 0;
222 if (!GetBackingListLength(aCx, aProxy, &length)) {
223 return false;
226 for (int32_t i = 0; i < int32_t(length); i++) {
227 if (!aProps.append(JS::PropertyKey::Int(i))) {
228 return false;
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)) {
249 return false;
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)) {
258 return false;
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)) {
278 return false;
280 slotValue = JS::ObjectValue(*newBackingListObj);
281 js::SetProxyReservedSlot(aProxy, OBSERVABLE_ARRAY_BACKING_LIST_OBJECT_SLOT,
282 slotValue);
284 aBackingListObject.set(&slotValue.toObject());
285 return true;
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;
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)) {
303 return false;
306 JS::ObjectOpResult result;
307 if (!SetLength(aCx, aProxy, backingListObj, aLength, result)) {
308 return false;
311 return result ? true : result.reportError(aCx, aProxy);
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;
324 if (aLength > oldLen) {
325 return aResult.failBadArrayLength();
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;
338 if (!OnDeleteItem(aCx, aProxy, value, indexToDelete)) {
339 ok = false;
340 break;
344 return JS::SetArrayLength(aCx, aBackingList, len) && ok ? aResult.succeed()
345 : false;
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;
358 double numberLen;
359 if (!ToNumber(aCx, aValue, &numberLen)) {
360 return false;
363 if (uint32Len != numberLen) {
364 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
365 JSMSG_BAD_INDEX);
366 return false;
369 return SetLength(aCx, aProxy, aBackingList, uint32Len, aResult);
372 } // namespace mozilla::dom