|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
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 |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "builtin/Object.h" |
|
8 |
|
9 #include "mozilla/ArrayUtils.h" |
|
10 |
|
11 #include "jscntxt.h" |
|
12 |
|
13 #include "frontend/BytecodeCompiler.h" |
|
14 #include "vm/StringBuffer.h" |
|
15 |
|
16 #include "jsobjinlines.h" |
|
17 |
|
18 #include "vm/ObjectImpl-inl.h" |
|
19 |
|
20 using namespace js; |
|
21 using namespace js::types; |
|
22 |
|
23 using js::frontend::IsIdentifier; |
|
24 using mozilla::ArrayLength; |
|
25 |
|
26 |
|
27 bool |
|
28 js::obj_construct(JSContext *cx, unsigned argc, Value *vp) |
|
29 { |
|
30 CallArgs args = CallArgsFromVp(argc, vp); |
|
31 |
|
32 RootedObject obj(cx, nullptr); |
|
33 if (args.length() > 0 && !args[0].isNullOrUndefined()) { |
|
34 obj = ToObject(cx, args[0]); |
|
35 if (!obj) |
|
36 return false; |
|
37 } else { |
|
38 /* Make an object whether this was called with 'new' or not. */ |
|
39 if (!NewObjectScriptedCall(cx, &obj)) |
|
40 return false; |
|
41 } |
|
42 |
|
43 args.rval().setObject(*obj); |
|
44 return true; |
|
45 } |
|
46 |
|
47 /* ES5 15.2.4.7. */ |
|
48 static bool |
|
49 obj_propertyIsEnumerable(JSContext *cx, unsigned argc, Value *vp) |
|
50 { |
|
51 CallArgs args = CallArgsFromVp(argc, vp); |
|
52 |
|
53 /* Step 1. */ |
|
54 RootedId id(cx); |
|
55 if (!ValueToId<CanGC>(cx, args.get(0), &id)) |
|
56 return false; |
|
57 |
|
58 /* Step 2. */ |
|
59 RootedObject obj(cx, ToObject(cx, args.thisv())); |
|
60 if (!obj) |
|
61 return false; |
|
62 |
|
63 /* Steps 3. */ |
|
64 RootedObject pobj(cx); |
|
65 RootedShape prop(cx); |
|
66 if (!JSObject::lookupGeneric(cx, obj, id, &pobj, &prop)) |
|
67 return false; |
|
68 |
|
69 /* Step 4. */ |
|
70 if (!prop) { |
|
71 args.rval().setBoolean(false); |
|
72 return true; |
|
73 } |
|
74 |
|
75 if (pobj != obj) { |
|
76 args.rval().setBoolean(false); |
|
77 return true; |
|
78 } |
|
79 |
|
80 /* Step 5. */ |
|
81 unsigned attrs; |
|
82 if (!JSObject::getGenericAttributes(cx, pobj, id, &attrs)) |
|
83 return false; |
|
84 |
|
85 args.rval().setBoolean((attrs & JSPROP_ENUMERATE) != 0); |
|
86 return true; |
|
87 } |
|
88 |
|
89 #if JS_HAS_TOSOURCE |
|
90 static bool |
|
91 obj_toSource(JSContext *cx, unsigned argc, Value *vp) |
|
92 { |
|
93 CallArgs args = CallArgsFromVp(argc, vp); |
|
94 JS_CHECK_RECURSION(cx, return false); |
|
95 |
|
96 RootedObject obj(cx, ToObject(cx, args.thisv())); |
|
97 if (!obj) |
|
98 return false; |
|
99 |
|
100 JSString *str = ObjectToSource(cx, obj); |
|
101 if (!str) |
|
102 return false; |
|
103 |
|
104 args.rval().setString(str); |
|
105 return true; |
|
106 } |
|
107 |
|
108 JSString * |
|
109 js::ObjectToSource(JSContext *cx, HandleObject obj) |
|
110 { |
|
111 /* If outermost, we need parentheses to be an expression, not a block. */ |
|
112 bool outermost = (cx->cycleDetectorSet.count() == 0); |
|
113 |
|
114 AutoCycleDetector detector(cx, obj); |
|
115 if (!detector.init()) |
|
116 return nullptr; |
|
117 if (detector.foundCycle()) |
|
118 return js_NewStringCopyZ<CanGC>(cx, "{}"); |
|
119 |
|
120 StringBuffer buf(cx); |
|
121 if (outermost && !buf.append('(')) |
|
122 return nullptr; |
|
123 if (!buf.append('{')) |
|
124 return nullptr; |
|
125 |
|
126 RootedValue v0(cx), v1(cx); |
|
127 MutableHandleValue val[2] = {&v0, &v1}; |
|
128 |
|
129 RootedString str0(cx), str1(cx); |
|
130 MutableHandleString gsop[2] = {&str0, &str1}; |
|
131 |
|
132 AutoIdVector idv(cx); |
|
133 if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &idv)) |
|
134 return nullptr; |
|
135 |
|
136 bool comma = false; |
|
137 for (size_t i = 0; i < idv.length(); ++i) { |
|
138 RootedId id(cx, idv[i]); |
|
139 RootedObject obj2(cx); |
|
140 RootedShape shape(cx); |
|
141 if (!JSObject::lookupGeneric(cx, obj, id, &obj2, &shape)) |
|
142 return nullptr; |
|
143 |
|
144 /* Decide early whether we prefer get/set or old getter/setter syntax. */ |
|
145 int valcnt = 0; |
|
146 if (shape) { |
|
147 bool doGet = true; |
|
148 if (obj2->isNative() && !IsImplicitDenseOrTypedArrayElement(shape)) { |
|
149 unsigned attrs = shape->attributes(); |
|
150 if (attrs & JSPROP_GETTER) { |
|
151 doGet = false; |
|
152 val[valcnt].set(shape->getterValue()); |
|
153 gsop[valcnt].set(cx->names().get); |
|
154 valcnt++; |
|
155 } |
|
156 if (attrs & JSPROP_SETTER) { |
|
157 doGet = false; |
|
158 val[valcnt].set(shape->setterValue()); |
|
159 gsop[valcnt].set(cx->names().set); |
|
160 valcnt++; |
|
161 } |
|
162 } |
|
163 if (doGet) { |
|
164 valcnt = 1; |
|
165 gsop[0].set(nullptr); |
|
166 if (!JSObject::getGeneric(cx, obj, obj, id, val[0])) |
|
167 return nullptr; |
|
168 } |
|
169 } |
|
170 |
|
171 /* Convert id to a linear string. */ |
|
172 RootedValue idv(cx, IdToValue(id)); |
|
173 JSString *s = ToString<CanGC>(cx, idv); |
|
174 if (!s) |
|
175 return nullptr; |
|
176 Rooted<JSLinearString*> idstr(cx, s->ensureLinear(cx)); |
|
177 if (!idstr) |
|
178 return nullptr; |
|
179 |
|
180 /* |
|
181 * If id is a string that's not an identifier, or if it's a negative |
|
182 * integer, then it must be quoted. |
|
183 */ |
|
184 if (JSID_IS_ATOM(id) |
|
185 ? !IsIdentifier(idstr) |
|
186 : (!JSID_IS_INT(id) || JSID_TO_INT(id) < 0)) |
|
187 { |
|
188 s = js_QuoteString(cx, idstr, jschar('\'')); |
|
189 if (!s || !(idstr = s->ensureLinear(cx))) |
|
190 return nullptr; |
|
191 } |
|
192 |
|
193 for (int j = 0; j < valcnt; j++) { |
|
194 /* |
|
195 * Censor an accessor descriptor getter or setter part if it's |
|
196 * undefined. |
|
197 */ |
|
198 if (gsop[j] && val[j].isUndefined()) |
|
199 continue; |
|
200 |
|
201 /* Convert val[j] to its canonical source form. */ |
|
202 RootedString valstr(cx, ValueToSource(cx, val[j])); |
|
203 if (!valstr) |
|
204 return nullptr; |
|
205 const jschar *vchars = valstr->getChars(cx); |
|
206 if (!vchars) |
|
207 return nullptr; |
|
208 size_t vlength = valstr->length(); |
|
209 |
|
210 /* |
|
211 * Remove '(function ' from the beginning of valstr and ')' from the |
|
212 * end so that we can put "get" in front of the function definition. |
|
213 */ |
|
214 if (gsop[j] && IsFunctionObject(val[j])) { |
|
215 const jschar *start = vchars; |
|
216 const jschar *end = vchars + vlength; |
|
217 |
|
218 uint8_t parenChomp = 0; |
|
219 if (vchars[0] == '(') { |
|
220 vchars++; |
|
221 parenChomp = 1; |
|
222 } |
|
223 |
|
224 /* Try to jump "function" keyword. */ |
|
225 if (vchars) |
|
226 vchars = js_strchr_limit(vchars, ' ', end); |
|
227 |
|
228 /* |
|
229 * Jump over the function's name: it can't be encoded as part |
|
230 * of an ECMA getter or setter. |
|
231 */ |
|
232 if (vchars) |
|
233 vchars = js_strchr_limit(vchars, '(', end); |
|
234 |
|
235 if (vchars) { |
|
236 if (*vchars == ' ') |
|
237 vchars++; |
|
238 vlength = end - vchars - parenChomp; |
|
239 } else { |
|
240 gsop[j].set(nullptr); |
|
241 vchars = start; |
|
242 } |
|
243 } |
|
244 |
|
245 if (comma && !buf.append(", ")) |
|
246 return nullptr; |
|
247 comma = true; |
|
248 |
|
249 if (gsop[j]) |
|
250 if (!buf.append(gsop[j]) || !buf.append(' ')) |
|
251 return nullptr; |
|
252 |
|
253 if (!buf.append(idstr)) |
|
254 return nullptr; |
|
255 if (!buf.append(gsop[j] ? ' ' : ':')) |
|
256 return nullptr; |
|
257 |
|
258 if (!buf.append(vchars, vlength)) |
|
259 return nullptr; |
|
260 } |
|
261 } |
|
262 |
|
263 if (!buf.append('}')) |
|
264 return nullptr; |
|
265 if (outermost && !buf.append(')')) |
|
266 return nullptr; |
|
267 |
|
268 return buf.finishString(); |
|
269 } |
|
270 #endif /* JS_HAS_TOSOURCE */ |
|
271 |
|
272 JSString * |
|
273 JS_BasicObjectToString(JSContext *cx, HandleObject obj) |
|
274 { |
|
275 // Some classes are really common, don't allocate new strings for them. |
|
276 // The ordering below is based on the measurements in bug 966264. |
|
277 if (obj->is<JSObject>()) |
|
278 return cx->names().objectObject; |
|
279 if (obj->is<StringObject>()) |
|
280 return cx->names().objectString; |
|
281 if (obj->is<ArrayObject>()) |
|
282 return cx->names().objectArray; |
|
283 if (obj->is<JSFunction>()) |
|
284 return cx->names().objectFunction; |
|
285 if (obj->is<NumberObject>()) |
|
286 return cx->names().objectNumber; |
|
287 |
|
288 const char *className = JSObject::className(cx, obj); |
|
289 |
|
290 if (strcmp(className, "Window") == 0) |
|
291 return cx->names().objectWindow; |
|
292 |
|
293 StringBuffer sb(cx); |
|
294 if (!sb.append("[object ") || !sb.appendInflated(className, strlen(className)) || |
|
295 !sb.append("]")) |
|
296 { |
|
297 return nullptr; |
|
298 } |
|
299 return sb.finishString(); |
|
300 } |
|
301 |
|
302 /* ES5 15.2.4.2. Note steps 1 and 2 are errata. */ |
|
303 static bool |
|
304 obj_toString(JSContext *cx, unsigned argc, Value *vp) |
|
305 { |
|
306 CallArgs args = CallArgsFromVp(argc, vp); |
|
307 |
|
308 /* Step 1. */ |
|
309 if (args.thisv().isUndefined()) { |
|
310 args.rval().setString(cx->names().objectUndefined); |
|
311 return true; |
|
312 } |
|
313 |
|
314 /* Step 2. */ |
|
315 if (args.thisv().isNull()) { |
|
316 args.rval().setString(cx->names().objectNull); |
|
317 return true; |
|
318 } |
|
319 |
|
320 /* Step 3. */ |
|
321 RootedObject obj(cx, ToObject(cx, args.thisv())); |
|
322 if (!obj) |
|
323 return false; |
|
324 |
|
325 /* Steps 4-5. */ |
|
326 JSString *str = JS_BasicObjectToString(cx, obj); |
|
327 if (!str) |
|
328 return false; |
|
329 args.rval().setString(str); |
|
330 return true; |
|
331 } |
|
332 |
|
333 /* ES5 15.2.4.3. */ |
|
334 static bool |
|
335 obj_toLocaleString(JSContext *cx, unsigned argc, Value *vp) |
|
336 { |
|
337 JS_CHECK_RECURSION(cx, return false); |
|
338 |
|
339 CallArgs args = CallArgsFromVp(argc, vp); |
|
340 |
|
341 /* Step 1. */ |
|
342 RootedObject obj(cx, ToObject(cx, args.thisv())); |
|
343 if (!obj) |
|
344 return false; |
|
345 |
|
346 /* Steps 2-4. */ |
|
347 RootedId id(cx, NameToId(cx->names().toString)); |
|
348 return obj->callMethod(cx, id, 0, nullptr, args.rval()); |
|
349 } |
|
350 |
|
351 static bool |
|
352 obj_valueOf(JSContext *cx, unsigned argc, Value *vp) |
|
353 { |
|
354 CallArgs args = CallArgsFromVp(argc, vp); |
|
355 RootedObject obj(cx, ToObject(cx, args.thisv())); |
|
356 if (!obj) |
|
357 return false; |
|
358 args.rval().setObject(*obj); |
|
359 return true; |
|
360 } |
|
361 |
|
362 #if JS_OLD_GETTER_SETTER_METHODS |
|
363 |
|
364 enum DefineType { GetterAccessor, SetterAccessor }; |
|
365 |
|
366 template<DefineType Type> |
|
367 static bool |
|
368 DefineAccessor(JSContext *cx, unsigned argc, Value *vp) |
|
369 { |
|
370 CallArgs args = CallArgsFromVp(argc, vp); |
|
371 if (!BoxNonStrictThis(cx, args)) |
|
372 return false; |
|
373 |
|
374 if (args.length() < 2 || !js_IsCallable(args[1])) { |
|
375 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, |
|
376 JSMSG_BAD_GETTER_OR_SETTER, |
|
377 Type == GetterAccessor ? js_getter_str : js_setter_str); |
|
378 return false; |
|
379 } |
|
380 |
|
381 RootedId id(cx); |
|
382 if (!ValueToId<CanGC>(cx, args[0], &id)) |
|
383 return false; |
|
384 |
|
385 RootedObject descObj(cx, NewBuiltinClassInstance(cx, &JSObject::class_)); |
|
386 if (!descObj) |
|
387 return false; |
|
388 |
|
389 JSAtomState &names = cx->names(); |
|
390 RootedValue trueVal(cx, BooleanValue(true)); |
|
391 |
|
392 /* enumerable: true */ |
|
393 if (!JSObject::defineProperty(cx, descObj, names.enumerable, trueVal)) |
|
394 return false; |
|
395 |
|
396 /* configurable: true */ |
|
397 if (!JSObject::defineProperty(cx, descObj, names.configurable, trueVal)) |
|
398 return false; |
|
399 |
|
400 /* enumerable: true */ |
|
401 PropertyName *acc = (Type == GetterAccessor) ? names.get : names.set; |
|
402 RootedValue accessorVal(cx, args[1]); |
|
403 if (!JSObject::defineProperty(cx, descObj, acc, accessorVal)) |
|
404 return false; |
|
405 |
|
406 RootedObject thisObj(cx, &args.thisv().toObject()); |
|
407 |
|
408 bool dummy; |
|
409 RootedValue descObjValue(cx, ObjectValue(*descObj)); |
|
410 if (!DefineOwnProperty(cx, thisObj, id, descObjValue, &dummy)) |
|
411 return false; |
|
412 |
|
413 args.rval().setUndefined(); |
|
414 return true; |
|
415 } |
|
416 |
|
417 JS_FRIEND_API(bool) |
|
418 js::obj_defineGetter(JSContext *cx, unsigned argc, Value *vp) |
|
419 { |
|
420 return DefineAccessor<GetterAccessor>(cx, argc, vp); |
|
421 } |
|
422 |
|
423 JS_FRIEND_API(bool) |
|
424 js::obj_defineSetter(JSContext *cx, unsigned argc, Value *vp) |
|
425 { |
|
426 return DefineAccessor<SetterAccessor>(cx, argc, vp); |
|
427 } |
|
428 |
|
429 static bool |
|
430 obj_lookupGetter(JSContext *cx, unsigned argc, Value *vp) |
|
431 { |
|
432 CallArgs args = CallArgsFromVp(argc, vp); |
|
433 |
|
434 RootedId id(cx); |
|
435 if (!ValueToId<CanGC>(cx, args.get(0), &id)) |
|
436 return false; |
|
437 RootedObject obj(cx, ToObject(cx, args.thisv())); |
|
438 if (!obj) |
|
439 return false; |
|
440 if (obj->is<ProxyObject>()) { |
|
441 // The vanilla getter lookup code below requires that the object is |
|
442 // native. Handle proxies separately. |
|
443 args.rval().setUndefined(); |
|
444 Rooted<PropertyDescriptor> desc(cx); |
|
445 if (!Proxy::getPropertyDescriptor(cx, obj, id, &desc)) |
|
446 return false; |
|
447 if (desc.object() && desc.hasGetterObject() && desc.getterObject()) |
|
448 args.rval().setObject(*desc.getterObject()); |
|
449 return true; |
|
450 } |
|
451 RootedObject pobj(cx); |
|
452 RootedShape shape(cx); |
|
453 if (!JSObject::lookupGeneric(cx, obj, id, &pobj, &shape)) |
|
454 return false; |
|
455 args.rval().setUndefined(); |
|
456 if (shape) { |
|
457 if (pobj->isNative() && !IsImplicitDenseOrTypedArrayElement(shape)) { |
|
458 if (shape->hasGetterValue()) |
|
459 args.rval().set(shape->getterValue()); |
|
460 } |
|
461 } |
|
462 return true; |
|
463 } |
|
464 |
|
465 static bool |
|
466 obj_lookupSetter(JSContext *cx, unsigned argc, Value *vp) |
|
467 { |
|
468 CallArgs args = CallArgsFromVp(argc, vp); |
|
469 |
|
470 RootedId id(cx); |
|
471 if (!ValueToId<CanGC>(cx, args.get(0), &id)) |
|
472 return false; |
|
473 RootedObject obj(cx, ToObject(cx, args.thisv())); |
|
474 if (!obj) |
|
475 return false; |
|
476 if (obj->is<ProxyObject>()) { |
|
477 // The vanilla setter lookup code below requires that the object is |
|
478 // native. Handle proxies separately. |
|
479 args.rval().setUndefined(); |
|
480 Rooted<PropertyDescriptor> desc(cx); |
|
481 if (!Proxy::getPropertyDescriptor(cx, obj, id, &desc)) |
|
482 return false; |
|
483 if (desc.object() && desc.hasSetterObject() && desc.setterObject()) |
|
484 args.rval().setObject(*desc.setterObject()); |
|
485 return true; |
|
486 } |
|
487 RootedObject pobj(cx); |
|
488 RootedShape shape(cx); |
|
489 if (!JSObject::lookupGeneric(cx, obj, id, &pobj, &shape)) |
|
490 return false; |
|
491 args.rval().setUndefined(); |
|
492 if (shape) { |
|
493 if (pobj->isNative() && !IsImplicitDenseOrTypedArrayElement(shape)) { |
|
494 if (shape->hasSetterValue()) |
|
495 args.rval().set(shape->setterValue()); |
|
496 } |
|
497 } |
|
498 return true; |
|
499 } |
|
500 #endif /* JS_OLD_GETTER_SETTER_METHODS */ |
|
501 |
|
502 /* ES5 15.2.3.2. */ |
|
503 static bool |
|
504 obj_getPrototypeOf(JSContext *cx, unsigned argc, Value *vp) |
|
505 { |
|
506 CallArgs args = CallArgsFromVp(argc, vp); |
|
507 |
|
508 /* Step 1. */ |
|
509 if (args.length() == 0) { |
|
510 js_ReportMissingArg(cx, args.calleev(), 0); |
|
511 return false; |
|
512 } |
|
513 |
|
514 if (args[0].isPrimitive()) { |
|
515 RootedValue val(cx, args[0]); |
|
516 char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, val, NullPtr()); |
|
517 if (!bytes) |
|
518 return false; |
|
519 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, |
|
520 JSMSG_UNEXPECTED_TYPE, bytes, "not an object"); |
|
521 js_free(bytes); |
|
522 return false; |
|
523 } |
|
524 |
|
525 /* Step 2. */ |
|
526 |
|
527 /* |
|
528 * Implement [[Prototype]]-getting -- particularly across compartment |
|
529 * boundaries -- by calling a cached __proto__ getter function. |
|
530 */ |
|
531 InvokeArgs args2(cx); |
|
532 if (!args2.init(0)) |
|
533 return false; |
|
534 args2.setCallee(cx->global()->protoGetter()); |
|
535 args2.setThis(args[0]); |
|
536 if (!Invoke(cx, args2)) |
|
537 return false; |
|
538 args.rval().set(args2.rval()); |
|
539 return true; |
|
540 } |
|
541 |
|
542 static bool |
|
543 obj_setPrototypeOf(JSContext *cx, unsigned argc, Value *vp) |
|
544 { |
|
545 CallArgs args = CallArgsFromVp(argc, vp); |
|
546 |
|
547 RootedObject setPrototypeOf(cx, &args.callee()); |
|
548 if (!GlobalObject::warnOnceAboutPrototypeMutation(cx, setPrototypeOf)) |
|
549 return false; |
|
550 |
|
551 if (args.length() < 2) { |
|
552 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, |
|
553 "Object.setPrototypeOf", "1", ""); |
|
554 return false; |
|
555 } |
|
556 |
|
557 /* Step 1-2. */ |
|
558 if (args[0].isNullOrUndefined()) { |
|
559 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, |
|
560 args[0].isNull() ? "null" : "undefined", "object"); |
|
561 return false; |
|
562 } |
|
563 |
|
564 /* Step 3. */ |
|
565 if (!args[1].isObjectOrNull()) { |
|
566 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, |
|
567 "Object.setPrototypeOf", "an object or null", InformalValueTypeName(args[1])); |
|
568 return false; |
|
569 } |
|
570 |
|
571 /* Step 4. */ |
|
572 if (!args[0].isObject()) { |
|
573 args.rval().set(args[0]); |
|
574 return true; |
|
575 } |
|
576 |
|
577 /* Step 5-6. */ |
|
578 RootedObject obj(cx, &args[0].toObject()); |
|
579 RootedObject newProto(cx, args[1].toObjectOrNull()); |
|
580 |
|
581 bool success; |
|
582 if (!JSObject::setProto(cx, obj, newProto, &success)) |
|
583 return false; |
|
584 |
|
585 /* Step 7. */ |
|
586 if (!success) { |
|
587 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_OBJECT_NOT_EXTENSIBLE, "object"); |
|
588 return false; |
|
589 } |
|
590 |
|
591 /* Step 8. */ |
|
592 args.rval().set(args[0]); |
|
593 return true; |
|
594 } |
|
595 |
|
596 #if JS_HAS_OBJ_WATCHPOINT |
|
597 |
|
598 bool |
|
599 js::WatchHandler(JSContext *cx, JSObject *obj_, jsid id_, JS::Value old, |
|
600 JS::Value *nvp, void *closure) |
|
601 { |
|
602 RootedObject obj(cx, obj_); |
|
603 RootedId id(cx, id_); |
|
604 |
|
605 /* Avoid recursion on (obj, id) already being watched on cx. */ |
|
606 AutoResolving resolving(cx, obj, id, AutoResolving::WATCH); |
|
607 if (resolving.alreadyStarted()) |
|
608 return true; |
|
609 |
|
610 JSObject *callable = (JSObject *)closure; |
|
611 Value argv[] = { IdToValue(id), old, *nvp }; |
|
612 RootedValue rv(cx); |
|
613 if (!Invoke(cx, ObjectValue(*obj), ObjectOrNullValue(callable), ArrayLength(argv), argv, &rv)) |
|
614 return false; |
|
615 |
|
616 *nvp = rv; |
|
617 return true; |
|
618 } |
|
619 |
|
620 static bool |
|
621 obj_watch(JSContext *cx, unsigned argc, Value *vp) |
|
622 { |
|
623 CallArgs args = CallArgsFromVp(argc, vp); |
|
624 |
|
625 RootedObject obj(cx, ToObject(cx, args.thisv())); |
|
626 if (!obj) |
|
627 return false; |
|
628 |
|
629 if (!GlobalObject::warnOnceAboutWatch(cx, obj)) |
|
630 return false; |
|
631 |
|
632 if (args.length() <= 1) { |
|
633 js_ReportMissingArg(cx, args.calleev(), 1); |
|
634 return false; |
|
635 } |
|
636 |
|
637 RootedObject callable(cx, ValueToCallable(cx, args[1], args.length() - 2)); |
|
638 if (!callable) |
|
639 return false; |
|
640 |
|
641 RootedId propid(cx); |
|
642 if (!ValueToId<CanGC>(cx, args[0], &propid)) |
|
643 return false; |
|
644 |
|
645 if (!JSObject::watch(cx, obj, propid, callable)) |
|
646 return false; |
|
647 |
|
648 args.rval().setUndefined(); |
|
649 return true; |
|
650 } |
|
651 |
|
652 static bool |
|
653 obj_unwatch(JSContext *cx, unsigned argc, Value *vp) |
|
654 { |
|
655 CallArgs args = CallArgsFromVp(argc, vp); |
|
656 |
|
657 RootedObject obj(cx, ToObject(cx, args.thisv())); |
|
658 if (!obj) |
|
659 return false; |
|
660 |
|
661 if (!GlobalObject::warnOnceAboutWatch(cx, obj)) |
|
662 return false; |
|
663 |
|
664 RootedId id(cx); |
|
665 if (args.length() != 0) { |
|
666 if (!ValueToId<CanGC>(cx, args[0], &id)) |
|
667 return false; |
|
668 } else { |
|
669 id = JSID_VOID; |
|
670 } |
|
671 |
|
672 if (!JSObject::unwatch(cx, obj, id)) |
|
673 return false; |
|
674 |
|
675 args.rval().setUndefined(); |
|
676 return true; |
|
677 } |
|
678 |
|
679 #endif /* JS_HAS_OBJ_WATCHPOINT */ |
|
680 |
|
681 /* ECMA 15.2.4.5. */ |
|
682 static bool |
|
683 obj_hasOwnProperty(JSContext *cx, unsigned argc, Value *vp) |
|
684 { |
|
685 CallArgs args = CallArgsFromVp(argc, vp); |
|
686 |
|
687 HandleValue idValue = args.get(0); |
|
688 |
|
689 /* Step 1, 2. */ |
|
690 jsid id; |
|
691 if (args.thisv().isObject() && ValueToId<NoGC>(cx, idValue, &id)) { |
|
692 JSObject *obj = &args.thisv().toObject(), *obj2; |
|
693 Shape *prop; |
|
694 if (!obj->is<ProxyObject>() && |
|
695 HasOwnProperty<NoGC>(cx, obj->getOps()->lookupGeneric, obj, id, &obj2, &prop)) |
|
696 { |
|
697 args.rval().setBoolean(!!prop); |
|
698 return true; |
|
699 } |
|
700 } |
|
701 |
|
702 /* Step 1. */ |
|
703 RootedId idRoot(cx); |
|
704 if (!ValueToId<CanGC>(cx, idValue, &idRoot)) |
|
705 return false; |
|
706 |
|
707 /* Step 2. */ |
|
708 RootedObject obj(cx, ToObject(cx, args.thisv())); |
|
709 if (!obj) |
|
710 return false; |
|
711 |
|
712 /* Non-standard code for proxies. */ |
|
713 if (obj->is<ProxyObject>()) { |
|
714 bool has; |
|
715 if (!Proxy::hasOwn(cx, obj, idRoot, &has)) |
|
716 return false; |
|
717 args.rval().setBoolean(has); |
|
718 return true; |
|
719 } |
|
720 |
|
721 /* Step 3. */ |
|
722 bool found; |
|
723 if (!HasOwnProperty(cx, obj, idRoot, &found)) |
|
724 return false; |
|
725 |
|
726 /* Step 4,5. */ |
|
727 args.rval().setBoolean(found); |
|
728 return true; |
|
729 } |
|
730 |
|
731 /* ES5 15.2.4.6. */ |
|
732 static bool |
|
733 obj_isPrototypeOf(JSContext *cx, unsigned argc, Value *vp) |
|
734 { |
|
735 CallArgs args = CallArgsFromVp(argc, vp); |
|
736 |
|
737 /* Step 1. */ |
|
738 if (args.length() < 1 || !args[0].isObject()) { |
|
739 args.rval().setBoolean(false); |
|
740 return true; |
|
741 } |
|
742 |
|
743 /* Step 2. */ |
|
744 RootedObject obj(cx, ToObject(cx, args.thisv())); |
|
745 if (!obj) |
|
746 return false; |
|
747 |
|
748 /* Step 3. */ |
|
749 bool isDelegate; |
|
750 if (!IsDelegate(cx, obj, args[0], &isDelegate)) |
|
751 return false; |
|
752 args.rval().setBoolean(isDelegate); |
|
753 return true; |
|
754 } |
|
755 |
|
756 /* ES5 15.2.3.5: Object.create(O [, Properties]) */ |
|
757 static bool |
|
758 obj_create(JSContext *cx, unsigned argc, Value *vp) |
|
759 { |
|
760 CallArgs args = CallArgsFromVp(argc, vp); |
|
761 if (args.length() == 0) { |
|
762 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, |
|
763 "Object.create", "0", "s"); |
|
764 return false; |
|
765 } |
|
766 |
|
767 RootedValue v(cx, args[0]); |
|
768 if (!v.isObjectOrNull()) { |
|
769 char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, NullPtr()); |
|
770 if (!bytes) |
|
771 return false; |
|
772 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, |
|
773 bytes, "not an object or null"); |
|
774 js_free(bytes); |
|
775 return false; |
|
776 } |
|
777 |
|
778 RootedObject proto(cx, v.toObjectOrNull()); |
|
779 |
|
780 /* |
|
781 * Use the callee's global as the parent of the new object to avoid dynamic |
|
782 * scoping (i.e., using the caller's global). |
|
783 */ |
|
784 RootedObject obj(cx, NewObjectWithGivenProto(cx, &JSObject::class_, proto, &args.callee().global())); |
|
785 if (!obj) |
|
786 return false; |
|
787 |
|
788 /* 15.2.3.5 step 4. */ |
|
789 if (args.hasDefined(1)) { |
|
790 if (args[1].isPrimitive()) { |
|
791 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); |
|
792 return false; |
|
793 } |
|
794 |
|
795 RootedObject props(cx, &args[1].toObject()); |
|
796 if (!DefineProperties(cx, obj, props)) |
|
797 return false; |
|
798 } |
|
799 |
|
800 /* 5. Return obj. */ |
|
801 args.rval().setObject(*obj); |
|
802 return true; |
|
803 } |
|
804 |
|
805 static bool |
|
806 obj_getOwnPropertyDescriptor(JSContext *cx, unsigned argc, Value *vp) |
|
807 { |
|
808 CallArgs args = CallArgsFromVp(argc, vp); |
|
809 RootedObject obj(cx); |
|
810 if (!GetFirstArgumentAsObject(cx, args, "Object.getOwnPropertyDescriptor", &obj)) |
|
811 return false; |
|
812 RootedId id(cx); |
|
813 if (!ValueToId<CanGC>(cx, args.get(1), &id)) |
|
814 return false; |
|
815 return GetOwnPropertyDescriptor(cx, obj, id, args.rval()); |
|
816 } |
|
817 |
|
818 static bool |
|
819 obj_keys(JSContext *cx, unsigned argc, Value *vp) |
|
820 { |
|
821 CallArgs args = CallArgsFromVp(argc, vp); |
|
822 RootedObject obj(cx); |
|
823 if (!GetFirstArgumentAsObject(cx, args, "Object.keys", &obj)) |
|
824 return false; |
|
825 |
|
826 AutoIdVector props(cx); |
|
827 if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &props)) |
|
828 return false; |
|
829 |
|
830 AutoValueVector vals(cx); |
|
831 if (!vals.reserve(props.length())) |
|
832 return false; |
|
833 for (size_t i = 0, len = props.length(); i < len; i++) { |
|
834 jsid id = props[i]; |
|
835 if (JSID_IS_STRING(id)) { |
|
836 vals.infallibleAppend(StringValue(JSID_TO_STRING(id))); |
|
837 } else if (JSID_IS_INT(id)) { |
|
838 JSString *str = Int32ToString<CanGC>(cx, JSID_TO_INT(id)); |
|
839 if (!str) |
|
840 return false; |
|
841 vals.infallibleAppend(StringValue(str)); |
|
842 } else { |
|
843 JS_ASSERT(JSID_IS_OBJECT(id)); |
|
844 } |
|
845 } |
|
846 |
|
847 JS_ASSERT(props.length() <= UINT32_MAX); |
|
848 JSObject *aobj = NewDenseCopiedArray(cx, uint32_t(vals.length()), vals.begin()); |
|
849 if (!aobj) |
|
850 return false; |
|
851 |
|
852 args.rval().setObject(*aobj); |
|
853 return true; |
|
854 } |
|
855 |
|
856 /* ES6 draft 15.2.3.16 */ |
|
857 static bool |
|
858 obj_is(JSContext *cx, unsigned argc, Value *vp) |
|
859 { |
|
860 CallArgs args = CallArgsFromVp(argc, vp); |
|
861 |
|
862 bool same; |
|
863 if (!SameValue(cx, args.get(0), args.get(1), &same)) |
|
864 return false; |
|
865 |
|
866 args.rval().setBoolean(same); |
|
867 return true; |
|
868 } |
|
869 |
|
870 static bool |
|
871 obj_getOwnPropertyNames(JSContext *cx, unsigned argc, Value *vp) |
|
872 { |
|
873 CallArgs args = CallArgsFromVp(argc, vp); |
|
874 RootedObject obj(cx); |
|
875 if (!GetFirstArgumentAsObject(cx, args, "Object.getOwnPropertyNames", &obj)) |
|
876 return false; |
|
877 |
|
878 AutoIdVector keys(cx); |
|
879 if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &keys)) |
|
880 return false; |
|
881 |
|
882 AutoValueVector vals(cx); |
|
883 if (!vals.resize(keys.length())) |
|
884 return false; |
|
885 |
|
886 for (size_t i = 0, len = keys.length(); i < len; i++) { |
|
887 jsid id = keys[i]; |
|
888 if (JSID_IS_INT(id)) { |
|
889 JSString *str = Int32ToString<CanGC>(cx, JSID_TO_INT(id)); |
|
890 if (!str) |
|
891 return false; |
|
892 vals[i].setString(str); |
|
893 } else if (JSID_IS_ATOM(id)) { |
|
894 vals[i].setString(JSID_TO_STRING(id)); |
|
895 } else { |
|
896 vals[i].setObject(*JSID_TO_OBJECT(id)); |
|
897 } |
|
898 } |
|
899 |
|
900 JSObject *aobj = NewDenseCopiedArray(cx, vals.length(), vals.begin()); |
|
901 if (!aobj) |
|
902 return false; |
|
903 |
|
904 args.rval().setObject(*aobj); |
|
905 return true; |
|
906 } |
|
907 |
|
908 /* ES5 15.2.3.6: Object.defineProperty(O, P, Attributes) */ |
|
909 static bool |
|
910 obj_defineProperty(JSContext *cx, unsigned argc, Value *vp) |
|
911 { |
|
912 CallArgs args = CallArgsFromVp(argc, vp); |
|
913 RootedObject obj(cx); |
|
914 if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperty", &obj)) |
|
915 return false; |
|
916 |
|
917 RootedId id(cx); |
|
918 if (!ValueToId<CanGC>(cx, args.get(1), &id)) |
|
919 return false; |
|
920 |
|
921 bool junk; |
|
922 if (!DefineOwnProperty(cx, obj, id, args.get(2), &junk)) |
|
923 return false; |
|
924 |
|
925 args.rval().setObject(*obj); |
|
926 return true; |
|
927 } |
|
928 |
|
929 /* ES5 15.2.3.7: Object.defineProperties(O, Properties) */ |
|
930 static bool |
|
931 obj_defineProperties(JSContext *cx, unsigned argc, Value *vp) |
|
932 { |
|
933 CallArgs args = CallArgsFromVp(argc, vp); |
|
934 |
|
935 /* Steps 1 and 7. */ |
|
936 RootedObject obj(cx); |
|
937 if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperties", &obj)) |
|
938 return false; |
|
939 args.rval().setObject(*obj); |
|
940 |
|
941 /* Step 2. */ |
|
942 if (args.length() < 2) { |
|
943 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, |
|
944 "Object.defineProperties", "0", "s"); |
|
945 return false; |
|
946 } |
|
947 RootedValue val(cx, args[1]); |
|
948 RootedObject props(cx, ToObject(cx, val)); |
|
949 if (!props) |
|
950 return false; |
|
951 |
|
952 /* Steps 3-6. */ |
|
953 return DefineProperties(cx, obj, props); |
|
954 } |
|
955 |
|
956 static bool |
|
957 obj_isExtensible(JSContext *cx, unsigned argc, Value *vp) |
|
958 { |
|
959 CallArgs args = CallArgsFromVp(argc, vp); |
|
960 RootedObject obj(cx); |
|
961 if (!GetFirstArgumentAsObject(cx, args, "Object.isExtensible", &obj)) |
|
962 return false; |
|
963 |
|
964 bool extensible; |
|
965 if (!JSObject::isExtensible(cx, obj, &extensible)) |
|
966 return false; |
|
967 args.rval().setBoolean(extensible); |
|
968 return true; |
|
969 } |
|
970 |
|
971 static bool |
|
972 obj_preventExtensions(JSContext *cx, unsigned argc, Value *vp) |
|
973 { |
|
974 CallArgs args = CallArgsFromVp(argc, vp); |
|
975 RootedObject obj(cx); |
|
976 if (!GetFirstArgumentAsObject(cx, args, "Object.preventExtensions", &obj)) |
|
977 return false; |
|
978 |
|
979 args.rval().setObject(*obj); |
|
980 |
|
981 bool extensible; |
|
982 if (!JSObject::isExtensible(cx, obj, &extensible)) |
|
983 return false; |
|
984 if (!extensible) |
|
985 return true; |
|
986 |
|
987 return JSObject::preventExtensions(cx, obj); |
|
988 } |
|
989 |
|
990 static bool |
|
991 obj_freeze(JSContext *cx, unsigned argc, Value *vp) |
|
992 { |
|
993 CallArgs args = CallArgsFromVp(argc, vp); |
|
994 RootedObject obj(cx); |
|
995 if (!GetFirstArgumentAsObject(cx, args, "Object.freeze", &obj)) |
|
996 return false; |
|
997 |
|
998 args.rval().setObject(*obj); |
|
999 |
|
1000 return JSObject::freeze(cx, obj); |
|
1001 } |
|
1002 |
|
1003 static bool |
|
1004 obj_isFrozen(JSContext *cx, unsigned argc, Value *vp) |
|
1005 { |
|
1006 CallArgs args = CallArgsFromVp(argc, vp); |
|
1007 RootedObject obj(cx); |
|
1008 if (!GetFirstArgumentAsObject(cx, args, "Object.preventExtensions", &obj)) |
|
1009 return false; |
|
1010 |
|
1011 bool frozen; |
|
1012 if (!JSObject::isFrozen(cx, obj, &frozen)) |
|
1013 return false; |
|
1014 args.rval().setBoolean(frozen); |
|
1015 return true; |
|
1016 } |
|
1017 |
|
1018 static bool |
|
1019 obj_seal(JSContext *cx, unsigned argc, Value *vp) |
|
1020 { |
|
1021 CallArgs args = CallArgsFromVp(argc, vp); |
|
1022 RootedObject obj(cx); |
|
1023 if (!GetFirstArgumentAsObject(cx, args, "Object.seal", &obj)) |
|
1024 return false; |
|
1025 |
|
1026 args.rval().setObject(*obj); |
|
1027 |
|
1028 return JSObject::seal(cx, obj); |
|
1029 } |
|
1030 |
|
1031 static bool |
|
1032 obj_isSealed(JSContext *cx, unsigned argc, Value *vp) |
|
1033 { |
|
1034 CallArgs args = CallArgsFromVp(argc, vp); |
|
1035 RootedObject obj(cx); |
|
1036 if (!GetFirstArgumentAsObject(cx, args, "Object.isSealed", &obj)) |
|
1037 return false; |
|
1038 |
|
1039 bool sealed; |
|
1040 if (!JSObject::isSealed(cx, obj, &sealed)) |
|
1041 return false; |
|
1042 args.rval().setBoolean(sealed); |
|
1043 return true; |
|
1044 } |
|
1045 |
|
1046 const JSFunctionSpec js::object_methods[] = { |
|
1047 #if JS_HAS_TOSOURCE |
|
1048 JS_FN(js_toSource_str, obj_toSource, 0,0), |
|
1049 #endif |
|
1050 JS_FN(js_toString_str, obj_toString, 0,0), |
|
1051 JS_FN(js_toLocaleString_str, obj_toLocaleString, 0,0), |
|
1052 JS_FN(js_valueOf_str, obj_valueOf, 0,0), |
|
1053 #if JS_HAS_OBJ_WATCHPOINT |
|
1054 JS_FN(js_watch_str, obj_watch, 2,0), |
|
1055 JS_FN(js_unwatch_str, obj_unwatch, 1,0), |
|
1056 #endif |
|
1057 JS_FN(js_hasOwnProperty_str, obj_hasOwnProperty, 1,0), |
|
1058 JS_FN(js_isPrototypeOf_str, obj_isPrototypeOf, 1,0), |
|
1059 JS_FN(js_propertyIsEnumerable_str, obj_propertyIsEnumerable, 1,0), |
|
1060 #if JS_OLD_GETTER_SETTER_METHODS |
|
1061 JS_FN(js_defineGetter_str, js::obj_defineGetter, 2,0), |
|
1062 JS_FN(js_defineSetter_str, js::obj_defineSetter, 2,0), |
|
1063 JS_FN(js_lookupGetter_str, obj_lookupGetter, 1,0), |
|
1064 JS_FN(js_lookupSetter_str, obj_lookupSetter, 1,0), |
|
1065 #endif |
|
1066 JS_FS_END |
|
1067 }; |
|
1068 |
|
1069 const JSFunctionSpec js::object_static_methods[] = { |
|
1070 JS_FN("getPrototypeOf", obj_getPrototypeOf, 1,0), |
|
1071 JS_FN("setPrototypeOf", obj_setPrototypeOf, 2,0), |
|
1072 JS_FN("getOwnPropertyDescriptor", obj_getOwnPropertyDescriptor,2,0), |
|
1073 JS_FN("keys", obj_keys, 1,0), |
|
1074 JS_FN("is", obj_is, 2,0), |
|
1075 JS_FN("defineProperty", obj_defineProperty, 3,0), |
|
1076 JS_FN("defineProperties", obj_defineProperties, 2,0), |
|
1077 JS_FN("create", obj_create, 2,0), |
|
1078 JS_FN("getOwnPropertyNames", obj_getOwnPropertyNames, 1,0), |
|
1079 JS_FN("isExtensible", obj_isExtensible, 1,0), |
|
1080 JS_FN("preventExtensions", obj_preventExtensions, 1,0), |
|
1081 JS_FN("freeze", obj_freeze, 1,0), |
|
1082 JS_FN("isFrozen", obj_isFrozen, 1,0), |
|
1083 JS_FN("seal", obj_seal, 1,0), |
|
1084 JS_FN("isSealed", obj_isSealed, 1,0), |
|
1085 JS_FS_END |
|
1086 }; |