5af205eadf88f894685efda34256d2acb4800b50
[platform/upstream/nodejs.git] / deps / v8 / test / mjsunit / es7 / object-observe.js
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-proxies
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(){}); }, TypeError);
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: 'reconfigure', name: 'a' },
354   { object: obj, type: 'reconfigure', name: 'b' },
355   { object: obj, type: 'reconfigure', name: 'c' },
356   { object: obj, type: 'preventExtensions' },
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: 'reconfigure', name: 'a' },
391   { object: obj, type: 'reconfigure', name: 'b' },
392   { object: obj, type: 'preventExtensions' },
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 }
1147
1148 for (var i in objects) for (var j in properties) {
1149   var obj = objects[i];
1150   var prop = properties[j];
1151   if (blacklisted(obj, prop)) continue;
1152   var desc = Object.getOwnPropertyDescriptor(obj, prop);
1153   print("***", typeof obj, stringifyNoThrow(obj), prop);
1154   if (!desc || desc.configurable)
1155     TestObserveConfigurable(obj, prop);
1156   else if (desc.writable)
1157     TestObserveNonConfigurable(obj, prop, desc);
1158 }
1159
1160
1161 // Observing array length (including truncation)
1162 reset();
1163 var arr = ['a', 'b', 'c', 'd'];
1164 var arr2 = ['alpha', 'beta'];
1165 var arr3 = ['hello'];
1166 arr3[2] = 'goodbye';
1167 arr3.length = 6;
1168 Object.defineProperty(arr, '0', {configurable: false});
1169 Object.defineProperty(arr, '2', {get: function(){}});
1170 Object.defineProperty(arr2, '0', {get: function(){}, configurable: false});
1171 Object.observe(arr, observer.callback);
1172 Array.observe(arr, observer2.callback);
1173 Object.observe(arr2, observer.callback);
1174 Array.observe(arr2, observer2.callback);
1175 Object.observe(arr3, observer.callback);
1176 Array.observe(arr3, observer2.callback);
1177 arr.length = 2;
1178 arr.length = 0;
1179 arr.length = 10;
1180 Object.defineProperty(arr, 'length', {writable: false});
1181 arr2.length = 0;
1182 arr2.length = 1; // no change expected
1183 Object.defineProperty(arr2, 'length', {value: 1, writable: false});
1184 arr3.length = 0;
1185 ++arr3.length;
1186 arr3.length++;
1187 arr3.length /= 2;
1188 Object.defineProperty(arr3, 'length', {value: 5});
1189 arr3[4] = 5;
1190 Object.defineProperty(arr3, 'length', {value: 1, writable: false});
1191 Object.deliverChangeRecords(observer.callback);
1192 observer.assertCallbackRecords([
1193   { object: arr, name: '3', type: 'delete', oldValue: 'd' },
1194   { object: arr, name: '2', type: 'delete' },
1195   { object: arr, name: 'length', type: 'update', oldValue: 4 },
1196   { object: arr, name: '1', type: 'delete', oldValue: 'b' },
1197   { object: arr, name: 'length', type: 'update', oldValue: 2 },
1198   { object: arr, name: 'length', type: 'update', oldValue: 1 },
1199   { object: arr, name: 'length', type: 'reconfigure' },
1200   { object: arr2, name: '1', type: 'delete', oldValue: 'beta' },
1201   { object: arr2, name: 'length', type: 'update', oldValue: 2 },
1202   { object: arr2, name: 'length', type: 'reconfigure' },
1203   { object: arr3, name: '2', type: 'delete', oldValue: 'goodbye' },
1204   { object: arr3, name: '0', type: 'delete', oldValue: 'hello' },
1205   { object: arr3, name: 'length', type: 'update', oldValue: 6 },
1206   { object: arr3, name: 'length', type: 'update', oldValue: 0 },
1207   { object: arr3, name: 'length', type: 'update', oldValue: 1 },
1208   { object: arr3, name: 'length', type: 'update', oldValue: 2 },
1209   { object: arr3, name: 'length', type: 'update', oldValue: 1 },
1210   { object: arr3, name: '4', type: 'add' },
1211   { object: arr3, name: '4', type: 'delete', oldValue: 5 },
1212   // TODO(rafaelw): It breaks spec compliance to get two records here.
1213   // When the TODO in v8natives.js::DefineArrayProperty is addressed
1214   // which prevents DefineProperty from over-writing the magic length
1215   // property, these will collapse into a single record.
1216   { object: arr3, name: 'length', type: 'update', oldValue: 5 },
1217   { object: arr3, name: 'length', type: 'reconfigure' }
1218 ]);
1219 Object.deliverChangeRecords(observer2.callback);
1220 observer2.assertCallbackRecords([
1221   { object: arr, type: 'splice', index: 2, removed: [, 'd'], addedCount: 0 },
1222   { object: arr, type: 'splice', index: 1, removed: ['b'], addedCount: 0 },
1223   { object: arr, type: 'splice', index: 1, removed: [], addedCount: 9 },
1224   { object: arr2, type: 'splice', index: 1, removed: ['beta'], addedCount: 0 },
1225   { object: arr3, type: 'splice', index: 0, removed: ['hello',, 'goodbye',,,,], addedCount: 0 },
1226   { object: arr3, type: 'splice', index: 0, removed: [], addedCount: 1 },
1227   { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 1 },
1228   { object: arr3, type: 'splice', index: 1, removed: [,], addedCount: 0 },
1229   { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 4 },
1230   { object: arr3, name: '4', type: 'add' },
1231   { object: arr3, type: 'splice', index: 1, removed: [,,,5], addedCount: 0 }
1232 ]);
1233
1234
1235 // Updating length on large (slow) array
1236 reset();
1237 var slow_arr = %NormalizeElements([]);
1238 slow_arr[500000000] = 'hello';
1239 slow_arr.length = 1000000000;
1240 Object.observe(slow_arr, observer.callback);
1241 var spliceRecords;
1242 function slowSpliceCallback(records) {
1243   spliceRecords = records;
1244 }
1245 Array.observe(slow_arr, slowSpliceCallback);
1246 slow_arr.length = 100;
1247 Object.deliverChangeRecords(observer.callback);
1248 observer.assertCallbackRecords([
1249   { object: slow_arr, name: '500000000', type: 'delete', oldValue: 'hello' },
1250   { object: slow_arr, name: 'length', type: 'update', oldValue: 1000000000 },
1251 ]);
1252 Object.deliverChangeRecords(slowSpliceCallback);
1253 assertEquals(spliceRecords.length, 1);
1254 // Have to custom assert this splice record because the removed array is huge.
1255 var splice = spliceRecords[0];
1256 assertSame(splice.object, slow_arr);
1257 assertEquals(splice.type, 'splice');
1258 assertEquals(splice.index, 100);
1259 assertEquals(splice.addedCount, 0);
1260 var array_keys = %GetArrayKeys(splice.removed, splice.removed.length);
1261 assertEquals(array_keys.length, 1);
1262 assertEquals(array_keys[0], 499999900);
1263 assertEquals(splice.removed[499999900], 'hello');
1264 assertEquals(splice.removed.length, 999999900);
1265
1266
1267 // Assignments in loops (checking different IC states).
1268 reset();
1269 var obj = {};
1270 Object.observe(obj, observer.callback);
1271 for (var i = 0; i < 5; i++) {
1272   obj["a" + i] = i;
1273 }
1274 Object.deliverChangeRecords(observer.callback);
1275 observer.assertCallbackRecords([
1276   { object: obj, name: "a0", type: "add" },
1277   { object: obj, name: "a1", type: "add" },
1278   { object: obj, name: "a2", type: "add" },
1279   { object: obj, name: "a3", type: "add" },
1280   { object: obj, name: "a4", type: "add" },
1281 ]);
1282
1283 reset();
1284 var obj = {};
1285 Object.observe(obj, observer.callback);
1286 for (var i = 0; i < 5; i++) {
1287   obj[i] = i;
1288 }
1289 Object.deliverChangeRecords(observer.callback);
1290 observer.assertCallbackRecords([
1291   { object: obj, name: "0", type: "add" },
1292   { object: obj, name: "1", type: "add" },
1293   { object: obj, name: "2", type: "add" },
1294   { object: obj, name: "3", type: "add" },
1295   { object: obj, name: "4", type: "add" },
1296 ]);
1297
1298
1299 // Adding elements past the end of an array should notify on length for
1300 // Object.observe and emit "splices" for Array.observe.
1301 reset();
1302 var arr = [1, 2, 3];
1303 Object.observe(arr, observer.callback);
1304 Array.observe(arr, observer2.callback);
1305 arr[3] = 10;
1306 arr[100] = 20;
1307 Object.defineProperty(arr, '200', {value: 7});
1308 Object.defineProperty(arr, '400', {get: function(){}});
1309 arr[50] = 30; // no length change expected
1310 Object.deliverChangeRecords(observer.callback);
1311 observer.assertCallbackRecords([
1312   { object: arr, name: '3', type: 'add' },
1313   { object: arr, name: 'length', type: 'update', oldValue: 3 },
1314   { object: arr, name: '100', type: 'add' },
1315   { object: arr, name: 'length', type: 'update', oldValue: 4 },
1316   { object: arr, name: '200', type: 'add' },
1317   { object: arr, name: 'length', type: 'update', oldValue: 101 },
1318   { object: arr, name: '400', type: 'add' },
1319   { object: arr, name: 'length', type: 'update', oldValue: 201 },
1320   { object: arr, name: '50', type: 'add' },
1321 ]);
1322 Object.deliverChangeRecords(observer2.callback);
1323 observer2.assertCallbackRecords([
1324   { object: arr, type: 'splice', index: 3, removed: [], addedCount: 1 },
1325   { object: arr, type: 'splice', index: 4, removed: [], addedCount: 97 },
1326   { object: arr, type: 'splice', index: 101, removed: [], addedCount: 100 },
1327   { object: arr, type: 'splice', index: 201, removed: [], addedCount: 200 },
1328   { object: arr, type: 'add', name: '50' },
1329 ]);
1330
1331
1332 // Tests for array methods, first on arrays and then on plain objects
1333 //
1334 // === ARRAYS ===
1335 //
1336 // Push
1337 reset();
1338 var array = [1, 2];
1339 Object.observe(array, observer.callback);
1340 Array.observe(array, observer2.callback);
1341 array.push(3, 4);
1342 array.push(5);
1343 Object.deliverChangeRecords(observer.callback);
1344 observer.assertCallbackRecords([
1345   { object: array, name: '2', type: 'add' },
1346   { object: array, name: 'length', type: 'update', oldValue: 2 },
1347   { object: array, name: '3', type: 'add' },
1348   { object: array, name: 'length', type: 'update', oldValue: 3 },
1349   { object: array, name: '4', type: 'add' },
1350   { object: array, name: 'length', type: 'update', oldValue: 4 },
1351 ]);
1352 Object.deliverChangeRecords(observer2.callback);
1353 observer2.assertCallbackRecords([
1354   { object: array, type: 'splice', index: 2, removed: [], addedCount: 2 },
1355   { object: array, type: 'splice', index: 4, removed: [], addedCount: 1 }
1356 ]);
1357
1358 // Pop
1359 reset();
1360 var array = [1, 2];
1361 Object.observe(array, observer.callback);
1362 array.pop();
1363 array.pop();
1364 Object.deliverChangeRecords(observer.callback);
1365 observer.assertCallbackRecords([
1366   { object: array, name: '1', type: 'delete', oldValue: 2 },
1367   { object: array, name: 'length', type: 'update', oldValue: 2 },
1368   { object: array, name: '0', type: 'delete', oldValue: 1 },
1369   { object: array, name: 'length', type: 'update', oldValue: 1 },
1370 ]);
1371
1372 // Shift
1373 reset();
1374 var array = [1, 2];
1375 Object.observe(array, observer.callback);
1376 array.shift();
1377 array.shift();
1378 Object.deliverChangeRecords(observer.callback);
1379 observer.assertCallbackRecords([
1380   { object: array, name: '0', type: 'update', oldValue: 1 },
1381   { object: array, name: '1', type: 'delete', oldValue: 2 },
1382   { object: array, name: 'length', type: 'update', oldValue: 2 },
1383   { object: array, name: '0', type: 'delete', oldValue: 2 },
1384   { object: array, name: 'length', type: 'update', oldValue: 1 },
1385 ]);
1386
1387 // Unshift
1388 reset();
1389 var array = [1, 2];
1390 Object.observe(array, observer.callback);
1391 array.unshift(3, 4);
1392 Object.deliverChangeRecords(observer.callback);
1393 observer.assertCallbackRecords([
1394   { object: array, name: '3', type: 'add' },
1395   { object: array, name: 'length', type: 'update', oldValue: 2 },
1396   { object: array, name: '2', type: 'add' },
1397   { object: array, name: '0', type: 'update', oldValue: 1 },
1398   { object: array, name: '1', type: 'update', oldValue: 2 },
1399 ]);
1400
1401 // Splice
1402 reset();
1403 var array = [1, 2, 3];
1404 Object.observe(array, observer.callback);
1405 array.splice(1, 1, 4, 5);
1406 Object.deliverChangeRecords(observer.callback);
1407 observer.assertCallbackRecords([
1408   { object: array, name: '3', type: 'add' },
1409   { object: array, name: 'length', type: 'update', oldValue: 3 },
1410   { object: array, name: '1', type: 'update', oldValue: 2 },
1411   { object: array, name: '2', type: 'update', oldValue: 3 },
1412 ]);
1413
1414 // Sort
1415 reset();
1416 var array = [3, 2, 1];
1417 Object.observe(array, observer.callback);
1418 array.sort();
1419 assertEquals(1, array[0]);
1420 assertEquals(2, array[1]);
1421 assertEquals(3, array[2]);
1422 Object.deliverChangeRecords(observer.callback);
1423 observer.assertCallbackRecords([
1424   { object: array, name: '1', type: 'update', oldValue: 2 },
1425   { object: array, name: '0', type: 'update', oldValue: 3 },
1426   { object: array, name: '2', type: 'update', oldValue: 1 },
1427   { object: array, name: '1', type: 'update', oldValue: 3 },
1428   { object: array, name: '0', type: 'update', oldValue: 2 },
1429 ]);
1430
1431 // Splice emitted after Array mutation methods
1432 function MockArray(initial, observer) {
1433   for (var i = 0; i < initial.length; i++)
1434     this[i] = initial[i];
1435
1436   this.length_ = initial.length;
1437   this.observer = observer;
1438 }
1439 MockArray.prototype = {
1440   set length(length) {
1441     Object.getNotifier(this).notify({ type: 'lengthChange' });
1442     this.length_ = length;
1443     Object.observe(this, this.observer.callback, ['splice']);
1444   },
1445   get length() {
1446     return this.length_;
1447   }
1448 }
1449
1450 reset();
1451 var array = new MockArray([], observer);
1452 Object.observe(array, observer.callback, ['lengthChange']);
1453 Array.prototype.push.call(array, 1);
1454 Object.deliverChangeRecords(observer.callback);
1455 observer.assertCallbackRecords([
1456   { object: array, type: 'lengthChange' },
1457   { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 },
1458 ]);
1459
1460 reset();
1461 var array = new MockArray([1], observer);
1462 Object.observe(array, observer.callback, ['lengthChange']);
1463 Array.prototype.pop.call(array);
1464 Object.deliverChangeRecords(observer.callback);
1465 observer.assertCallbackRecords([
1466   { object: array, type: 'lengthChange' },
1467   { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
1468 ]);
1469
1470 reset();
1471 var array = new MockArray([1], observer);
1472 Object.observe(array, observer.callback, ['lengthChange']);
1473 Array.prototype.shift.call(array);
1474 Object.deliverChangeRecords(observer.callback);
1475 observer.assertCallbackRecords([
1476   { object: array, type: 'lengthChange' },
1477   { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
1478 ]);
1479
1480 reset();
1481 var array = new MockArray([], observer);
1482 Object.observe(array, observer.callback, ['lengthChange']);
1483 Array.prototype.unshift.call(array, 1);
1484 Object.deliverChangeRecords(observer.callback);
1485 observer.assertCallbackRecords([
1486   { object: array, type: 'lengthChange' },
1487   { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 },
1488 ]);
1489
1490 reset();
1491 var array = new MockArray([0, 1, 2], observer);
1492 Object.observe(array, observer.callback, ['lengthChange']);
1493 Array.prototype.splice.call(array, 1, 1);
1494 Object.deliverChangeRecords(observer.callback);
1495 observer.assertCallbackRecords([
1496   { object: array, type: 'lengthChange' },
1497   { object: array, type: 'splice', index: 1, removed: [1], addedCount: 0 },
1498 ]);
1499
1500 //
1501 // === PLAIN OBJECTS ===
1502 //
1503 // Push
1504 reset()
1505 var array = {0: 1, 1: 2, length: 2}
1506 Object.observe(array, observer.callback);
1507 Array.prototype.push.call(array, 3, 4);
1508 Object.deliverChangeRecords(observer.callback);
1509 observer.assertCallbackRecords([
1510   { object: array, name: '2', type: 'add' },
1511   { object: array, name: '3', type: 'add' },
1512   { object: array, name: 'length', type: 'update', oldValue: 2 },
1513 ]);
1514
1515 // Pop
1516 reset();
1517 var array = [1, 2];
1518 Object.observe(array, observer.callback);
1519 Array.observe(array, observer2.callback);
1520 array.pop();
1521 array.pop();
1522 array.pop();
1523 Object.deliverChangeRecords(observer.callback);
1524 observer.assertCallbackRecords([
1525   { object: array, name: '1', type: 'delete', oldValue: 2 },
1526   { object: array, name: 'length', type: 'update', oldValue: 2 },
1527   { object: array, name: '0', type: 'delete', oldValue: 1 },
1528   { object: array, name: 'length', type: 'update', oldValue: 1 },
1529 ]);
1530 Object.deliverChangeRecords(observer2.callback);
1531 observer2.assertCallbackRecords([
1532   { object: array, type: 'splice', index: 1, removed: [2], addedCount: 0 },
1533   { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }
1534 ]);
1535
1536 // Shift
1537 reset();
1538 var array = [1, 2];
1539 Object.observe(array, observer.callback);
1540 Array.observe(array, observer2.callback);
1541 array.shift();
1542 array.shift();
1543 array.shift();
1544 Object.deliverChangeRecords(observer.callback);
1545 observer.assertCallbackRecords([
1546   { object: array, name: '0', type: 'update', oldValue: 1 },
1547   { object: array, name: '1', type: 'delete', oldValue: 2 },
1548   { object: array, name: 'length', type: 'update', oldValue: 2 },
1549   { object: array, name: '0', type: 'delete', oldValue: 2 },
1550   { object: array, name: 'length', type: 'update', oldValue: 1 },
1551 ]);
1552 Object.deliverChangeRecords(observer2.callback);
1553 observer2.assertCallbackRecords([
1554   { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
1555   { object: array, type: 'splice', index: 0, removed: [2], addedCount: 0 }
1556 ]);
1557
1558 // Unshift
1559 reset();
1560 var array = [1, 2];
1561 Object.observe(array, observer.callback);
1562 Array.observe(array, observer2.callback);
1563 array.unshift(3, 4);
1564 array.unshift(5);
1565 Object.deliverChangeRecords(observer.callback);
1566 observer.assertCallbackRecords([
1567   { object: array, name: '3', type: 'add' },
1568   { object: array, name: 'length', type: 'update', oldValue: 2 },
1569   { object: array, name: '2', type: 'add' },
1570   { object: array, name: '0', type: 'update', oldValue: 1 },
1571   { object: array, name: '1', type: 'update', oldValue: 2 },
1572   { object: array, name: '4', type: 'add' },
1573   { object: array, name: 'length', type: 'update', oldValue: 4 },
1574   { object: array, name: '3', type: 'update', oldValue: 2 },
1575   { object: array, name: '2', type: 'update', oldValue: 1 },
1576   { object: array, name: '1', type: 'update', oldValue: 4 },
1577   { object: array, name: '0', type: 'update', oldValue: 3 },
1578 ]);
1579 Object.deliverChangeRecords(observer2.callback);
1580 observer2.assertCallbackRecords([
1581   { object: array, type: 'splice', index: 0, removed: [], addedCount: 2 },
1582   { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }
1583 ]);
1584
1585 // Splice
1586 reset();
1587 var array = [1, 2, 3];
1588 Object.observe(array, observer.callback);
1589 Array.observe(array, observer2.callback);
1590 array.splice(1, 0, 4, 5); // 1 4 5 2 3
1591 array.splice(0, 2); // 5 2 3
1592 array.splice(1, 2, 6, 7); // 5 6 7
1593 array.splice(2, 0);
1594 Object.deliverChangeRecords(observer.callback);
1595 observer.assertCallbackRecords([
1596   { object: array, name: '4', type: 'add' },
1597   { object: array, name: 'length', type: 'update', oldValue: 3 },
1598   { object: array, name: '3', type: 'add' },
1599   { object: array, name: '1', type: 'update', oldValue: 2 },
1600   { object: array, name: '2', type: 'update', oldValue: 3 },
1601
1602   { object: array, name: '0', type: 'update', oldValue: 1 },
1603   { object: array, name: '1', type: 'update', oldValue: 4 },
1604   { object: array, name: '2', type: 'update', oldValue: 5 },
1605   { object: array, name: '4', type: 'delete', oldValue: 3 },
1606   { object: array, name: '3', type: 'delete', oldValue: 2 },
1607   { object: array, name: 'length', type: 'update', oldValue: 5 },
1608
1609   { object: array, name: '1', type: 'update', oldValue: 2 },
1610   { object: array, name: '2', type: 'update', oldValue: 3 },
1611 ]);
1612 Object.deliverChangeRecords(observer2.callback);
1613 observer2.assertCallbackRecords([
1614   { object: array, type: 'splice', index: 1, removed: [], addedCount: 2 },
1615   { object: array, type: 'splice', index: 0, removed: [1, 4], addedCount: 0 },
1616   { object: array, type: 'splice', index: 1, removed: [2, 3], addedCount: 2 },
1617 ]);
1618
1619 // Exercise StoreIC_ArrayLength
1620 reset();
1621 var dummy = {};
1622 Object.observe(dummy, observer.callback);
1623 Object.unobserve(dummy, observer.callback);
1624 var array = [0];
1625 Object.observe(array, observer.callback);
1626 array.splice(0, 1);
1627 Object.deliverChangeRecords(observer.callback);
1628 observer.assertCallbackRecords([
1629   { object: array, name: '0', type: 'delete', oldValue: 0 },
1630   { object: array, name: 'length', type: 'update', oldValue: 1},
1631 ]);
1632
1633
1634 // __proto__
1635 reset();
1636 var obj = {};
1637 Object.observe(obj, observer.callback);
1638 var p = {foo: 'yes'};
1639 var q = {bar: 'no'};
1640 obj.__proto__ = p;
1641 obj.__proto__ = p;  // ignored
1642 obj.__proto__ = null;
1643 obj.__proto__ = q;  // the __proto__ accessor is gone
1644 // TODO(adamk): Add tests for objects with hidden prototypes
1645 // once we support observing the global object.
1646 Object.deliverChangeRecords(observer.callback);
1647 observer.assertCallbackRecords([
1648   { object: obj, name: '__proto__', type: 'setPrototype',
1649     oldValue: Object.prototype },
1650   { object: obj, name: '__proto__', type: 'setPrototype', oldValue: p },
1651   { object: obj, name: '__proto__', type: 'add' },
1652 ]);
1653
1654
1655 // Function.prototype
1656 reset();
1657 var fun = function(){};
1658 Object.observe(fun, observer.callback);
1659 var myproto = {foo: 'bar'};
1660 fun.prototype = myproto;
1661 fun.prototype = 7;
1662 fun.prototype = 7;  // ignored
1663 Object.defineProperty(fun, 'prototype', {value: 8});
1664 Object.deliverChangeRecords(observer.callback);
1665 observer.assertRecordCount(3);
1666 // Manually examine the first record in order to test
1667 // lazy creation of oldValue
1668 assertSame(fun, observer.records[0].object);
1669 assertEquals('prototype', observer.records[0].name);
1670 assertEquals('update', observer.records[0].type);
1671 // The only existing reference to the oldValue object is in this
1672 // record, so to test that lazy creation happened correctly
1673 // we compare its constructor to our function (one of the invariants
1674 // ensured when creating an object via AllocateFunctionPrototype).
1675 assertSame(fun, observer.records[0].oldValue.constructor);
1676 observer.records.splice(0, 1);
1677 observer.assertCallbackRecords([
1678   { object: fun, name: 'prototype', type: 'update', oldValue: myproto },
1679   { object: fun, name: 'prototype', type: 'update', oldValue: 7 },
1680 ]);
1681
1682 // Function.prototype should not be observable except on the object itself
1683 reset();
1684 var fun = function(){};
1685 var obj = { __proto__: fun };
1686 Object.observe(obj, observer.callback);
1687 obj.prototype = 7;
1688 Object.deliverChangeRecords(observer.callback);
1689 observer.assertRecordCount(1);
1690 observer.assertCallbackRecords([
1691   { object: obj, name: 'prototype', type: 'add' },
1692 ]);
1693
1694 // Check that changes in observation status are detected in all IC states and
1695 // in optimized code, especially in cases usually using fast elements.
1696 var mutation = [
1697   "a[i] = v",
1698   "a[i] ? ++a[i] : a[i] = v",
1699   "a[i] ? a[i]++ : a[i] = v",
1700   "a[i] ? a[i] += 1 : a[i] = v",
1701   "a[i] ? a[i] -= -1 : a[i] = v",
1702 ];
1703
1704 var props = [1, "1", "a"];
1705
1706 function TestFastElements(prop, mutation, prepopulate, polymorphic, optimize) {
1707   var setElement = eval(
1708     "(function setElement(a, i, v) { " + mutation + "; " +
1709     "/* " + [].join.call(arguments, " ") + " */" +
1710     "})"
1711   );
1712   print("TestFastElements:", setElement);
1713
1714   var arr = prepopulate ? [1, 2, 3, 4, 5] : [0];
1715   if (prepopulate) arr[prop] = 2;  // for non-element case
1716   setElement(arr, prop, 3);
1717   setElement(arr, prop, 4);
1718   if (polymorphic) setElement(["M", "i", "l", "n", "e", "r"], 0, "m");
1719   if (optimize) %OptimizeFunctionOnNextCall(setElement);
1720   setElement(arr, prop, 5);
1721
1722   reset();
1723   Object.observe(arr, observer.callback);
1724   setElement(arr, prop, 989898);
1725   Object.deliverChangeRecords(observer.callback);
1726   observer.assertCallbackRecords([
1727     { object: arr, name: "" + prop, type: 'update', oldValue: 5 }
1728   ]);
1729 }
1730
1731 for (var b1 = 0; b1 < 2; ++b1)
1732   for (var b2 = 0; b2 < 2; ++b2)
1733     for (var b3 = 0; b3 < 2; ++b3)
1734       for (var i in props)
1735         for (var j in mutation)
1736           TestFastElements(props[i], mutation[j], b1 != 0, b2 != 0, b3 != 0);
1737
1738
1739 var mutation = [
1740   "a.length = v",
1741   "a.length += newSize - oldSize",
1742   "a.length -= oldSize - newSize",
1743 ];
1744
1745 var mutationByIncr = [
1746   "++a.length",
1747   "a.length++",
1748 ];
1749
1750 function TestFastElementsLength(
1751   mutation, polymorphic, optimize, oldSize, newSize) {
1752   var setLength = eval(
1753     "(function setLength(a, v) { " + mutation + "; " +
1754     "/* " + [].join.call(arguments, " ") + " */"
1755     + "})"
1756   );
1757   print("TestFastElementsLength:", setLength);
1758
1759   function array(n) {
1760     var arr = new Array(n);
1761     for (var i = 0; i < n; ++i) arr[i] = i;
1762     return arr;
1763   }
1764
1765   setLength(array(oldSize), newSize);
1766   setLength(array(oldSize), newSize);
1767   if (polymorphic) setLength(array(oldSize).map(isNaN), newSize);
1768   if (optimize) %OptimizeFunctionOnNextCall(setLength);
1769   setLength(array(oldSize), newSize);
1770
1771   reset();
1772   var arr = array(oldSize);
1773   Object.observe(arr, observer.callback);
1774   setLength(arr, newSize);
1775   Object.deliverChangeRecords(observer.callback);
1776   if (oldSize === newSize) {
1777     observer.assertNotCalled();
1778   } else {
1779     var count = oldSize > newSize ? oldSize - newSize : 0;
1780     observer.assertRecordCount(count + 1);
1781     var lengthRecord = observer.records[count];
1782     assertSame(arr, lengthRecord.object);
1783     assertEquals('length', lengthRecord.name);
1784     assertEquals('update', lengthRecord.type);
1785     assertSame(oldSize, lengthRecord.oldValue);
1786   }
1787 }
1788
1789 for (var b1 = 0; b1 < 2; ++b1)
1790   for (var b2 = 0; b2 < 2; ++b2)
1791     for (var n1 = 0; n1 < 3; ++n1)
1792       for (var n2 = 0; n2 < 3; ++n2)
1793         for (var i in mutation)
1794           TestFastElementsLength(mutation[i], b1 != 0, b2 != 0, 20*n1, 20*n2);
1795
1796 for (var b1 = 0; b1 < 2; ++b1)
1797   for (var b2 = 0; b2 < 2; ++b2)
1798     for (var n = 0; n < 3; ++n)
1799       for (var i in mutationByIncr)
1800         TestFastElementsLength(mutationByIncr[i], b1 != 0, b2 != 0, 7*n, 7*n+1);