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