OLD | NEW |
| (Empty) |
1 // Copyright 2012 the V8 project authors. All rights reserved. | |
2 // Redistribution and use in source and binary forms, with or without | |
3 // modification, are permitted provided that the following conditions are | |
4 // met: | |
5 // | |
6 // * Redistributions of source code must retain the above copyright | |
7 // notice, this list of conditions and the following disclaimer. | |
8 // * Redistributions in binary form must reproduce the above | |
9 // copyright notice, this list of conditions and the following | |
10 // disclaimer in the documentation and/or other materials provided | |
11 // with the distribution. | |
12 // * Neither the name of Google Inc. nor the names of its | |
13 // contributors may be used to endorse or promote products derived | |
14 // from this software without specific prior written permission. | |
15 // | |
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
27 | |
28 // Flags: --harmony-object-observe | |
29 // Flags: --allow-natives-syntax | |
30 | |
31 var allObservers = []; | |
32 function reset() { | |
33 allObservers.forEach(function(observer) { observer.reset(); }); | |
34 } | |
35 | |
36 function stringifyNoThrow(arg) { | |
37 try { | |
38 return JSON.stringify(arg); | |
39 } catch (e) { | |
40 return '{<circular reference>}'; | |
41 } | |
42 } | |
43 | |
44 function createObserver() { | |
45 "use strict"; // So that |this| in callback can be undefined. | |
46 | |
47 var observer = { | |
48 records: undefined, | |
49 callbackCount: 0, | |
50 reset: function() { | |
51 this.records = undefined; | |
52 this.callbackCount = 0; | |
53 }, | |
54 assertNotCalled: function() { | |
55 assertEquals(undefined, this.records); | |
56 assertEquals(0, this.callbackCount); | |
57 }, | |
58 assertCalled: function() { | |
59 assertEquals(1, this.callbackCount); | |
60 }, | |
61 assertRecordCount: function(count) { | |
62 this.assertCalled(); | |
63 assertEquals(count, this.records.length); | |
64 }, | |
65 assertCallbackRecords: function(recs) { | |
66 this.assertRecordCount(recs.length); | |
67 for (var i = 0; i < recs.length; i++) { | |
68 if ('name' in recs[i]) recs[i].name = String(recs[i].name); | |
69 print(i, stringifyNoThrow(this.records[i]), stringifyNoThrow(recs[i])); | |
70 assertSame(this.records[i].object, recs[i].object); | |
71 assertEquals('string', typeof recs[i].type); | |
72 assertPropertiesEqual(this.records[i], recs[i]); | |
73 } | |
74 } | |
75 }; | |
76 | |
77 observer.callback = function(r) { | |
78 assertEquals(undefined, this); | |
79 assertEquals('object', typeof r); | |
80 assertTrue(r instanceof Array) | |
81 observer.records = r; | |
82 observer.callbackCount++; | |
83 }; | |
84 | |
85 observer.reset(); | |
86 allObservers.push(observer); | |
87 return observer; | |
88 } | |
89 | |
90 var observer = createObserver(); | |
91 var observer2 = createObserver(); | |
92 | |
93 assertEquals("function", typeof observer.callback); | |
94 assertEquals("function", typeof observer2.callback); | |
95 | |
96 var obj = {}; | |
97 | |
98 function frozenFunction() {} | |
99 Object.freeze(frozenFunction); | |
100 var nonFunction = {}; | |
101 var changeRecordWithAccessor = { type: 'foo' }; | |
102 var recordCreated = false; | |
103 Object.defineProperty(changeRecordWithAccessor, 'name', { | |
104 get: function() { | |
105 recordCreated = true; | |
106 return "bar"; | |
107 }, | |
108 enumerable: true | |
109 }) | |
110 | |
111 | |
112 // Object.observe | |
113 assertThrows(function() { Object.observe("non-object", observer.callback); }, | |
114 TypeError); | |
115 assertThrows(function() { Object.observe(this, observer.callback); }, | |
116 TypeError); | |
117 assertThrows(function() { Object.observe(obj, nonFunction); }, TypeError); | |
118 assertThrows(function() { Object.observe(obj, frozenFunction); }, TypeError); | |
119 assertEquals(obj, Object.observe(obj, observer.callback, [1])); | |
120 assertEquals(obj, Object.observe(obj, observer.callback, [true])); | |
121 assertEquals(obj, Object.observe(obj, observer.callback, ['foo', null])); | |
122 assertEquals(obj, Object.observe(obj, observer.callback, [undefined])); | |
123 assertEquals(obj, Object.observe(obj, observer.callback, | |
124 ['foo', 'bar', 'baz'])); | |
125 assertEquals(obj, Object.observe(obj, observer.callback, [])); | |
126 assertEquals(obj, Object.observe(obj, observer.callback, undefined)); | |
127 assertEquals(obj, Object.observe(obj, observer.callback)); | |
128 | |
129 // Object.unobserve | |
130 assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError); | |
131 assertThrows(function() { Object.unobserve(this, observer.callback); }, | |
132 TypeError); | |
133 assertThrows(function() { Object.unobserve(obj, nonFunction); }, TypeError); | |
134 assertEquals(obj, Object.unobserve(obj, observer.callback)); | |
135 | |
136 | |
137 // Object.getNotifier | |
138 var notifier = Object.getNotifier(obj); | |
139 assertSame(notifier, Object.getNotifier(obj)); | |
140 assertEquals(null, Object.getNotifier(Object.freeze({}))); | |
141 assertThrows(function() { Object.getNotifier(this) }, TypeError); | |
142 assertFalse(notifier.hasOwnProperty('notify')); | |
143 assertEquals([], Object.keys(notifier)); | |
144 var notifyDesc = Object.getOwnPropertyDescriptor(notifier.__proto__, 'notify'); | |
145 assertTrue(notifyDesc.configurable); | |
146 assertTrue(notifyDesc.writable); | |
147 assertFalse(notifyDesc.enumerable); | |
148 assertThrows(function() { notifier.notify({}); }, TypeError); | |
149 assertThrows(function() { notifier.notify({ type: 4 }); }, TypeError); | |
150 | |
151 assertThrows(function() { notifier.performChange(1, function(){}); }, TypeError)
; | |
152 assertThrows(function() { notifier.performChange(undefined, function(){}); }, Ty
peError); | |
153 assertThrows(function() { notifier.performChange('foo', undefined); }, TypeError
); | |
154 assertThrows(function() { notifier.performChange('foo', 'bar'); }, TypeError); | |
155 var global = this; | |
156 notifier.performChange('foo', function() { | |
157 assertEquals(global, this); | |
158 }); | |
159 | |
160 var notify = notifier.notify; | |
161 assertThrows(function() { notify.call(undefined, { type: 'a' }); }, TypeError); | |
162 assertThrows(function() { notify.call(null, { type: 'a' }); }, TypeError); | |
163 assertThrows(function() { notify.call(5, { type: 'a' }); }, TypeError); | |
164 assertThrows(function() { notify.call('hello', { type: 'a' }); }, TypeError); | |
165 assertThrows(function() { notify.call(false, { type: 'a' }); }, TypeError); | |
166 assertThrows(function() { notify.call({}, { type: 'a' }); }, TypeError); | |
167 assertFalse(recordCreated); | |
168 notifier.notify(changeRecordWithAccessor); | |
169 assertFalse(recordCreated); // not observed yet | |
170 | |
171 | |
172 // Object.deliverChangeRecords | |
173 assertThrows(function() { Object.deliverChangeRecords(nonFunction); }, TypeError
); | |
174 | |
175 Object.observe(obj, observer.callback); | |
176 | |
177 | |
178 // notify uses to [[CreateOwnProperty]] to create changeRecord; | |
179 reset(); | |
180 var protoExpandoAccessed = false; | |
181 Object.defineProperty(Object.prototype, 'protoExpando', | |
182 { | |
183 configurable: true, | |
184 set: function() { protoExpandoAccessed = true; } | |
185 } | |
186 ); | |
187 notifier.notify({ type: 'foo', protoExpando: 'val'}); | |
188 assertFalse(protoExpandoAccessed); | |
189 delete Object.prototype.protoExpando; | |
190 Object.deliverChangeRecords(observer.callback); | |
191 | |
192 | |
193 // Multiple records are delivered. | |
194 reset(); | |
195 notifier.notify({ | |
196 type: 'update', | |
197 name: 'foo', | |
198 expando: 1 | |
199 }); | |
200 | |
201 notifier.notify({ | |
202 object: notifier, // object property is ignored | |
203 type: 'delete', | |
204 name: 'bar', | |
205 expando2: 'str' | |
206 }); | |
207 Object.deliverChangeRecords(observer.callback); | |
208 observer.assertCallbackRecords([ | |
209 { object: obj, name: 'foo', type: 'update', expando: 1 }, | |
210 { object: obj, name: 'bar', type: 'delete', expando2: 'str' } | |
211 ]); | |
212 | |
213 // Non-string accept values are coerced to strings | |
214 reset(); | |
215 Object.observe(obj, observer.callback, [true, 1, null, undefined]); | |
216 notifier = Object.getNotifier(obj); | |
217 notifier.notify({ type: 'true' }); | |
218 notifier.notify({ type: 'false' }); | |
219 notifier.notify({ type: '1' }); | |
220 notifier.notify({ type: '-1' }); | |
221 notifier.notify({ type: 'null' }); | |
222 notifier.notify({ type: 'nill' }); | |
223 notifier.notify({ type: 'undefined' }); | |
224 notifier.notify({ type: 'defined' }); | |
225 Object.deliverChangeRecords(observer.callback); | |
226 observer.assertCallbackRecords([ | |
227 { object: obj, type: 'true' }, | |
228 { object: obj, type: '1' }, | |
229 { object: obj, type: 'null' }, | |
230 { object: obj, type: 'undefined' } | |
231 ]); | |
232 | |
233 // No delivery takes place if no records are pending | |
234 reset(); | |
235 Object.deliverChangeRecords(observer.callback); | |
236 observer.assertNotCalled(); | |
237 | |
238 | |
239 // Multiple observation has no effect. | |
240 reset(); | |
241 Object.observe(obj, observer.callback); | |
242 Object.observe(obj, observer.callback); | |
243 Object.getNotifier(obj).notify({ | |
244 type: 'update', | |
245 }); | |
246 Object.deliverChangeRecords(observer.callback); | |
247 observer.assertCalled(); | |
248 | |
249 | |
250 // Observation can be stopped. | |
251 reset(); | |
252 Object.unobserve(obj, observer.callback); | |
253 Object.getNotifier(obj).notify({ | |
254 type: 'update', | |
255 }); | |
256 Object.deliverChangeRecords(observer.callback); | |
257 observer.assertNotCalled(); | |
258 | |
259 | |
260 // Multiple unobservation has no effect | |
261 reset(); | |
262 Object.unobserve(obj, observer.callback); | |
263 Object.unobserve(obj, observer.callback); | |
264 Object.getNotifier(obj).notify({ | |
265 type: 'update', | |
266 }); | |
267 Object.deliverChangeRecords(observer.callback); | |
268 observer.assertNotCalled(); | |
269 | |
270 | |
271 // Re-observation works and only includes changeRecords after of call. | |
272 reset(); | |
273 Object.getNotifier(obj).notify({ | |
274 type: 'update', | |
275 }); | |
276 Object.observe(obj, observer.callback); | |
277 Object.getNotifier(obj).notify({ | |
278 type: 'update', | |
279 }); | |
280 records = undefined; | |
281 Object.deliverChangeRecords(observer.callback); | |
282 observer.assertRecordCount(1); | |
283 | |
284 // Get notifier prior to observing | |
285 reset(); | |
286 var obj = {}; | |
287 Object.getNotifier(obj); | |
288 Object.observe(obj, observer.callback); | |
289 obj.id = 1; | |
290 Object.deliverChangeRecords(observer.callback); | |
291 observer.assertCallbackRecords([ | |
292 { object: obj, type: 'add', name: 'id' }, | |
293 ]); | |
294 | |
295 // The empty-string property is observable | |
296 reset(); | |
297 var obj = {}; | |
298 Object.observe(obj, observer.callback); | |
299 obj[''] = ''; | |
300 obj[''] = ' '; | |
301 delete obj['']; | |
302 Object.deliverChangeRecords(observer.callback); | |
303 observer.assertCallbackRecords([ | |
304 { object: obj, type: 'add', name: '' }, | |
305 { object: obj, type: 'update', name: '', oldValue: '' }, | |
306 { object: obj, type: 'delete', name: '', oldValue: ' ' }, | |
307 ]); | |
308 | |
309 // Object.preventExtensions | |
310 reset(); | |
311 var obj = { foo: 'bar'}; | |
312 Object.observe(obj, observer.callback); | |
313 obj.baz = 'bat'; | |
314 Object.preventExtensions(obj); | |
315 | |
316 Object.deliverChangeRecords(observer.callback); | |
317 observer.assertCallbackRecords([ | |
318 { object: obj, type: 'add', name: 'baz' }, | |
319 { object: obj, type: 'preventExtensions' }, | |
320 ]); | |
321 | |
322 reset(); | |
323 var obj = { foo: 'bar'}; | |
324 Object.preventExtensions(obj); | |
325 Object.observe(obj, observer.callback); | |
326 Object.preventExtensions(obj); | |
327 Object.deliverChangeRecords(observer.callback); | |
328 observer.assertNotCalled(); | |
329 | |
330 // Object.freeze | |
331 reset(); | |
332 var obj = { a: 'a' }; | |
333 Object.defineProperty(obj, 'b', { | |
334 writable: false, | |
335 configurable: true, | |
336 value: 'b' | |
337 }); | |
338 Object.defineProperty(obj, 'c', { | |
339 writable: true, | |
340 configurable: false, | |
341 value: 'c' | |
342 }); | |
343 Object.defineProperty(obj, 'd', { | |
344 writable: false, | |
345 configurable: false, | |
346 value: 'd' | |
347 }); | |
348 Object.observe(obj, observer.callback); | |
349 Object.freeze(obj); | |
350 | |
351 Object.deliverChangeRecords(observer.callback); | |
352 observer.assertCallbackRecords([ | |
353 { object: obj, type: 'preventExtensions' }, | |
354 { object: obj, type: 'reconfigure', name: 'a' }, | |
355 { object: obj, type: 'reconfigure', name: 'b' }, | |
356 { object: obj, type: 'reconfigure', name: 'c' }, | |
357 ]); | |
358 | |
359 reset(); | |
360 var obj = { foo: 'bar'}; | |
361 Object.freeze(obj); | |
362 Object.observe(obj, observer.callback); | |
363 Object.freeze(obj); | |
364 Object.deliverChangeRecords(observer.callback); | |
365 observer.assertNotCalled(); | |
366 | |
367 // Object.seal | |
368 reset(); | |
369 var obj = { a: 'a' }; | |
370 Object.defineProperty(obj, 'b', { | |
371 writable: false, | |
372 configurable: true, | |
373 value: 'b' | |
374 }); | |
375 Object.defineProperty(obj, 'c', { | |
376 writable: true, | |
377 configurable: false, | |
378 value: 'c' | |
379 }); | |
380 Object.defineProperty(obj, 'd', { | |
381 writable: false, | |
382 configurable: false, | |
383 value: 'd' | |
384 }); | |
385 Object.observe(obj, observer.callback); | |
386 Object.seal(obj); | |
387 | |
388 Object.deliverChangeRecords(observer.callback); | |
389 observer.assertCallbackRecords([ | |
390 { object: obj, type: 'preventExtensions' }, | |
391 { object: obj, type: 'reconfigure', name: 'a' }, | |
392 { object: obj, type: 'reconfigure', name: 'b' }, | |
393 ]); | |
394 | |
395 reset(); | |
396 var obj = { foo: 'bar'}; | |
397 Object.seal(obj); | |
398 Object.observe(obj, observer.callback); | |
399 Object.seal(obj); | |
400 Object.deliverChangeRecords(observer.callback); | |
401 observer.assertNotCalled(); | |
402 | |
403 // Observing a continuous stream of changes, while itermittantly unobserving. | |
404 reset(); | |
405 var obj = {}; | |
406 Object.observe(obj, observer.callback); | |
407 Object.getNotifier(obj).notify({ | |
408 type: 'update', | |
409 val: 1 | |
410 }); | |
411 | |
412 Object.unobserve(obj, observer.callback); | |
413 Object.getNotifier(obj).notify({ | |
414 type: 'update', | |
415 val: 2 | |
416 }); | |
417 | |
418 Object.observe(obj, observer.callback); | |
419 Object.getNotifier(obj).notify({ | |
420 type: 'update', | |
421 val: 3 | |
422 }); | |
423 | |
424 Object.unobserve(obj, observer.callback); | |
425 Object.getNotifier(obj).notify({ | |
426 type: 'update', | |
427 val: 4 | |
428 }); | |
429 | |
430 Object.observe(obj, observer.callback); | |
431 Object.getNotifier(obj).notify({ | |
432 type: 'update', | |
433 val: 5 | |
434 }); | |
435 | |
436 Object.unobserve(obj, observer.callback); | |
437 Object.deliverChangeRecords(observer.callback); | |
438 observer.assertCallbackRecords([ | |
439 { object: obj, type: 'update', val: 1 }, | |
440 { object: obj, type: 'update', val: 3 }, | |
441 { object: obj, type: 'update', val: 5 } | |
442 ]); | |
443 | |
444 // Accept | |
445 reset(); | |
446 Object.observe(obj, observer.callback, ['somethingElse']); | |
447 Object.getNotifier(obj).notify({ | |
448 type: 'add' | |
449 }); | |
450 Object.getNotifier(obj).notify({ | |
451 type: 'update' | |
452 }); | |
453 Object.getNotifier(obj).notify({ | |
454 type: 'delete' | |
455 }); | |
456 Object.getNotifier(obj).notify({ | |
457 type: 'reconfigure' | |
458 }); | |
459 Object.getNotifier(obj).notify({ | |
460 type: 'setPrototype' | |
461 }); | |
462 Object.deliverChangeRecords(observer.callback); | |
463 observer.assertNotCalled(); | |
464 | |
465 reset(); | |
466 Object.observe(obj, observer.callback, ['add', 'delete', 'setPrototype']); | |
467 Object.getNotifier(obj).notify({ | |
468 type: 'add' | |
469 }); | |
470 Object.getNotifier(obj).notify({ | |
471 type: 'update' | |
472 }); | |
473 Object.getNotifier(obj).notify({ | |
474 type: 'delete' | |
475 }); | |
476 Object.getNotifier(obj).notify({ | |
477 type: 'delete' | |
478 }); | |
479 Object.getNotifier(obj).notify({ | |
480 type: 'reconfigure' | |
481 }); | |
482 Object.getNotifier(obj).notify({ | |
483 type: 'setPrototype' | |
484 }); | |
485 Object.deliverChangeRecords(observer.callback); | |
486 observer.assertCallbackRecords([ | |
487 { object: obj, type: 'add' }, | |
488 { object: obj, type: 'delete' }, | |
489 { object: obj, type: 'delete' }, | |
490 { object: obj, type: 'setPrototype' } | |
491 ]); | |
492 | |
493 reset(); | |
494 Object.observe(obj, observer.callback, ['update', 'foo']); | |
495 Object.getNotifier(obj).notify({ | |
496 type: 'add' | |
497 }); | |
498 Object.getNotifier(obj).notify({ | |
499 type: 'update' | |
500 }); | |
501 Object.getNotifier(obj).notify({ | |
502 type: 'delete' | |
503 }); | |
504 Object.getNotifier(obj).notify({ | |
505 type: 'foo' | |
506 }); | |
507 Object.getNotifier(obj).notify({ | |
508 type: 'bar' | |
509 }); | |
510 Object.getNotifier(obj).notify({ | |
511 type: 'foo' | |
512 }); | |
513 Object.deliverChangeRecords(observer.callback); | |
514 observer.assertCallbackRecords([ | |
515 { object: obj, type: 'update' }, | |
516 { object: obj, type: 'foo' }, | |
517 { object: obj, type: 'foo' } | |
518 ]); | |
519 | |
520 reset(); | |
521 function Thingy(a, b, c) { | |
522 this.a = a; | |
523 this.b = b; | |
524 } | |
525 | |
526 Thingy.MULTIPLY = 'multiply'; | |
527 Thingy.INCREMENT = 'increment'; | |
528 Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply'; | |
529 | |
530 Thingy.prototype = { | |
531 increment: function(amount) { | |
532 var notifier = Object.getNotifier(this); | |
533 | |
534 var self = this; | |
535 notifier.performChange(Thingy.INCREMENT, function() { | |
536 self.a += amount; | |
537 self.b += amount; | |
538 | |
539 return { | |
540 incremented: amount | |
541 }; // implicit notify | |
542 }); | |
543 }, | |
544 | |
545 multiply: function(amount) { | |
546 var notifier = Object.getNotifier(this); | |
547 | |
548 var self = this; | |
549 notifier.performChange(Thingy.MULTIPLY, function() { | |
550 self.a *= amount; | |
551 self.b *= amount; | |
552 | |
553 return { | |
554 multiplied: amount | |
555 }; // implicit notify | |
556 }); | |
557 }, | |
558 | |
559 incrementAndMultiply: function(incAmount, multAmount) { | |
560 var notifier = Object.getNotifier(this); | |
561 | |
562 var self = this; | |
563 notifier.performChange(Thingy.INCREMENT_AND_MULTIPLY, function() { | |
564 self.increment(incAmount); | |
565 self.multiply(multAmount); | |
566 | |
567 return { | |
568 incremented: incAmount, | |
569 multiplied: multAmount | |
570 }; // implicit notify | |
571 }); | |
572 } | |
573 } | |
574 | |
575 Thingy.observe = function(thingy, callback) { | |
576 Object.observe(thingy, callback, [Thingy.INCREMENT, | |
577 Thingy.MULTIPLY, | |
578 Thingy.INCREMENT_AND_MULTIPLY, | |
579 'update']); | |
580 } | |
581 | |
582 Thingy.unobserve = function(thingy, callback) { | |
583 Object.unobserve(thingy); | |
584 } | |
585 | |
586 var thingy = new Thingy(2, 4); | |
587 | |
588 Object.observe(thingy, observer.callback); | |
589 Thingy.observe(thingy, observer2.callback); | |
590 thingy.increment(3); // { a: 5, b: 7 } | |
591 thingy.b++; // { a: 5, b: 8 } | |
592 thingy.multiply(2); // { a: 10, b: 16 } | |
593 thingy.a++; // { a: 11, b: 16 } | |
594 thingy.incrementAndMultiply(2, 2); // { a: 26, b: 36 } | |
595 | |
596 Object.deliverChangeRecords(observer.callback); | |
597 Object.deliverChangeRecords(observer2.callback); | |
598 observer.assertCallbackRecords([ | |
599 { object: thingy, type: 'update', name: 'a', oldValue: 2 }, | |
600 { object: thingy, type: 'update', name: 'b', oldValue: 4 }, | |
601 { object: thingy, type: 'update', name: 'b', oldValue: 7 }, | |
602 { object: thingy, type: 'update', name: 'a', oldValue: 5 }, | |
603 { object: thingy, type: 'update', name: 'b', oldValue: 8 }, | |
604 { object: thingy, type: 'update', name: 'a', oldValue: 10 }, | |
605 { object: thingy, type: 'update', name: 'a', oldValue: 11 }, | |
606 { object: thingy, type: 'update', name: 'b', oldValue: 16 }, | |
607 { object: thingy, type: 'update', name: 'a', oldValue: 13 }, | |
608 { object: thingy, type: 'update', name: 'b', oldValue: 18 }, | |
609 ]); | |
610 observer2.assertCallbackRecords([ | |
611 { object: thingy, type: Thingy.INCREMENT, incremented: 3 }, | |
612 { object: thingy, type: 'update', name: 'b', oldValue: 7 }, | |
613 { object: thingy, type: Thingy.MULTIPLY, multiplied: 2 }, | |
614 { object: thingy, type: 'update', name: 'a', oldValue: 10 }, | |
615 { | |
616 object: thingy, | |
617 type: Thingy.INCREMENT_AND_MULTIPLY, | |
618 incremented: 2, | |
619 multiplied: 2 | |
620 } | |
621 ]); | |
622 | |
623 // ArrayPush cached stub | |
624 reset(); | |
625 | |
626 function pushMultiple(arr) { | |
627 arr.push('a'); | |
628 arr.push('b'); | |
629 arr.push('c'); | |
630 } | |
631 | |
632 for (var i = 0; i < 5; i++) { | |
633 var arr = []; | |
634 pushMultiple(arr); | |
635 } | |
636 | |
637 for (var i = 0; i < 5; i++) { | |
638 reset(); | |
639 var arr = []; | |
640 Object.observe(arr, observer.callback); | |
641 pushMultiple(arr); | |
642 Object.unobserve(arr, observer.callback); | |
643 Object.deliverChangeRecords(observer.callback); | |
644 observer.assertCallbackRecords([ | |
645 { object: arr, type: 'add', name: '0' }, | |
646 { object: arr, type: 'update', name: 'length', oldValue: 0 }, | |
647 { object: arr, type: 'add', name: '1' }, | |
648 { object: arr, type: 'update', name: 'length', oldValue: 1 }, | |
649 { object: arr, type: 'add', name: '2' }, | |
650 { object: arr, type: 'update', name: 'length', oldValue: 2 }, | |
651 ]); | |
652 } | |
653 | |
654 | |
655 // ArrayPop cached stub | |
656 reset(); | |
657 | |
658 function popMultiple(arr) { | |
659 arr.pop(); | |
660 arr.pop(); | |
661 arr.pop(); | |
662 } | |
663 | |
664 for (var i = 0; i < 5; i++) { | |
665 var arr = ['a', 'b', 'c']; | |
666 popMultiple(arr); | |
667 } | |
668 | |
669 for (var i = 0; i < 5; i++) { | |
670 reset(); | |
671 var arr = ['a', 'b', 'c']; | |
672 Object.observe(arr, observer.callback); | |
673 popMultiple(arr); | |
674 Object.unobserve(arr, observer.callback); | |
675 Object.deliverChangeRecords(observer.callback); | |
676 observer.assertCallbackRecords([ | |
677 { object: arr, type: 'delete', name: '2', oldValue: 'c' }, | |
678 { object: arr, type: 'update', name: 'length', oldValue: 3 }, | |
679 { object: arr, type: 'delete', name: '1', oldValue: 'b' }, | |
680 { object: arr, type: 'update', name: 'length', oldValue: 2 }, | |
681 { object: arr, type: 'delete', name: '0', oldValue: 'a' }, | |
682 { object: arr, type: 'update', name: 'length', oldValue: 1 }, | |
683 ]); | |
684 } | |
685 | |
686 | |
687 reset(); | |
688 function RecursiveThingy() {} | |
689 | |
690 RecursiveThingy.MULTIPLY_FIRST_N = 'multiplyFirstN'; | |
691 | |
692 RecursiveThingy.prototype = { | |
693 __proto__: Array.prototype, | |
694 | |
695 multiplyFirstN: function(amount, n) { | |
696 if (!n) | |
697 return; | |
698 var notifier = Object.getNotifier(this); | |
699 var self = this; | |
700 notifier.performChange(RecursiveThingy.MULTIPLY_FIRST_N, function() { | |
701 self[n-1] = self[n-1]*amount; | |
702 self.multiplyFirstN(amount, n-1); | |
703 }); | |
704 | |
705 notifier.notify({ | |
706 type: RecursiveThingy.MULTIPLY_FIRST_N, | |
707 multiplied: amount, | |
708 n: n | |
709 }); | |
710 }, | |
711 } | |
712 | |
713 RecursiveThingy.observe = function(thingy, callback) { | |
714 Object.observe(thingy, callback, [RecursiveThingy.MULTIPLY_FIRST_N]); | |
715 } | |
716 | |
717 RecursiveThingy.unobserve = function(thingy, callback) { | |
718 Object.unobserve(thingy); | |
719 } | |
720 | |
721 var thingy = new RecursiveThingy; | |
722 thingy.push(1, 2, 3, 4); | |
723 | |
724 Object.observe(thingy, observer.callback); | |
725 RecursiveThingy.observe(thingy, observer2.callback); | |
726 thingy.multiplyFirstN(2, 3); // [2, 4, 6, 4] | |
727 | |
728 Object.deliverChangeRecords(observer.callback); | |
729 Object.deliverChangeRecords(observer2.callback); | |
730 observer.assertCallbackRecords([ | |
731 { object: thingy, type: 'update', name: '2', oldValue: 3 }, | |
732 { object: thingy, type: 'update', name: '1', oldValue: 2 }, | |
733 { object: thingy, type: 'update', name: '0', oldValue: 1 } | |
734 ]); | |
735 observer2.assertCallbackRecords([ | |
736 { object: thingy, type: RecursiveThingy.MULTIPLY_FIRST_N, multiplied: 2, n: 3
} | |
737 ]); | |
738 | |
739 reset(); | |
740 function DeckSuit() { | |
741 this.push('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'A', 'Q', 'K'); | |
742 } | |
743 | |
744 DeckSuit.SHUFFLE = 'shuffle'; | |
745 | |
746 DeckSuit.prototype = { | |
747 __proto__: Array.prototype, | |
748 | |
749 shuffle: function() { | |
750 var notifier = Object.getNotifier(this); | |
751 var self = this; | |
752 notifier.performChange(DeckSuit.SHUFFLE, function() { | |
753 self.reverse(); | |
754 self.sort(function() { return Math.random()* 2 - 1; }); | |
755 var cut = self.splice(0, 6); | |
756 Array.prototype.push.apply(self, cut); | |
757 self.reverse(); | |
758 self.sort(function() { return Math.random()* 2 - 1; }); | |
759 var cut = self.splice(0, 6); | |
760 Array.prototype.push.apply(self, cut); | |
761 self.reverse(); | |
762 self.sort(function() { return Math.random()* 2 - 1; }); | |
763 }); | |
764 | |
765 notifier.notify({ | |
766 type: DeckSuit.SHUFFLE | |
767 }); | |
768 }, | |
769 } | |
770 | |
771 DeckSuit.observe = function(thingy, callback) { | |
772 Object.observe(thingy, callback, [DeckSuit.SHUFFLE]); | |
773 } | |
774 | |
775 DeckSuit.unobserve = function(thingy, callback) { | |
776 Object.unobserve(thingy); | |
777 } | |
778 | |
779 var deck = new DeckSuit; | |
780 | |
781 DeckSuit.observe(deck, observer2.callback); | |
782 deck.shuffle(); | |
783 | |
784 Object.deliverChangeRecords(observer2.callback); | |
785 observer2.assertCallbackRecords([ | |
786 { object: deck, type: DeckSuit.SHUFFLE } | |
787 ]); | |
788 | |
789 // Observing multiple objects; records appear in order. | |
790 reset(); | |
791 var obj2 = {}; | |
792 var obj3 = {} | |
793 Object.observe(obj, observer.callback); | |
794 Object.observe(obj3, observer.callback); | |
795 Object.observe(obj2, observer.callback); | |
796 Object.getNotifier(obj).notify({ | |
797 type: 'add', | |
798 }); | |
799 Object.getNotifier(obj2).notify({ | |
800 type: 'update', | |
801 }); | |
802 Object.getNotifier(obj3).notify({ | |
803 type: 'delete', | |
804 }); | |
805 Object.observe(obj3, observer.callback); | |
806 Object.deliverChangeRecords(observer.callback); | |
807 observer.assertCallbackRecords([ | |
808 { object: obj, type: 'add' }, | |
809 { object: obj2, type: 'update' }, | |
810 { object: obj3, type: 'delete' } | |
811 ]); | |
812 | |
813 | |
814 // Recursive observation. | |
815 var obj = {a: 1}; | |
816 var callbackCount = 0; | |
817 function recursiveObserver(r) { | |
818 assertEquals(1, r.length); | |
819 ++callbackCount; | |
820 if (r[0].oldValue < 100) ++obj[r[0].name]; | |
821 } | |
822 Object.observe(obj, recursiveObserver); | |
823 ++obj.a; | |
824 Object.deliverChangeRecords(recursiveObserver); | |
825 assertEquals(100, callbackCount); | |
826 | |
827 var obj1 = {a: 1}; | |
828 var obj2 = {a: 1}; | |
829 var recordCount = 0; | |
830 function recursiveObserver2(r) { | |
831 recordCount += r.length; | |
832 if (r[0].oldValue < 100) { | |
833 ++obj1.a; | |
834 ++obj2.a; | |
835 } | |
836 } | |
837 Object.observe(obj1, recursiveObserver2); | |
838 Object.observe(obj2, recursiveObserver2); | |
839 ++obj1.a; | |
840 Object.deliverChangeRecords(recursiveObserver2); | |
841 assertEquals(199, recordCount); | |
842 | |
843 | |
844 // Observing named properties. | |
845 reset(); | |
846 var obj = {a: 1} | |
847 Object.observe(obj, observer.callback); | |
848 obj.a = 2; | |
849 obj["a"] = 3; | |
850 delete obj.a; | |
851 obj.a = 4; | |
852 obj.a = 4; // ignored | |
853 obj.a = 5; | |
854 Object.defineProperty(obj, "a", {value: 6}); | |
855 Object.defineProperty(obj, "a", {writable: false}); | |
856 obj.a = 7; // ignored | |
857 Object.defineProperty(obj, "a", {value: 8}); | |
858 Object.defineProperty(obj, "a", {value: 7, writable: true}); | |
859 Object.defineProperty(obj, "a", {get: function() {}}); | |
860 Object.defineProperty(obj, "a", {get: frozenFunction}); | |
861 Object.defineProperty(obj, "a", {get: frozenFunction}); // ignored | |
862 Object.defineProperty(obj, "a", {get: frozenFunction, set: frozenFunction}); | |
863 Object.defineProperty(obj, "a", {set: frozenFunction}); // ignored | |
864 Object.defineProperty(obj, "a", {get: undefined, set: frozenFunction}); | |
865 delete obj.a; | |
866 delete obj.a; | |
867 Object.defineProperty(obj, "a", {get: function() {}, configurable: true}); | |
868 Object.defineProperty(obj, "a", {value: 9, writable: true}); | |
869 obj.a = 10; | |
870 ++obj.a; | |
871 obj.a++; | |
872 obj.a *= 3; | |
873 delete obj.a; | |
874 Object.defineProperty(obj, "a", {value: 11, configurable: true}); | |
875 Object.deliverChangeRecords(observer.callback); | |
876 observer.assertCallbackRecords([ | |
877 { object: obj, name: "a", type: "update", oldValue: 1 }, | |
878 { object: obj, name: "a", type: "update", oldValue: 2 }, | |
879 { object: obj, name: "a", type: "delete", oldValue: 3 }, | |
880 { object: obj, name: "a", type: "add" }, | |
881 { object: obj, name: "a", type: "update", oldValue: 4 }, | |
882 { object: obj, name: "a", type: "update", oldValue: 5 }, | |
883 { object: obj, name: "a", type: "reconfigure" }, | |
884 { object: obj, name: "a", type: "update", oldValue: 6 }, | |
885 { object: obj, name: "a", type: "reconfigure", oldValue: 8 }, | |
886 { object: obj, name: "a", type: "reconfigure", oldValue: 7 }, | |
887 { object: obj, name: "a", type: "reconfigure" }, | |
888 { object: obj, name: "a", type: "reconfigure" }, | |
889 { object: obj, name: "a", type: "reconfigure" }, | |
890 { object: obj, name: "a", type: "delete" }, | |
891 { object: obj, name: "a", type: "add" }, | |
892 { object: obj, name: "a", type: "reconfigure" }, | |
893 { object: obj, name: "a", type: "update", oldValue: 9 }, | |
894 { object: obj, name: "a", type: "update", oldValue: 10 }, | |
895 { object: obj, name: "a", type: "update", oldValue: 11 }, | |
896 { object: obj, name: "a", type: "update", oldValue: 12 }, | |
897 { object: obj, name: "a", type: "delete", oldValue: 36 }, | |
898 { object: obj, name: "a", type: "add" }, | |
899 ]); | |
900 | |
901 | |
902 // Observing indexed properties. | |
903 reset(); | |
904 var obj = {'1': 1} | |
905 Object.observe(obj, observer.callback); | |
906 obj[1] = 2; | |
907 obj[1] = 3; | |
908 delete obj[1]; | |
909 obj[1] = 4; | |
910 obj[1] = 4; // ignored | |
911 obj[1] = 5; | |
912 Object.defineProperty(obj, "1", {value: 6}); | |
913 Object.defineProperty(obj, "1", {writable: false}); | |
914 obj[1] = 7; // ignored | |
915 Object.defineProperty(obj, "1", {value: 8}); | |
916 Object.defineProperty(obj, "1", {value: 7, writable: true}); | |
917 Object.defineProperty(obj, "1", {get: function() {}}); | |
918 Object.defineProperty(obj, "1", {get: frozenFunction}); | |
919 Object.defineProperty(obj, "1", {get: frozenFunction}); // ignored | |
920 Object.defineProperty(obj, "1", {get: frozenFunction, set: frozenFunction}); | |
921 Object.defineProperty(obj, "1", {set: frozenFunction}); // ignored | |
922 Object.defineProperty(obj, "1", {get: undefined, set: frozenFunction}); | |
923 delete obj[1]; | |
924 delete obj[1]; | |
925 Object.defineProperty(obj, "1", {get: function() {}, configurable: true}); | |
926 Object.defineProperty(obj, "1", {value: 9, writable: true}); | |
927 obj[1] = 10; | |
928 ++obj[1]; | |
929 obj[1]++; | |
930 obj[1] *= 3; | |
931 delete obj[1]; | |
932 Object.defineProperty(obj, "1", {value: 11, configurable: true}); | |
933 Object.deliverChangeRecords(observer.callback); | |
934 observer.assertCallbackRecords([ | |
935 { object: obj, name: "1", type: "update", oldValue: 1 }, | |
936 { object: obj, name: "1", type: "update", oldValue: 2 }, | |
937 { object: obj, name: "1", type: "delete", oldValue: 3 }, | |
938 { object: obj, name: "1", type: "add" }, | |
939 { object: obj, name: "1", type: "update", oldValue: 4 }, | |
940 { object: obj, name: "1", type: "update", oldValue: 5 }, | |
941 { object: obj, name: "1", type: "reconfigure" }, | |
942 { object: obj, name: "1", type: "update", oldValue: 6 }, | |
943 { object: obj, name: "1", type: "reconfigure", oldValue: 8 }, | |
944 { object: obj, name: "1", type: "reconfigure", oldValue: 7 }, | |
945 { object: obj, name: "1", type: "reconfigure" }, | |
946 { object: obj, name: "1", type: "reconfigure" }, | |
947 { object: obj, name: "1", type: "reconfigure" }, | |
948 { object: obj, name: "1", type: "delete" }, | |
949 { object: obj, name: "1", type: "add" }, | |
950 { object: obj, name: "1", type: "reconfigure" }, | |
951 { object: obj, name: "1", type: "update", oldValue: 9 }, | |
952 { object: obj, name: "1", type: "update", oldValue: 10 }, | |
953 { object: obj, name: "1", type: "update", oldValue: 11 }, | |
954 { object: obj, name: "1", type: "update", oldValue: 12 }, | |
955 { object: obj, name: "1", type: "delete", oldValue: 36 }, | |
956 { object: obj, name: "1", type: "add" }, | |
957 ]); | |
958 | |
959 | |
960 // Observing symbol properties (not). | |
961 print("*****") | |
962 reset(); | |
963 var obj = {} | |
964 var symbol = Symbol("secret"); | |
965 Object.observe(obj, observer.callback); | |
966 obj[symbol] = 3; | |
967 delete obj[symbol]; | |
968 Object.defineProperty(obj, symbol, {get: function() {}, configurable: true}); | |
969 Object.defineProperty(obj, symbol, {value: 6}); | |
970 Object.defineProperty(obj, symbol, {writable: false}); | |
971 delete obj[symbol]; | |
972 Object.defineProperty(obj, symbol, {value: 7}); | |
973 ++obj[symbol]; | |
974 obj[symbol]++; | |
975 obj[symbol] *= 3; | |
976 delete obj[symbol]; | |
977 obj.__defineSetter__(symbol, function() {}); | |
978 obj.__defineGetter__(symbol, function() {}); | |
979 Object.deliverChangeRecords(observer.callback); | |
980 observer.assertNotCalled(); | |
981 | |
982 | |
983 // Test all kinds of objects generically. | |
984 function TestObserveConfigurable(obj, prop) { | |
985 reset(); | |
986 Object.observe(obj, observer.callback); | |
987 Object.unobserve(obj, observer.callback); | |
988 obj[prop] = 1; | |
989 Object.observe(obj, observer.callback); | |
990 obj[prop] = 2; | |
991 obj[prop] = 3; | |
992 delete obj[prop]; | |
993 obj[prop] = 4; | |
994 obj[prop] = 4; // ignored | |
995 obj[prop] = 5; | |
996 Object.defineProperty(obj, prop, {value: 6}); | |
997 Object.defineProperty(obj, prop, {writable: false}); | |
998 obj[prop] = 7; // ignored | |
999 Object.defineProperty(obj, prop, {value: 8}); | |
1000 Object.defineProperty(obj, prop, {value: 7, writable: true}); | |
1001 Object.defineProperty(obj, prop, {get: function() {}}); | |
1002 Object.defineProperty(obj, prop, {get: frozenFunction}); | |
1003 Object.defineProperty(obj, prop, {get: frozenFunction}); // ignored | |
1004 Object.defineProperty(obj, prop, {get: frozenFunction, set: frozenFunction}); | |
1005 Object.defineProperty(obj, prop, {set: frozenFunction}); // ignored | |
1006 Object.defineProperty(obj, prop, {get: undefined, set: frozenFunction}); | |
1007 obj.__defineSetter__(prop, frozenFunction); // ignored | |
1008 obj.__defineSetter__(prop, function() {}); | |
1009 obj.__defineGetter__(prop, function() {}); | |
1010 delete obj[prop]; | |
1011 delete obj[prop]; // ignored | |
1012 obj.__defineGetter__(prop, function() {}); | |
1013 delete obj[prop]; | |
1014 Object.defineProperty(obj, prop, {get: function() {}, configurable: true}); | |
1015 Object.defineProperty(obj, prop, {value: 9, writable: true}); | |
1016 obj[prop] = 10; | |
1017 ++obj[prop]; | |
1018 obj[prop]++; | |
1019 obj[prop] *= 3; | |
1020 delete obj[prop]; | |
1021 Object.defineProperty(obj, prop, {value: 11, configurable: true}); | |
1022 Object.deliverChangeRecords(observer.callback); | |
1023 observer.assertCallbackRecords([ | |
1024 { object: obj, name: prop, type: "update", oldValue: 1 }, | |
1025 { object: obj, name: prop, type: "update", oldValue: 2 }, | |
1026 { object: obj, name: prop, type: "delete", oldValue: 3 }, | |
1027 { object: obj, name: prop, type: "add" }, | |
1028 { object: obj, name: prop, type: "update", oldValue: 4 }, | |
1029 { object: obj, name: prop, type: "update", oldValue: 5 }, | |
1030 { object: obj, name: prop, type: "reconfigure" }, | |
1031 { object: obj, name: prop, type: "update", oldValue: 6 }, | |
1032 { object: obj, name: prop, type: "reconfigure", oldValue: 8 }, | |
1033 { object: obj, name: prop, type: "reconfigure", oldValue: 7 }, | |
1034 { object: obj, name: prop, type: "reconfigure" }, | |
1035 { object: obj, name: prop, type: "reconfigure" }, | |
1036 { object: obj, name: prop, type: "reconfigure" }, | |
1037 { object: obj, name: prop, type: "reconfigure" }, | |
1038 { object: obj, name: prop, type: "reconfigure" }, | |
1039 { object: obj, name: prop, type: "delete" }, | |
1040 { object: obj, name: prop, type: "add" }, | |
1041 { object: obj, name: prop, type: "delete" }, | |
1042 { object: obj, name: prop, type: "add" }, | |
1043 { object: obj, name: prop, type: "reconfigure" }, | |
1044 { object: obj, name: prop, type: "update", oldValue: 9 }, | |
1045 { object: obj, name: prop, type: "update", oldValue: 10 }, | |
1046 { object: obj, name: prop, type: "update", oldValue: 11 }, | |
1047 { object: obj, name: prop, type: "update", oldValue: 12 }, | |
1048 { object: obj, name: prop, type: "delete", oldValue: 36 }, | |
1049 { object: obj, name: prop, type: "add" }, | |
1050 ]); | |
1051 Object.unobserve(obj, observer.callback); | |
1052 delete obj[prop]; | |
1053 } | |
1054 | |
1055 function TestObserveNonConfigurable(obj, prop, desc) { | |
1056 reset(); | |
1057 Object.observe(obj, observer.callback); | |
1058 Object.unobserve(obj, observer.callback); | |
1059 obj[prop] = 1; | |
1060 Object.observe(obj, observer.callback); | |
1061 obj[prop] = 4; | |
1062 obj[prop] = 4; // ignored | |
1063 obj[prop] = 5; | |
1064 Object.defineProperty(obj, prop, {value: 6}); | |
1065 Object.defineProperty(obj, prop, {value: 6}); // ignored | |
1066 Object.defineProperty(obj, prop, {value: 7}); | |
1067 Object.defineProperty(obj, prop, {enumerable: desc.enumerable}); // ignored | |
1068 Object.defineProperty(obj, prop, {writable: false}); | |
1069 obj[prop] = 7; // ignored | |
1070 Object.deliverChangeRecords(observer.callback); | |
1071 observer.assertCallbackRecords([ | |
1072 { object: obj, name: prop, type: "update", oldValue: 1 }, | |
1073 { object: obj, name: prop, type: "update", oldValue: 4 }, | |
1074 { object: obj, name: prop, type: "update", oldValue: 5 }, | |
1075 { object: obj, name: prop, type: "update", oldValue: 6 }, | |
1076 { object: obj, name: prop, type: "reconfigure" }, | |
1077 ]); | |
1078 Object.unobserve(obj, observer.callback); | |
1079 } | |
1080 | |
1081 // TODO(rafaelw) Enable when ES6 Proxies are implemented | |
1082 /* | |
1083 function createProxy(create, x) { | |
1084 var handler = { | |
1085 getPropertyDescriptor: function(k) { | |
1086 for (var o = this.target; o; o = Object.getPrototypeOf(o)) { | |
1087 var desc = Object.getOwnPropertyDescriptor(o, k); | |
1088 if (desc) return desc; | |
1089 } | |
1090 return undefined; | |
1091 }, | |
1092 getOwnPropertyDescriptor: function(k) { | |
1093 return Object.getOwnPropertyDescriptor(this.target, k); | |
1094 }, | |
1095 defineProperty: function(k, desc) { | |
1096 var x = Object.defineProperty(this.target, k, desc); | |
1097 Object.deliverChangeRecords(this.callback); | |
1098 return x; | |
1099 }, | |
1100 delete: function(k) { | |
1101 var x = delete this.target[k]; | |
1102 Object.deliverChangeRecords(this.callback); | |
1103 return x; | |
1104 }, | |
1105 getPropertyNames: function() { | |
1106 return Object.getOwnPropertyNames(this.target); | |
1107 }, | |
1108 target: {isProxy: true}, | |
1109 callback: function(changeRecords) { | |
1110 print("callback", stringifyNoThrow(handler.proxy), stringifyNoThrow(got)); | |
1111 for (var i in changeRecords) { | |
1112 var got = changeRecords[i]; | |
1113 var change = {object: handler.proxy, name: got.name, type: got.type}; | |
1114 if ("oldValue" in got) change.oldValue = got.oldValue; | |
1115 Object.getNotifier(handler.proxy).notify(change); | |
1116 } | |
1117 }, | |
1118 }; | |
1119 Object.observe(handler.target, handler.callback); | |
1120 return handler.proxy = create(handler, x); | |
1121 } | |
1122 */ | |
1123 | |
1124 var objects = [ | |
1125 {}, | |
1126 [], | |
1127 function(){}, | |
1128 (function(){ return arguments })(), | |
1129 (function(){ "use strict"; return arguments })(), | |
1130 Object(1), Object(true), Object("bla"), | |
1131 new Date(), | |
1132 Object, Function, Date, RegExp, | |
1133 new Set, new Map, new WeakMap, | |
1134 new ArrayBuffer(10), new Int32Array(5) | |
1135 // TODO(rafaelw) Enable when ES6 Proxies are implemented. | |
1136 // createProxy(Proxy.create, null), | |
1137 // createProxy(Proxy.createFunction, function(){}), | |
1138 ]; | |
1139 var properties = ["a", "1", 1, "length", "setPrototype", "name", "caller"]; | |
1140 | |
1141 // Cases that yield non-standard results. | |
1142 function blacklisted(obj, prop) { | |
1143 return (obj instanceof Int32Array && prop == 1) || | |
1144 (obj instanceof Int32Array && prop === "length") || | |
1145 (obj instanceof ArrayBuffer && prop == 1) || | |
1146 (obj instanceof Function && prop === "name") || // Has its own test. | |
1147 (obj instanceof Function && prop === "length"); // Has its own test. | |
1148 } | |
1149 | |
1150 for (var i in objects) for (var j in properties) { | |
1151 var obj = objects[i]; | |
1152 var prop = properties[j]; | |
1153 if (blacklisted(obj, prop)) continue; | |
1154 var desc = Object.getOwnPropertyDescriptor(obj, prop); | |
1155 print("***", typeof obj, stringifyNoThrow(obj), prop); | |
1156 if (!desc || desc.configurable) | |
1157 TestObserveConfigurable(obj, prop); | |
1158 else if (desc.writable) | |
1159 TestObserveNonConfigurable(obj, prop, desc); | |
1160 } | |
1161 | |
1162 | |
1163 // Observing array length (including truncation) | |
1164 reset(); | |
1165 var arr = ['a', 'b', 'c', 'd']; | |
1166 var arr2 = ['alpha', 'beta']; | |
1167 var arr3 = ['hello']; | |
1168 arr3[2] = 'goodbye'; | |
1169 arr3.length = 6; | |
1170 Object.defineProperty(arr, '0', {configurable: false}); | |
1171 Object.defineProperty(arr, '2', {get: function(){}}); | |
1172 Object.defineProperty(arr2, '0', {get: function(){}, configurable: false}); | |
1173 Object.observe(arr, observer.callback); | |
1174 Array.observe(arr, observer2.callback); | |
1175 Object.observe(arr2, observer.callback); | |
1176 Array.observe(arr2, observer2.callback); | |
1177 Object.observe(arr3, observer.callback); | |
1178 Array.observe(arr3, observer2.callback); | |
1179 arr.length = 2; | |
1180 arr.length = 0; | |
1181 arr.length = 10; | |
1182 Object.defineProperty(arr, 'length', {writable: false}); | |
1183 arr2.length = 0; | |
1184 arr2.length = 1; // no change expected | |
1185 Object.defineProperty(arr2, 'length', {value: 1, writable: false}); | |
1186 arr3.length = 0; | |
1187 ++arr3.length; | |
1188 arr3.length++; | |
1189 arr3.length /= 2; | |
1190 Object.defineProperty(arr3, 'length', {value: 5}); | |
1191 arr3[4] = 5; | |
1192 Object.defineProperty(arr3, 'length', {value: 1, writable: false}); | |
1193 Object.deliverChangeRecords(observer.callback); | |
1194 observer.assertCallbackRecords([ | |
1195 { object: arr, name: '3', type: 'delete', oldValue: 'd' }, | |
1196 { object: arr, name: '2', type: 'delete' }, | |
1197 { object: arr, name: 'length', type: 'update', oldValue: 4 }, | |
1198 { object: arr, name: '1', type: 'delete', oldValue: 'b' }, | |
1199 { object: arr, name: 'length', type: 'update', oldValue: 2 }, | |
1200 { object: arr, name: 'length', type: 'update', oldValue: 1 }, | |
1201 { object: arr, name: 'length', type: 'reconfigure' }, | |
1202 { object: arr2, name: '1', type: 'delete', oldValue: 'beta' }, | |
1203 { object: arr2, name: 'length', type: 'update', oldValue: 2 }, | |
1204 { object: arr2, name: 'length', type: 'reconfigure' }, | |
1205 { object: arr3, name: '2', type: 'delete', oldValue: 'goodbye' }, | |
1206 { object: arr3, name: '0', type: 'delete', oldValue: 'hello' }, | |
1207 { object: arr3, name: 'length', type: 'update', oldValue: 6 }, | |
1208 { object: arr3, name: 'length', type: 'update', oldValue: 0 }, | |
1209 { object: arr3, name: 'length', type: 'update', oldValue: 1 }, | |
1210 { object: arr3, name: 'length', type: 'update', oldValue: 2 }, | |
1211 { object: arr3, name: 'length', type: 'update', oldValue: 1 }, | |
1212 { object: arr3, name: '4', type: 'add' }, | |
1213 { object: arr3, name: '4', type: 'delete', oldValue: 5 }, | |
1214 // TODO(rafaelw): It breaks spec compliance to get two records here. | |
1215 // When the TODO in v8natives.js::DefineArrayProperty is addressed | |
1216 // which prevents DefineProperty from over-writing the magic length | |
1217 // property, these will collapse into a single record. | |
1218 { object: arr3, name: 'length', type: 'update', oldValue: 5 }, | |
1219 { object: arr3, name: 'length', type: 'reconfigure' } | |
1220 ]); | |
1221 Object.deliverChangeRecords(observer2.callback); | |
1222 observer2.assertCallbackRecords([ | |
1223 { object: arr, type: 'splice', index: 2, removed: [, 'd'], addedCount: 0 }, | |
1224 { object: arr, type: 'splice', index: 1, removed: ['b'], addedCount: 0 }, | |
1225 { object: arr, type: 'splice', index: 1, removed: [], addedCount: 9 }, | |
1226 { object: arr2, type: 'splice', index: 1, removed: ['beta'], addedCount: 0 }, | |
1227 { object: arr3, type: 'splice', index: 0, removed: ['hello',, 'goodbye',,,,],
addedCount: 0 }, | |
1228 { object: arr3, type: 'splice', index: 0, removed: [], addedCount: 1 }, | |
1229 { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 1 }, | |
1230 { object: arr3, type: 'splice', index: 1, removed: [,], addedCount: 0 }, | |
1231 { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 4 }, | |
1232 { object: arr3, name: '4', type: 'add' }, | |
1233 { object: arr3, type: 'splice', index: 1, removed: [,,,5], addedCount: 0 } | |
1234 ]); | |
1235 | |
1236 | |
1237 // Updating length on large (slow) array | |
1238 reset(); | |
1239 var slow_arr = %NormalizeElements([]); | |
1240 slow_arr[500000000] = 'hello'; | |
1241 slow_arr.length = 1000000000; | |
1242 Object.observe(slow_arr, observer.callback); | |
1243 var spliceRecords; | |
1244 function slowSpliceCallback(records) { | |
1245 spliceRecords = records; | |
1246 } | |
1247 Array.observe(slow_arr, slowSpliceCallback); | |
1248 slow_arr.length = 100; | |
1249 Object.deliverChangeRecords(observer.callback); | |
1250 observer.assertCallbackRecords([ | |
1251 { object: slow_arr, name: '500000000', type: 'delete', oldValue: 'hello' }, | |
1252 { object: slow_arr, name: 'length', type: 'update', oldValue: 1000000000 }, | |
1253 ]); | |
1254 Object.deliverChangeRecords(slowSpliceCallback); | |
1255 assertEquals(spliceRecords.length, 1); | |
1256 // Have to custom assert this splice record because the removed array is huge. | |
1257 var splice = spliceRecords[0]; | |
1258 assertSame(splice.object, slow_arr); | |
1259 assertEquals(splice.type, 'splice'); | |
1260 assertEquals(splice.index, 100); | |
1261 assertEquals(splice.addedCount, 0); | |
1262 var array_keys = %GetArrayKeys(splice.removed, splice.removed.length); | |
1263 assertEquals(array_keys.length, 1); | |
1264 assertEquals(array_keys[0], 499999900); | |
1265 assertEquals(splice.removed[499999900], 'hello'); | |
1266 assertEquals(splice.removed.length, 999999900); | |
1267 | |
1268 | |
1269 // Assignments in loops (checking different IC states). | |
1270 reset(); | |
1271 var obj = {}; | |
1272 Object.observe(obj, observer.callback); | |
1273 for (var i = 0; i < 5; i++) { | |
1274 obj["a" + i] = i; | |
1275 } | |
1276 Object.deliverChangeRecords(observer.callback); | |
1277 observer.assertCallbackRecords([ | |
1278 { object: obj, name: "a0", type: "add" }, | |
1279 { object: obj, name: "a1", type: "add" }, | |
1280 { object: obj, name: "a2", type: "add" }, | |
1281 { object: obj, name: "a3", type: "add" }, | |
1282 { object: obj, name: "a4", type: "add" }, | |
1283 ]); | |
1284 | |
1285 reset(); | |
1286 var obj = {}; | |
1287 Object.observe(obj, observer.callback); | |
1288 for (var i = 0; i < 5; i++) { | |
1289 obj[i] = i; | |
1290 } | |
1291 Object.deliverChangeRecords(observer.callback); | |
1292 observer.assertCallbackRecords([ | |
1293 { object: obj, name: "0", type: "add" }, | |
1294 { object: obj, name: "1", type: "add" }, | |
1295 { object: obj, name: "2", type: "add" }, | |
1296 { object: obj, name: "3", type: "add" }, | |
1297 { object: obj, name: "4", type: "add" }, | |
1298 ]); | |
1299 | |
1300 | |
1301 // Adding elements past the end of an array should notify on length for | |
1302 // Object.observe and emit "splices" for Array.observe. | |
1303 reset(); | |
1304 var arr = [1, 2, 3]; | |
1305 Object.observe(arr, observer.callback); | |
1306 Array.observe(arr, observer2.callback); | |
1307 arr[3] = 10; | |
1308 arr[100] = 20; | |
1309 Object.defineProperty(arr, '200', {value: 7}); | |
1310 Object.defineProperty(arr, '400', {get: function(){}}); | |
1311 arr[50] = 30; // no length change expected | |
1312 Object.deliverChangeRecords(observer.callback); | |
1313 observer.assertCallbackRecords([ | |
1314 { object: arr, name: '3', type: 'add' }, | |
1315 { object: arr, name: 'length', type: 'update', oldValue: 3 }, | |
1316 { object: arr, name: '100', type: 'add' }, | |
1317 { object: arr, name: 'length', type: 'update', oldValue: 4 }, | |
1318 { object: arr, name: '200', type: 'add' }, | |
1319 { object: arr, name: 'length', type: 'update', oldValue: 101 }, | |
1320 { object: arr, name: '400', type: 'add' }, | |
1321 { object: arr, name: 'length', type: 'update', oldValue: 201 }, | |
1322 { object: arr, name: '50', type: 'add' }, | |
1323 ]); | |
1324 Object.deliverChangeRecords(observer2.callback); | |
1325 observer2.assertCallbackRecords([ | |
1326 { object: arr, type: 'splice', index: 3, removed: [], addedCount: 1 }, | |
1327 { object: arr, type: 'splice', index: 4, removed: [], addedCount: 97 }, | |
1328 { object: arr, type: 'splice', index: 101, removed: [], addedCount: 100 }, | |
1329 { object: arr, type: 'splice', index: 201, removed: [], addedCount: 200 }, | |
1330 { object: arr, type: 'add', name: '50' }, | |
1331 ]); | |
1332 | |
1333 | |
1334 // Tests for array methods, first on arrays and then on plain objects | |
1335 // | |
1336 // === ARRAYS === | |
1337 // | |
1338 // Push | |
1339 reset(); | |
1340 var array = [1, 2]; | |
1341 Object.observe(array, observer.callback); | |
1342 Array.observe(array, observer2.callback); | |
1343 array.push(3, 4); | |
1344 array.push(5); | |
1345 Object.deliverChangeRecords(observer.callback); | |
1346 observer.assertCallbackRecords([ | |
1347 { object: array, name: '2', type: 'add' }, | |
1348 { object: array, name: 'length', type: 'update', oldValue: 2 }, | |
1349 { object: array, name: '3', type: 'add' }, | |
1350 { object: array, name: 'length', type: 'update', oldValue: 3 }, | |
1351 { object: array, name: '4', type: 'add' }, | |
1352 { object: array, name: 'length', type: 'update', oldValue: 4 }, | |
1353 ]); | |
1354 Object.deliverChangeRecords(observer2.callback); | |
1355 observer2.assertCallbackRecords([ | |
1356 { object: array, type: 'splice', index: 2, removed: [], addedCount: 2 }, | |
1357 { object: array, type: 'splice', index: 4, removed: [], addedCount: 1 } | |
1358 ]); | |
1359 | |
1360 // Pop | |
1361 reset(); | |
1362 var array = [1, 2]; | |
1363 Object.observe(array, observer.callback); | |
1364 array.pop(); | |
1365 array.pop(); | |
1366 Object.deliverChangeRecords(observer.callback); | |
1367 observer.assertCallbackRecords([ | |
1368 { object: array, name: '1', type: 'delete', oldValue: 2 }, | |
1369 { object: array, name: 'length', type: 'update', oldValue: 2 }, | |
1370 { object: array, name: '0', type: 'delete', oldValue: 1 }, | |
1371 { object: array, name: 'length', type: 'update', oldValue: 1 }, | |
1372 ]); | |
1373 | |
1374 // Shift | |
1375 reset(); | |
1376 var array = [1, 2]; | |
1377 Object.observe(array, observer.callback); | |
1378 array.shift(); | |
1379 array.shift(); | |
1380 Object.deliverChangeRecords(observer.callback); | |
1381 observer.assertCallbackRecords([ | |
1382 { object: array, name: '0', type: 'update', oldValue: 1 }, | |
1383 { object: array, name: '1', type: 'delete', oldValue: 2 }, | |
1384 { object: array, name: 'length', type: 'update', oldValue: 2 }, | |
1385 { object: array, name: '0', type: 'delete', oldValue: 2 }, | |
1386 { object: array, name: 'length', type: 'update', oldValue: 1 }, | |
1387 ]); | |
1388 | |
1389 // Unshift | |
1390 reset(); | |
1391 var array = [1, 2]; | |
1392 Object.observe(array, observer.callback); | |
1393 array.unshift(3, 4); | |
1394 Object.deliverChangeRecords(observer.callback); | |
1395 observer.assertCallbackRecords([ | |
1396 { object: array, name: '3', type: 'add' }, | |
1397 { object: array, name: 'length', type: 'update', oldValue: 2 }, | |
1398 { object: array, name: '2', type: 'add' }, | |
1399 { object: array, name: '0', type: 'update', oldValue: 1 }, | |
1400 { object: array, name: '1', type: 'update', oldValue: 2 }, | |
1401 ]); | |
1402 | |
1403 // Splice | |
1404 reset(); | |
1405 var array = [1, 2, 3]; | |
1406 Object.observe(array, observer.callback); | |
1407 array.splice(1, 1, 4, 5); | |
1408 Object.deliverChangeRecords(observer.callback); | |
1409 observer.assertCallbackRecords([ | |
1410 { object: array, name: '3', type: 'add' }, | |
1411 { object: array, name: 'length', type: 'update', oldValue: 3 }, | |
1412 { object: array, name: '1', type: 'update', oldValue: 2 }, | |
1413 { object: array, name: '2', type: 'update', oldValue: 3 }, | |
1414 ]); | |
1415 | |
1416 // Sort | |
1417 reset(); | |
1418 var array = [3, 2, 1]; | |
1419 Object.observe(array, observer.callback); | |
1420 array.sort(); | |
1421 assertEquals(1, array[0]); | |
1422 assertEquals(2, array[1]); | |
1423 assertEquals(3, array[2]); | |
1424 Object.deliverChangeRecords(observer.callback); | |
1425 observer.assertCallbackRecords([ | |
1426 { object: array, name: '1', type: 'update', oldValue: 2 }, | |
1427 { object: array, name: '0', type: 'update', oldValue: 3 }, | |
1428 { object: array, name: '2', type: 'update', oldValue: 1 }, | |
1429 { object: array, name: '1', type: 'update', oldValue: 3 }, | |
1430 { object: array, name: '0', type: 'update', oldValue: 2 }, | |
1431 ]); | |
1432 | |
1433 // Splice emitted after Array mutation methods | |
1434 function MockArray(initial, observer) { | |
1435 for (var i = 0; i < initial.length; i++) | |
1436 this[i] = initial[i]; | |
1437 | |
1438 this.length_ = initial.length; | |
1439 this.observer = observer; | |
1440 } | |
1441 MockArray.prototype = { | |
1442 set length(length) { | |
1443 Object.getNotifier(this).notify({ type: 'lengthChange' }); | |
1444 this.length_ = length; | |
1445 Object.observe(this, this.observer.callback, ['splice']); | |
1446 }, | |
1447 get length() { | |
1448 return this.length_; | |
1449 } | |
1450 } | |
1451 | |
1452 reset(); | |
1453 var array = new MockArray([], observer); | |
1454 Object.observe(array, observer.callback, ['lengthChange']); | |
1455 Array.prototype.push.call(array, 1); | |
1456 Object.deliverChangeRecords(observer.callback); | |
1457 observer.assertCallbackRecords([ | |
1458 { object: array, type: 'lengthChange' }, | |
1459 { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }, | |
1460 ]); | |
1461 | |
1462 reset(); | |
1463 var array = new MockArray([1], observer); | |
1464 Object.observe(array, observer.callback, ['lengthChange']); | |
1465 Array.prototype.pop.call(array); | |
1466 Object.deliverChangeRecords(observer.callback); | |
1467 observer.assertCallbackRecords([ | |
1468 { object: array, type: 'lengthChange' }, | |
1469 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }, | |
1470 ]); | |
1471 | |
1472 reset(); | |
1473 var array = new MockArray([1], observer); | |
1474 Object.observe(array, observer.callback, ['lengthChange']); | |
1475 Array.prototype.shift.call(array); | |
1476 Object.deliverChangeRecords(observer.callback); | |
1477 observer.assertCallbackRecords([ | |
1478 { object: array, type: 'lengthChange' }, | |
1479 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }, | |
1480 ]); | |
1481 | |
1482 reset(); | |
1483 var array = new MockArray([], observer); | |
1484 Object.observe(array, observer.callback, ['lengthChange']); | |
1485 Array.prototype.unshift.call(array, 1); | |
1486 Object.deliverChangeRecords(observer.callback); | |
1487 observer.assertCallbackRecords([ | |
1488 { object: array, type: 'lengthChange' }, | |
1489 { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }, | |
1490 ]); | |
1491 | |
1492 reset(); | |
1493 var array = new MockArray([0, 1, 2], observer); | |
1494 Object.observe(array, observer.callback, ['lengthChange']); | |
1495 Array.prototype.splice.call(array, 1, 1); | |
1496 Object.deliverChangeRecords(observer.callback); | |
1497 observer.assertCallbackRecords([ | |
1498 { object: array, type: 'lengthChange' }, | |
1499 { object: array, type: 'splice', index: 1, removed: [1], addedCount: 0 }, | |
1500 ]); | |
1501 | |
1502 // | |
1503 // === PLAIN OBJECTS === | |
1504 // | |
1505 // Push | |
1506 reset() | |
1507 var array = {0: 1, 1: 2, length: 2} | |
1508 Object.observe(array, observer.callback); | |
1509 Array.prototype.push.call(array, 3, 4); | |
1510 Object.deliverChangeRecords(observer.callback); | |
1511 observer.assertCallbackRecords([ | |
1512 { object: array, name: '2', type: 'add' }, | |
1513 { object: array, name: '3', type: 'add' }, | |
1514 { object: array, name: 'length', type: 'update', oldValue: 2 }, | |
1515 ]); | |
1516 | |
1517 // Pop | |
1518 reset(); | |
1519 var array = [1, 2]; | |
1520 Object.observe(array, observer.callback); | |
1521 Array.observe(array, observer2.callback); | |
1522 array.pop(); | |
1523 array.pop(); | |
1524 array.pop(); | |
1525 Object.deliverChangeRecords(observer.callback); | |
1526 observer.assertCallbackRecords([ | |
1527 { object: array, name: '1', type: 'delete', oldValue: 2 }, | |
1528 { object: array, name: 'length', type: 'update', oldValue: 2 }, | |
1529 { object: array, name: '0', type: 'delete', oldValue: 1 }, | |
1530 { object: array, name: 'length', type: 'update', oldValue: 1 }, | |
1531 ]); | |
1532 Object.deliverChangeRecords(observer2.callback); | |
1533 observer2.assertCallbackRecords([ | |
1534 { object: array, type: 'splice', index: 1, removed: [2], addedCount: 0 }, | |
1535 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 } | |
1536 ]); | |
1537 | |
1538 // Shift | |
1539 reset(); | |
1540 var array = [1, 2]; | |
1541 Object.observe(array, observer.callback); | |
1542 Array.observe(array, observer2.callback); | |
1543 array.shift(); | |
1544 array.shift(); | |
1545 array.shift(); | |
1546 Object.deliverChangeRecords(observer.callback); | |
1547 observer.assertCallbackRecords([ | |
1548 { object: array, name: '0', type: 'update', oldValue: 1 }, | |
1549 { object: array, name: '1', type: 'delete', oldValue: 2 }, | |
1550 { object: array, name: 'length', type: 'update', oldValue: 2 }, | |
1551 { object: array, name: '0', type: 'delete', oldValue: 2 }, | |
1552 { object: array, name: 'length', type: 'update', oldValue: 1 }, | |
1553 ]); | |
1554 Object.deliverChangeRecords(observer2.callback); | |
1555 observer2.assertCallbackRecords([ | |
1556 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }, | |
1557 { object: array, type: 'splice', index: 0, removed: [2], addedCount: 0 } | |
1558 ]); | |
1559 | |
1560 // Unshift | |
1561 reset(); | |
1562 var array = [1, 2]; | |
1563 Object.observe(array, observer.callback); | |
1564 Array.observe(array, observer2.callback); | |
1565 array.unshift(3, 4); | |
1566 array.unshift(5); | |
1567 Object.deliverChangeRecords(observer.callback); | |
1568 observer.assertCallbackRecords([ | |
1569 { object: array, name: '3', type: 'add' }, | |
1570 { object: array, name: 'length', type: 'update', oldValue: 2 }, | |
1571 { object: array, name: '2', type: 'add' }, | |
1572 { object: array, name: '0', type: 'update', oldValue: 1 }, | |
1573 { object: array, name: '1', type: 'update', oldValue: 2 }, | |
1574 { object: array, name: '4', type: 'add' }, | |
1575 { object: array, name: 'length', type: 'update', oldValue: 4 }, | |
1576 { object: array, name: '3', type: 'update', oldValue: 2 }, | |
1577 { object: array, name: '2', type: 'update', oldValue: 1 }, | |
1578 { object: array, name: '1', type: 'update', oldValue: 4 }, | |
1579 { object: array, name: '0', type: 'update', oldValue: 3 }, | |
1580 ]); | |
1581 Object.deliverChangeRecords(observer2.callback); | |
1582 observer2.assertCallbackRecords([ | |
1583 { object: array, type: 'splice', index: 0, removed: [], addedCount: 2 }, | |
1584 { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 } | |
1585 ]); | |
1586 | |
1587 // Splice | |
1588 reset(); | |
1589 var array = [1, 2, 3]; | |
1590 Object.observe(array, observer.callback); | |
1591 Array.observe(array, observer2.callback); | |
1592 array.splice(1, 0, 4, 5); // 1 4 5 2 3 | |
1593 array.splice(0, 2); // 5 2 3 | |
1594 array.splice(1, 2, 6, 7); // 5 6 7 | |
1595 array.splice(2, 0); | |
1596 Object.deliverChangeRecords(observer.callback); | |
1597 observer.assertCallbackRecords([ | |
1598 { object: array, name: '4', type: 'add' }, | |
1599 { object: array, name: 'length', type: 'update', oldValue: 3 }, | |
1600 { object: array, name: '3', type: 'add' }, | |
1601 { object: array, name: '1', type: 'update', oldValue: 2 }, | |
1602 { object: array, name: '2', type: 'update', oldValue: 3 }, | |
1603 | |
1604 { object: array, name: '0', type: 'update', oldValue: 1 }, | |
1605 { object: array, name: '1', type: 'update', oldValue: 4 }, | |
1606 { object: array, name: '2', type: 'update', oldValue: 5 }, | |
1607 { object: array, name: '4', type: 'delete', oldValue: 3 }, | |
1608 { object: array, name: '3', type: 'delete', oldValue: 2 }, | |
1609 { object: array, name: 'length', type: 'update', oldValue: 5 }, | |
1610 | |
1611 { object: array, name: '1', type: 'update', oldValue: 2 }, | |
1612 { object: array, name: '2', type: 'update', oldValue: 3 }, | |
1613 ]); | |
1614 Object.deliverChangeRecords(observer2.callback); | |
1615 observer2.assertCallbackRecords([ | |
1616 { object: array, type: 'splice', index: 1, removed: [], addedCount: 2 }, | |
1617 { object: array, type: 'splice', index: 0, removed: [1, 4], addedCount: 0 }, | |
1618 { object: array, type: 'splice', index: 1, removed: [2, 3], addedCount: 2 }, | |
1619 ]); | |
1620 | |
1621 // Exercise StoreIC_ArrayLength | |
1622 reset(); | |
1623 var dummy = {}; | |
1624 Object.observe(dummy, observer.callback); | |
1625 Object.unobserve(dummy, observer.callback); | |
1626 var array = [0]; | |
1627 Object.observe(array, observer.callback); | |
1628 array.splice(0, 1); | |
1629 Object.deliverChangeRecords(observer.callback); | |
1630 observer.assertCallbackRecords([ | |
1631 { object: array, name: '0', type: 'delete', oldValue: 0 }, | |
1632 { object: array, name: 'length', type: 'update', oldValue: 1}, | |
1633 ]); | |
1634 | |
1635 | |
1636 // __proto__ | |
1637 reset(); | |
1638 var obj = {}; | |
1639 Object.observe(obj, observer.callback); | |
1640 var p = {foo: 'yes'}; | |
1641 var q = {bar: 'no'}; | |
1642 obj.__proto__ = p; | |
1643 obj.__proto__ = p; // ignored | |
1644 obj.__proto__ = null; | |
1645 obj.__proto__ = q; // the __proto__ accessor is gone | |
1646 // TODO(adamk): Add tests for objects with hidden prototypes | |
1647 // once we support observing the global object. | |
1648 Object.deliverChangeRecords(observer.callback); | |
1649 observer.assertCallbackRecords([ | |
1650 { object: obj, name: '__proto__', type: 'setPrototype', | |
1651 oldValue: Object.prototype }, | |
1652 { object: obj, name: '__proto__', type: 'setPrototype', oldValue: p }, | |
1653 { object: obj, name: '__proto__', type: 'add' }, | |
1654 ]); | |
1655 | |
1656 | |
1657 // Function.prototype | |
1658 reset(); | |
1659 var fun = function(){}; | |
1660 Object.observe(fun, observer.callback); | |
1661 var myproto = {foo: 'bar'}; | |
1662 fun.prototype = myproto; | |
1663 fun.prototype = 7; | |
1664 fun.prototype = 7; // ignored | |
1665 Object.defineProperty(fun, 'prototype', {value: 8}); | |
1666 Object.deliverChangeRecords(observer.callback); | |
1667 observer.assertRecordCount(3); | |
1668 // Manually examine the first record in order to test | |
1669 // lazy creation of oldValue | |
1670 assertSame(fun, observer.records[0].object); | |
1671 assertEquals('prototype', observer.records[0].name); | |
1672 assertEquals('update', observer.records[0].type); | |
1673 // The only existing reference to the oldValue object is in this | |
1674 // record, so to test that lazy creation happened correctly | |
1675 // we compare its constructor to our function (one of the invariants | |
1676 // ensured when creating an object via AllocateFunctionPrototype). | |
1677 assertSame(fun, observer.records[0].oldValue.constructor); | |
1678 observer.records.splice(0, 1); | |
1679 observer.assertCallbackRecords([ | |
1680 { object: fun, name: 'prototype', type: 'update', oldValue: myproto }, | |
1681 { object: fun, name: 'prototype', type: 'update', oldValue: 7 }, | |
1682 ]); | |
1683 | |
1684 // Function.prototype should not be observable except on the object itself | |
1685 reset(); | |
1686 var fun = function(){}; | |
1687 var obj = { __proto__: fun }; | |
1688 Object.observe(obj, observer.callback); | |
1689 obj.prototype = 7; | |
1690 Object.deliverChangeRecords(observer.callback); | |
1691 observer.assertRecordCount(1); | |
1692 observer.assertCallbackRecords([ | |
1693 { object: obj, name: 'prototype', type: 'add' }, | |
1694 ]); | |
1695 | |
1696 // Check that changes in observation status are detected in all IC states and | |
1697 // in optimized code, especially in cases usually using fast elements. | |
1698 var mutation = [ | |
1699 "a[i] = v", | |
1700 "a[i] ? ++a[i] : a[i] = v", | |
1701 "a[i] ? a[i]++ : a[i] = v", | |
1702 "a[i] ? a[i] += 1 : a[i] = v", | |
1703 "a[i] ? a[i] -= -1 : a[i] = v", | |
1704 ]; | |
1705 | |
1706 var props = [1, "1", "a"]; | |
1707 | |
1708 function TestFastElements(prop, mutation, prepopulate, polymorphic, optimize) { | |
1709 var setElement = eval( | |
1710 "(function setElement(a, i, v) { " + mutation + "; " + | |
1711 "/* " + [].join.call(arguments, " ") + " */" + | |
1712 "})" | |
1713 ); | |
1714 print("TestFastElements:", setElement); | |
1715 | |
1716 var arr = prepopulate ? [1, 2, 3, 4, 5] : [0]; | |
1717 if (prepopulate) arr[prop] = 2; // for non-element case | |
1718 setElement(arr, prop, 3); | |
1719 setElement(arr, prop, 4); | |
1720 if (polymorphic) setElement(["M", "i", "l", "n", "e", "r"], 0, "m"); | |
1721 if (optimize) %OptimizeFunctionOnNextCall(setElement); | |
1722 setElement(arr, prop, 5); | |
1723 | |
1724 reset(); | |
1725 Object.observe(arr, observer.callback); | |
1726 setElement(arr, prop, 989898); | |
1727 Object.deliverChangeRecords(observer.callback); | |
1728 observer.assertCallbackRecords([ | |
1729 { object: arr, name: "" + prop, type: 'update', oldValue: 5 } | |
1730 ]); | |
1731 } | |
1732 | |
1733 for (var b1 = 0; b1 < 2; ++b1) | |
1734 for (var b2 = 0; b2 < 2; ++b2) | |
1735 for (var b3 = 0; b3 < 2; ++b3) | |
1736 for (var i in props) | |
1737 for (var j in mutation) | |
1738 TestFastElements(props[i], mutation[j], b1 != 0, b2 != 0, b3 != 0); | |
1739 | |
1740 | |
1741 var mutation = [ | |
1742 "a.length = v", | |
1743 "a.length += newSize - oldSize", | |
1744 "a.length -= oldSize - newSize", | |
1745 ]; | |
1746 | |
1747 var mutationByIncr = [ | |
1748 "++a.length", | |
1749 "a.length++", | |
1750 ]; | |
1751 | |
1752 function TestFastElementsLength( | |
1753 mutation, polymorphic, optimize, oldSize, newSize) { | |
1754 var setLength = eval( | |
1755 "(function setLength(a, v) { " + mutation + "; " + | |
1756 "/* " + [].join.call(arguments, " ") + " */" | |
1757 + "})" | |
1758 ); | |
1759 print("TestFastElementsLength:", setLength); | |
1760 | |
1761 function array(n) { | |
1762 var arr = new Array(n); | |
1763 for (var i = 0; i < n; ++i) arr[i] = i; | |
1764 return arr; | |
1765 } | |
1766 | |
1767 setLength(array(oldSize), newSize); | |
1768 setLength(array(oldSize), newSize); | |
1769 if (polymorphic) setLength(array(oldSize).map(isNaN), newSize); | |
1770 if (optimize) %OptimizeFunctionOnNextCall(setLength); | |
1771 setLength(array(oldSize), newSize); | |
1772 | |
1773 reset(); | |
1774 var arr = array(oldSize); | |
1775 Object.observe(arr, observer.callback); | |
1776 setLength(arr, newSize); | |
1777 Object.deliverChangeRecords(observer.callback); | |
1778 if (oldSize === newSize) { | |
1779 observer.assertNotCalled(); | |
1780 } else { | |
1781 var count = oldSize > newSize ? oldSize - newSize : 0; | |
1782 observer.assertRecordCount(count + 1); | |
1783 var lengthRecord = observer.records[count]; | |
1784 assertSame(arr, lengthRecord.object); | |
1785 assertEquals('length', lengthRecord.name); | |
1786 assertEquals('update', lengthRecord.type); | |
1787 assertSame(oldSize, lengthRecord.oldValue); | |
1788 } | |
1789 } | |
1790 | |
1791 for (var b1 = 0; b1 < 2; ++b1) | |
1792 for (var b2 = 0; b2 < 2; ++b2) | |
1793 for (var n1 = 0; n1 < 3; ++n1) | |
1794 for (var n2 = 0; n2 < 3; ++n2) | |
1795 for (var i in mutation) | |
1796 TestFastElementsLength(mutation[i], b1 != 0, b2 != 0, 20*n1, 20*n2); | |
1797 | |
1798 for (var b1 = 0; b1 < 2; ++b1) | |
1799 for (var b2 = 0; b2 < 2; ++b2) | |
1800 for (var n = 0; n < 3; ++n) | |
1801 for (var i in mutationByIncr) | |
1802 TestFastElementsLength(mutationByIncr[i], b1 != 0, b2 != 0, 7*n, 7*n+1); | |
1803 | |
1804 | |
1805 (function TestFunctionName() { | |
1806 reset(); | |
1807 | |
1808 function fun() {} | |
1809 Object.observe(fun, observer.callback); | |
1810 fun.name = 'x'; // No change. Not writable. | |
1811 Object.defineProperty(fun, 'name', {value: 'a'}); | |
1812 Object.defineProperty(fun, 'name', {writable: true}); | |
1813 fun.name = 'b'; | |
1814 delete fun.name; | |
1815 fun.name = 'x'; // No change. Function.prototype.name is non writable | |
1816 Object.defineProperty(Function.prototype, 'name', {writable: true}); | |
1817 fun.name = 'c'; | |
1818 fun.name = 'c'; // Same, no update. | |
1819 Object.deliverChangeRecords(observer.callback); | |
1820 observer.assertCallbackRecords([ | |
1821 { object: fun, type: 'update', name: 'name', oldValue: 'fun' }, | |
1822 { object: fun, type: 'reconfigure', name: 'name'}, | |
1823 { object: fun, type: 'update', name: 'name', oldValue: 'a' }, | |
1824 { object: fun, type: 'delete', name: 'name', oldValue: 'b' }, | |
1825 { object: fun, type: 'add', name: 'name' }, | |
1826 ]); | |
1827 })(); | |
1828 | |
1829 | |
1830 (function TestFunctionLength() { | |
1831 reset(); | |
1832 | |
1833 function fun(x) {} | |
1834 Object.observe(fun, observer.callback); | |
1835 fun.length = 'x'; // No change. Not writable. | |
1836 Object.defineProperty(fun, 'length', {value: 'a'}); | |
1837 Object.defineProperty(fun, 'length', {writable: true}); | |
1838 fun.length = 'b'; | |
1839 delete fun.length; | |
1840 fun.length = 'x'; // No change. Function.prototype.length is non writable | |
1841 Object.defineProperty(Function.prototype, 'length', {writable: true}); | |
1842 fun.length = 'c'; | |
1843 fun.length = 'c'; // Same, no update. | |
1844 Object.deliverChangeRecords(observer.callback); | |
1845 observer.assertCallbackRecords([ | |
1846 { object: fun, type: 'update', name: 'length', oldValue: 1 }, | |
1847 { object: fun, type: 'reconfigure', name: 'length'}, | |
1848 { object: fun, type: 'update', name: 'length', oldValue: 'a' }, | |
1849 { object: fun, type: 'delete', name: 'length', oldValue: 'b' }, | |
1850 { object: fun, type: 'add', name: 'length' }, | |
1851 ]); | |
1852 })(); | |
1853 | |
1854 | |
1855 (function TestObserveInvalidAcceptMessage() { | |
1856 var ex; | |
1857 try { | |
1858 Object.observe({}, function(){}, "not an object"); | |
1859 } catch (e) { | |
1860 ex = e; | |
1861 } | |
1862 assertInstanceof(ex, TypeError); | |
1863 assertEquals("Third argument to Object.observe must be an array of strings.", | |
1864 ex.message); | |
1865 })() | |
OLD | NEW |