Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / 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-observation --harmony-proxies
29 // Flags: --harmony-collections --harmony-weak-collections
30 // Flags: --harmony-symbols --allow-natives-syntax
31
32 var allObservers = [];
33 function reset() {
34   allObservers.forEach(function(observer) { observer.reset(); });
35 }
36
37 function stringifyNoThrow(arg) {
38   try {
39     return JSON.stringify(arg);
40   } catch (e) {
41     return '{<circular reference>}';
42   }
43 }
44
45 function createObserver() {
46   "use strict";  // So that |this| in callback can be undefined.
47
48   var observer = {
49     records: undefined,
50     callbackCount: 0,
51     reset: function() {
52       this.records = undefined;
53       this.callbackCount = 0;
54     },
55     assertNotCalled: function() {
56       assertEquals(undefined, this.records);
57       assertEquals(0, this.callbackCount);
58     },
59     assertCalled: function() {
60       assertEquals(1, this.callbackCount);
61     },
62     assertRecordCount: function(count) {
63       this.assertCalled();
64       assertEquals(count, this.records.length);
65     },
66     assertCallbackRecords: function(recs) {
67       this.assertRecordCount(recs.length);
68       for (var i = 0; i < recs.length; i++) {
69         if ('name' in recs[i]) recs[i].name = String(recs[i].name);
70         print(i, stringifyNoThrow(this.records[i]), stringifyNoThrow(recs[i]));
71         assertSame(this.records[i].object, recs[i].object);
72         assertEquals('string', typeof recs[i].type);
73         assertPropertiesEqual(this.records[i], recs[i]);
74       }
75     }
76   };
77
78   observer.callback = function(r) {
79     assertEquals(undefined, this);
80     assertEquals('object', typeof r);
81     assertTrue(r instanceof Array)
82     observer.records = r;
83     observer.callbackCount++;
84   };
85
86   observer.reset();
87   allObservers.push(observer);
88   return observer;
89 }
90
91 var observer = createObserver();
92 var observer2 = createObserver();
93
94 assertEquals("function", typeof observer.callback);
95 assertEquals("function", typeof observer2.callback);
96
97 var obj = {};
98
99 function frozenFunction() {}
100 Object.freeze(frozenFunction);
101 var nonFunction = {};
102 var changeRecordWithAccessor = { type: 'foo' };
103 var recordCreated = false;
104 Object.defineProperty(changeRecordWithAccessor, 'name', {
105   get: function() {
106     recordCreated = true;
107     return "bar";
108   },
109   enumerable: true
110 })
111
112
113 // Object.observe
114 assertThrows(function() { Object.observe("non-object", observer.callback); },
115              TypeError);
116 assertThrows(function() { Object.observe(this, observer.callback); },
117              TypeError);
118 assertThrows(function() { Object.observe(obj, nonFunction); }, TypeError);
119 assertThrows(function() { Object.observe(obj, frozenFunction); }, TypeError);
120 assertEquals(obj, Object.observe(obj, observer.callback, [1]));
121 assertEquals(obj, Object.observe(obj, observer.callback, [true]));
122 assertEquals(obj, Object.observe(obj, observer.callback, ['foo', null]));
123 assertEquals(obj, Object.observe(obj, observer.callback, [undefined]));
124 assertEquals(obj, Object.observe(obj, observer.callback,
125              ['foo', 'bar', 'baz']));
126 assertEquals(obj, Object.observe(obj, observer.callback, []));
127 assertEquals(obj, Object.observe(obj, observer.callback, undefined));
128 assertEquals(obj, Object.observe(obj, observer.callback));
129
130 // Object.unobserve
131 assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError);
132 assertThrows(function() { Object.unobserve(this, observer.callback); },
133              TypeError);
134 assertThrows(function() { Object.unobserve(obj, nonFunction); }, TypeError);
135 assertEquals(obj, Object.unobserve(obj, observer.callback));
136
137
138 // Object.getNotifier
139 var notifier = Object.getNotifier(obj);
140 assertSame(notifier, Object.getNotifier(obj));
141 assertEquals(null, Object.getNotifier(Object.freeze({})));
142 assertThrows(function() { Object.getNotifier(this) }, TypeError);
143 assertFalse(notifier.hasOwnProperty('notify'));
144 assertEquals([], Object.keys(notifier));
145 var notifyDesc = Object.getOwnPropertyDescriptor(notifier.__proto__, 'notify');
146 assertTrue(notifyDesc.configurable);
147 assertTrue(notifyDesc.writable);
148 assertFalse(notifyDesc.enumerable);
149 assertThrows(function() { notifier.notify({}); }, TypeError);
150 assertThrows(function() { notifier.notify({ type: 4 }); }, TypeError);
151
152 assertThrows(function() { notifier.performChange(1, function(){}); }, TypeError);
153 assertThrows(function() { notifier.performChange(undefined, function(){}); }, TypeError);
154 assertThrows(function() { notifier.performChange('foo', undefined); }, TypeError);
155 assertThrows(function() { notifier.performChange('foo', 'bar'); }, TypeError);
156 var global = this;
157 notifier.performChange('foo', function() {
158   assertEquals(global, this);
159 });
160
161 var notify = notifier.notify;
162 assertThrows(function() { notify.call(undefined, { type: 'a' }); }, TypeError);
163 assertThrows(function() { notify.call(null, { type: 'a' }); }, TypeError);
164 assertThrows(function() { notify.call(5, { type: 'a' }); }, TypeError);
165 assertThrows(function() { notify.call('hello', { type: 'a' }); }, TypeError);
166 assertThrows(function() { notify.call(false, { type: 'a' }); }, TypeError);
167 assertThrows(function() { notify.call({}, { type: 'a' }); }, TypeError);
168 assertFalse(recordCreated);
169 notifier.notify(changeRecordWithAccessor);
170 assertFalse(recordCreated);  // not observed yet
171
172
173 // Object.deliverChangeRecords
174 assertThrows(function() { Object.deliverChangeRecords(nonFunction); }, TypeError);
175
176 Object.observe(obj, observer.callback);
177
178
179 // notify uses to [[CreateOwnProperty]] to create changeRecord;
180 reset();
181 var protoExpandoAccessed = false;
182 Object.defineProperty(Object.prototype, 'protoExpando',
183   {
184     configurable: true,
185     set: function() { protoExpandoAccessed = true; }
186   }
187 );
188 notifier.notify({ type: 'foo', protoExpando: 'val'});
189 assertFalse(protoExpandoAccessed);
190 delete Object.prototype.protoExpando;
191 Object.deliverChangeRecords(observer.callback);
192
193
194 // Multiple records are delivered.
195 reset();
196 notifier.notify({
197   type: 'update',
198   name: 'foo',
199   expando: 1
200 });
201
202 notifier.notify({
203   object: notifier,  // object property is ignored
204   type: 'delete',
205   name: 'bar',
206   expando2: 'str'
207 });
208 Object.deliverChangeRecords(observer.callback);
209 observer.assertCallbackRecords([
210   { object: obj, name: 'foo', type: 'update', expando: 1 },
211   { object: obj, name: 'bar', type: 'delete', expando2: 'str' }
212 ]);
213
214 // Non-string accept values are coerced to strings
215 reset();
216 Object.observe(obj, observer.callback, [true, 1, null, undefined]);
217 notifier = Object.getNotifier(obj);
218 notifier.notify({ type: 'true' });
219 notifier.notify({ type: 'false' });
220 notifier.notify({ type: '1' });
221 notifier.notify({ type: '-1' });
222 notifier.notify({ type: 'null' });
223 notifier.notify({ type: 'nill' });
224 notifier.notify({ type: 'undefined' });
225 notifier.notify({ type: 'defined' });
226 Object.deliverChangeRecords(observer.callback);
227 observer.assertCallbackRecords([
228   { object: obj, type: 'true' },
229   { object: obj, type: '1' },
230   { object: obj, type: 'null' },
231   { object: obj, type: 'undefined' }
232 ]);
233
234 // No delivery takes place if no records are pending
235 reset();
236 Object.deliverChangeRecords(observer.callback);
237 observer.assertNotCalled();
238
239
240 // Multiple observation has no effect.
241 reset();
242 Object.observe(obj, observer.callback);
243 Object.observe(obj, observer.callback);
244 Object.getNotifier(obj).notify({
245   type: 'update',
246 });
247 Object.deliverChangeRecords(observer.callback);
248 observer.assertCalled();
249
250
251 // Observation can be stopped.
252 reset();
253 Object.unobserve(obj, observer.callback);
254 Object.getNotifier(obj).notify({
255   type: 'update',
256 });
257 Object.deliverChangeRecords(observer.callback);
258 observer.assertNotCalled();
259
260
261 // Multiple unobservation has no effect
262 reset();
263 Object.unobserve(obj, observer.callback);
264 Object.unobserve(obj, observer.callback);
265 Object.getNotifier(obj).notify({
266   type: 'update',
267 });
268 Object.deliverChangeRecords(observer.callback);
269 observer.assertNotCalled();
270
271
272 // Re-observation works and only includes changeRecords after of call.
273 reset();
274 Object.getNotifier(obj).notify({
275   type: 'update',
276 });
277 Object.observe(obj, observer.callback);
278 Object.getNotifier(obj).notify({
279   type: 'update',
280 });
281 records = undefined;
282 Object.deliverChangeRecords(observer.callback);
283 observer.assertRecordCount(1);
284
285 // Get notifier prior to observing
286 reset();
287 var obj = {};
288 Object.getNotifier(obj);
289 Object.observe(obj, observer.callback);
290 obj.id = 1;
291 Object.deliverChangeRecords(observer.callback);
292 observer.assertCallbackRecords([
293   { object: obj, type: 'add', name: 'id' },
294 ]);
295
296 // The empty-string property is observable
297 reset();
298 var obj = {};
299 Object.observe(obj, observer.callback);
300 obj[''] = '';
301 obj[''] = ' ';
302 delete obj[''];
303 Object.deliverChangeRecords(observer.callback);
304 observer.assertCallbackRecords([
305   { object: obj, type: 'add', name: '' },
306   { object: obj, type: 'update', name: '', oldValue: '' },
307   { object: obj, type: 'delete', name: '', oldValue: ' ' },
308 ]);
309
310 // Object.preventExtensions
311 reset();
312 var obj = { foo: 'bar'};
313 Object.observe(obj, observer.callback);
314 obj.baz = 'bat';
315 Object.preventExtensions(obj);
316
317 Object.deliverChangeRecords(observer.callback);
318 observer.assertCallbackRecords([
319   { object: obj, type: 'add', name: 'baz' },
320   { object: obj, type: 'preventExtensions' },
321 ]);
322
323 reset();
324 var obj = { foo: 'bar'};
325 Object.preventExtensions(obj);
326 Object.observe(obj, observer.callback);
327 Object.preventExtensions(obj);
328 Object.deliverChangeRecords(observer.callback);
329 observer.assertNotCalled();
330
331 // Object.freeze
332 reset();
333 var obj = { a: 'a' };
334 Object.defineProperty(obj, 'b', {
335   writable: false,
336   configurable: true,
337   value: 'b'
338 });
339 Object.defineProperty(obj, 'c', {
340   writable: true,
341   configurable: false,
342   value: 'c'
343 });
344 Object.defineProperty(obj, 'd', {
345   writable: false,
346   configurable: false,
347   value: 'd'
348 });
349 Object.observe(obj, observer.callback);
350 Object.freeze(obj);
351
352 Object.deliverChangeRecords(observer.callback);
353 observer.assertCallbackRecords([
354   { object: obj, type: 'reconfigure', name: 'a' },
355   { object: obj, type: 'reconfigure', name: 'b' },
356   { object: obj, type: 'reconfigure', name: 'c' },
357   { object: obj, type: 'preventExtensions' },
358 ]);
359
360 reset();
361 var obj = { foo: 'bar'};
362 Object.freeze(obj);
363 Object.observe(obj, observer.callback);
364 Object.freeze(obj);
365 Object.deliverChangeRecords(observer.callback);
366 observer.assertNotCalled();
367
368 // Object.seal
369 reset();
370 var obj = { a: 'a' };
371 Object.defineProperty(obj, 'b', {
372   writable: false,
373   configurable: true,
374   value: 'b'
375 });
376 Object.defineProperty(obj, 'c', {
377   writable: true,
378   configurable: false,
379   value: 'c'
380 });
381 Object.defineProperty(obj, 'd', {
382   writable: false,
383   configurable: false,
384   value: 'd'
385 });
386 Object.observe(obj, observer.callback);
387 Object.seal(obj);
388
389 Object.deliverChangeRecords(observer.callback);
390 observer.assertCallbackRecords([
391   { object: obj, type: 'reconfigure', name: 'a' },
392   { object: obj, type: 'reconfigure', name: 'b' },
393   { object: obj, type: 'preventExtensions' },
394 ]);
395
396 reset();
397 var obj = { foo: 'bar'};
398 Object.seal(obj);
399 Object.observe(obj, observer.callback);
400 Object.seal(obj);
401 Object.deliverChangeRecords(observer.callback);
402 observer.assertNotCalled();
403
404 // Observing a continuous stream of changes, while itermittantly unobserving.
405 reset();
406 var obj = {};
407 Object.observe(obj, observer.callback);
408 Object.getNotifier(obj).notify({
409   type: 'update',
410   val: 1
411 });
412
413 Object.unobserve(obj, observer.callback);
414 Object.getNotifier(obj).notify({
415   type: 'update',
416   val: 2
417 });
418
419 Object.observe(obj, observer.callback);
420 Object.getNotifier(obj).notify({
421   type: 'update',
422   val: 3
423 });
424
425 Object.unobserve(obj, observer.callback);
426 Object.getNotifier(obj).notify({
427   type: 'update',
428   val: 4
429 });
430
431 Object.observe(obj, observer.callback);
432 Object.getNotifier(obj).notify({
433   type: 'update',
434   val: 5
435 });
436
437 Object.unobserve(obj, observer.callback);
438 Object.deliverChangeRecords(observer.callback);
439 observer.assertCallbackRecords([
440   { object: obj, type: 'update', val: 1 },
441   { object: obj, type: 'update', val: 3 },
442   { object: obj, type: 'update', val: 5 }
443 ]);
444
445 // Accept
446 reset();
447 Object.observe(obj, observer.callback, ['somethingElse']);
448 Object.getNotifier(obj).notify({
449   type: 'add'
450 });
451 Object.getNotifier(obj).notify({
452   type: 'update'
453 });
454 Object.getNotifier(obj).notify({
455   type: 'delete'
456 });
457 Object.getNotifier(obj).notify({
458   type: 'reconfigure'
459 });
460 Object.getNotifier(obj).notify({
461   type: 'setPrototype'
462 });
463 Object.deliverChangeRecords(observer.callback);
464 observer.assertNotCalled();
465
466 reset();
467 Object.observe(obj, observer.callback, ['add', 'delete', 'setPrototype']);
468 Object.getNotifier(obj).notify({
469   type: 'add'
470 });
471 Object.getNotifier(obj).notify({
472   type: 'update'
473 });
474 Object.getNotifier(obj).notify({
475   type: 'delete'
476 });
477 Object.getNotifier(obj).notify({
478   type: 'delete'
479 });
480 Object.getNotifier(obj).notify({
481   type: 'reconfigure'
482 });
483 Object.getNotifier(obj).notify({
484   type: 'setPrototype'
485 });
486 Object.deliverChangeRecords(observer.callback);
487 observer.assertCallbackRecords([
488   { object: obj, type: 'add' },
489   { object: obj, type: 'delete' },
490   { object: obj, type: 'delete' },
491   { object: obj, type: 'setPrototype' }
492 ]);
493
494 reset();
495 Object.observe(obj, observer.callback, ['update', 'foo']);
496 Object.getNotifier(obj).notify({
497   type: 'add'
498 });
499 Object.getNotifier(obj).notify({
500   type: 'update'
501 });
502 Object.getNotifier(obj).notify({
503   type: 'delete'
504 });
505 Object.getNotifier(obj).notify({
506   type: 'foo'
507 });
508 Object.getNotifier(obj).notify({
509   type: 'bar'
510 });
511 Object.getNotifier(obj).notify({
512   type: 'foo'
513 });
514 Object.deliverChangeRecords(observer.callback);
515 observer.assertCallbackRecords([
516   { object: obj, type: 'update' },
517   { object: obj, type: 'foo' },
518   { object: obj, type: 'foo' }
519 ]);
520
521 reset();
522 function Thingy(a, b, c) {
523   this.a = a;
524   this.b = b;
525 }
526
527 Thingy.MULTIPLY = 'multiply';
528 Thingy.INCREMENT = 'increment';
529 Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply';
530
531 Thingy.prototype = {
532   increment: function(amount) {
533     var notifier = Object.getNotifier(this);
534
535     var self = this;
536     notifier.performChange(Thingy.INCREMENT, function() {
537       self.a += amount;
538       self.b += amount;
539
540       return {
541         incremented: amount
542       };  // implicit notify
543     });
544   },
545
546   multiply: function(amount) {
547     var notifier = Object.getNotifier(this);
548
549     var self = this;
550     notifier.performChange(Thingy.MULTIPLY, function() {
551       self.a *= amount;
552       self.b *= amount;
553
554       return {
555         multiplied: amount
556       };  // implicit notify
557     });
558   },
559
560   incrementAndMultiply: function(incAmount, multAmount) {
561     var notifier = Object.getNotifier(this);
562
563     var self = this;
564     notifier.performChange(Thingy.INCREMENT_AND_MULTIPLY, function() {
565       self.increment(incAmount);
566       self.multiply(multAmount);
567
568       return {
569         incremented: incAmount,
570         multiplied: multAmount
571       };  // implicit notify
572     });
573   }
574 }
575
576 Thingy.observe = function(thingy, callback) {
577   Object.observe(thingy, callback, [Thingy.INCREMENT,
578                                     Thingy.MULTIPLY,
579                                     Thingy.INCREMENT_AND_MULTIPLY,
580                                     'update']);
581 }
582
583 Thingy.unobserve = function(thingy, callback) {
584   Object.unobserve(thingy);
585 }
586
587 var thingy = new Thingy(2, 4);
588
589 Object.observe(thingy, observer.callback);
590 Thingy.observe(thingy, observer2.callback);
591 thingy.increment(3);               // { a: 5, b: 7 }
592 thingy.b++;                        // { a: 5, b: 8 }
593 thingy.multiply(2);                // { a: 10, b: 16 }
594 thingy.a++;                        // { a: 11, b: 16 }
595 thingy.incrementAndMultiply(2, 2); // { a: 26, b: 36 }
596
597 Object.deliverChangeRecords(observer.callback);
598 Object.deliverChangeRecords(observer2.callback);
599 observer.assertCallbackRecords([
600   { object: thingy, type: 'update', name: 'a', oldValue: 2 },
601   { object: thingy, type: 'update', name: 'b', oldValue: 4 },
602   { object: thingy, type: 'update', name: 'b', oldValue: 7 },
603   { object: thingy, type: 'update', name: 'a', oldValue: 5 },
604   { object: thingy, type: 'update', name: 'b', oldValue: 8 },
605   { object: thingy, type: 'update', name: 'a', oldValue: 10 },
606   { object: thingy, type: 'update', name: 'a', oldValue: 11 },
607   { object: thingy, type: 'update', name: 'b', oldValue: 16 },
608   { object: thingy, type: 'update', name: 'a', oldValue: 13 },
609   { object: thingy, type: 'update', name: 'b', oldValue: 18 },
610 ]);
611 observer2.assertCallbackRecords([
612   { object: thingy, type: Thingy.INCREMENT, incremented: 3 },
613   { object: thingy, type: 'update', name: 'b', oldValue: 7 },
614   { object: thingy, type: Thingy.MULTIPLY, multiplied: 2 },
615   { object: thingy, type: 'update', name: 'a', oldValue: 10 },
616   {
617     object: thingy,
618     type: Thingy.INCREMENT_AND_MULTIPLY,
619     incremented: 2,
620     multiplied: 2
621   }
622 ]);
623
624 // ArrayPush cached stub
625 reset();
626
627 function pushMultiple(arr) {
628   arr.push('a');
629   arr.push('b');
630   arr.push('c');
631 }
632
633 for (var i = 0; i < 5; i++) {
634   var arr = [];
635   pushMultiple(arr);
636 }
637
638 for (var i = 0; i < 5; i++) {
639   reset();
640   var arr = [];
641   Object.observe(arr, observer.callback);
642   pushMultiple(arr);
643   Object.unobserve(arr, observer.callback);
644   Object.deliverChangeRecords(observer.callback);
645   observer.assertCallbackRecords([
646     { object: arr, type: 'add', name: '0' },
647     { object: arr, type: 'update', name: 'length', oldValue: 0 },
648     { object: arr, type: 'add', name: '1' },
649     { object: arr, type: 'update', name: 'length', oldValue: 1 },
650     { object: arr, type: 'add', name: '2' },
651     { object: arr, type: 'update', name: 'length', oldValue: 2 },
652   ]);
653 }
654
655
656 // ArrayPop cached stub
657 reset();
658
659 function popMultiple(arr) {
660   arr.pop();
661   arr.pop();
662   arr.pop();
663 }
664
665 for (var i = 0; i < 5; i++) {
666   var arr = ['a', 'b', 'c'];
667   popMultiple(arr);
668 }
669
670 for (var i = 0; i < 5; i++) {
671   reset();
672   var arr = ['a', 'b', 'c'];
673   Object.observe(arr, observer.callback);
674   popMultiple(arr);
675   Object.unobserve(arr, observer.callback);
676   Object.deliverChangeRecords(observer.callback);
677   observer.assertCallbackRecords([
678     { object: arr, type: 'delete', name: '2', oldValue: 'c' },
679     { object: arr, type: 'update', name: 'length', oldValue: 3 },
680     { object: arr, type: 'delete', name: '1', oldValue: 'b' },
681     { object: arr, type: 'update', name: 'length', oldValue: 2 },
682     { object: arr, type: 'delete', name: '0', oldValue: 'a' },
683     { object: arr, type: 'update', name: 'length', oldValue: 1 },
684   ]);
685 }
686
687
688 reset();
689 function RecursiveThingy() {}
690
691 RecursiveThingy.MULTIPLY_FIRST_N = 'multiplyFirstN';
692
693 RecursiveThingy.prototype = {
694   __proto__: Array.prototype,
695
696   multiplyFirstN: function(amount, n) {
697     if (!n)
698       return;
699     var notifier = Object.getNotifier(this);
700     var self = this;
701     notifier.performChange(RecursiveThingy.MULTIPLY_FIRST_N, function() {
702       self[n-1] = self[n-1]*amount;
703       self.multiplyFirstN(amount, n-1);
704     });
705
706     notifier.notify({
707       type: RecursiveThingy.MULTIPLY_FIRST_N,
708       multiplied: amount,
709       n: n
710     });
711   },
712 }
713
714 RecursiveThingy.observe = function(thingy, callback) {
715   Object.observe(thingy, callback, [RecursiveThingy.MULTIPLY_FIRST_N]);
716 }
717
718 RecursiveThingy.unobserve = function(thingy, callback) {
719   Object.unobserve(thingy);
720 }
721
722 var thingy = new RecursiveThingy;
723 thingy.push(1, 2, 3, 4);
724
725 Object.observe(thingy, observer.callback);
726 RecursiveThingy.observe(thingy, observer2.callback);
727 thingy.multiplyFirstN(2, 3);                // [2, 4, 6, 4]
728
729 Object.deliverChangeRecords(observer.callback);
730 Object.deliverChangeRecords(observer2.callback);
731 observer.assertCallbackRecords([
732   { object: thingy, type: 'update', name: '2', oldValue: 3 },
733   { object: thingy, type: 'update', name: '1', oldValue: 2 },
734   { object: thingy, type: 'update', name: '0', oldValue: 1 }
735 ]);
736 observer2.assertCallbackRecords([
737   { object: thingy, type: RecursiveThingy.MULTIPLY_FIRST_N, multiplied: 2, n: 3 }
738 ]);
739
740 reset();
741 function DeckSuit() {
742   this.push('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'A', 'Q', 'K');
743 }
744
745 DeckSuit.SHUFFLE = 'shuffle';
746
747 DeckSuit.prototype = {
748   __proto__: Array.prototype,
749
750   shuffle: function() {
751     var notifier = Object.getNotifier(this);
752     var self = this;
753     notifier.performChange(DeckSuit.SHUFFLE, function() {
754       self.reverse();
755       self.sort(function() { return Math.random()* 2 - 1; });
756       var cut = self.splice(0, 6);
757       Array.prototype.push.apply(self, cut);
758       self.reverse();
759       self.sort(function() { return Math.random()* 2 - 1; });
760       var cut = self.splice(0, 6);
761       Array.prototype.push.apply(self, cut);
762       self.reverse();
763       self.sort(function() { return Math.random()* 2 - 1; });
764     });
765
766     notifier.notify({
767       type: DeckSuit.SHUFFLE
768     });
769   },
770 }
771
772 DeckSuit.observe = function(thingy, callback) {
773   Object.observe(thingy, callback, [DeckSuit.SHUFFLE]);
774 }
775
776 DeckSuit.unobserve = function(thingy, callback) {
777   Object.unobserve(thingy);
778 }
779
780 var deck = new DeckSuit;
781
782 DeckSuit.observe(deck, observer2.callback);
783 deck.shuffle();
784
785 Object.deliverChangeRecords(observer2.callback);
786 observer2.assertCallbackRecords([
787   { object: deck, type: DeckSuit.SHUFFLE }
788 ]);
789
790 // Observing multiple objects; records appear in order.
791 reset();
792 var obj2 = {};
793 var obj3 = {}
794 Object.observe(obj, observer.callback);
795 Object.observe(obj3, observer.callback);
796 Object.observe(obj2, observer.callback);
797 Object.getNotifier(obj).notify({
798   type: 'add',
799 });
800 Object.getNotifier(obj2).notify({
801   type: 'update',
802 });
803 Object.getNotifier(obj3).notify({
804   type: 'delete',
805 });
806 Object.observe(obj3, observer.callback);
807 Object.deliverChangeRecords(observer.callback);
808 observer.assertCallbackRecords([
809   { object: obj, type: 'add' },
810   { object: obj2, type: 'update' },
811   { object: obj3, type: 'delete' }
812 ]);
813
814
815 // Recursive observation.
816 var obj = {a: 1};
817 var callbackCount = 0;
818 function recursiveObserver(r) {
819   assertEquals(1, r.length);
820   ++callbackCount;
821   if (r[0].oldValue < 100) ++obj[r[0].name];
822 }
823 Object.observe(obj, recursiveObserver);
824 ++obj.a;
825 Object.deliverChangeRecords(recursiveObserver);
826 assertEquals(100, callbackCount);
827
828 var obj1 = {a: 1};
829 var obj2 = {a: 1};
830 var recordCount = 0;
831 function recursiveObserver2(r) {
832   recordCount += r.length;
833   if (r[0].oldValue < 100) {
834     ++obj1.a;
835     ++obj2.a;
836   }
837 }
838 Object.observe(obj1, recursiveObserver2);
839 Object.observe(obj2, recursiveObserver2);
840 ++obj1.a;
841 Object.deliverChangeRecords(recursiveObserver2);
842 assertEquals(199, recordCount);
843
844
845 // Observing named properties.
846 reset();
847 var obj = {a: 1}
848 Object.observe(obj, observer.callback);
849 obj.a = 2;
850 obj["a"] = 3;
851 delete obj.a;
852 obj.a = 4;
853 obj.a = 4;  // ignored
854 obj.a = 5;
855 Object.defineProperty(obj, "a", {value: 6});
856 Object.defineProperty(obj, "a", {writable: false});
857 obj.a = 7;  // ignored
858 Object.defineProperty(obj, "a", {value: 8});
859 Object.defineProperty(obj, "a", {value: 7, writable: true});
860 Object.defineProperty(obj, "a", {get: function() {}});
861 Object.defineProperty(obj, "a", {get: frozenFunction});
862 Object.defineProperty(obj, "a", {get: frozenFunction});  // ignored
863 Object.defineProperty(obj, "a", {get: frozenFunction, set: frozenFunction});
864 Object.defineProperty(obj, "a", {set: frozenFunction});  // ignored
865 Object.defineProperty(obj, "a", {get: undefined, set: frozenFunction});
866 delete obj.a;
867 delete obj.a;
868 Object.defineProperty(obj, "a", {get: function() {}, configurable: true});
869 Object.defineProperty(obj, "a", {value: 9, writable: true});
870 obj.a = 10;
871 ++obj.a;
872 obj.a++;
873 obj.a *= 3;
874 delete obj.a;
875 Object.defineProperty(obj, "a", {value: 11, configurable: true});
876 Object.deliverChangeRecords(observer.callback);
877 observer.assertCallbackRecords([
878   { object: obj, name: "a", type: "update", oldValue: 1 },
879   { object: obj, name: "a", type: "update", oldValue: 2 },
880   { object: obj, name: "a", type: "delete", oldValue: 3 },
881   { object: obj, name: "a", type: "add" },
882   { object: obj, name: "a", type: "update", oldValue: 4 },
883   { object: obj, name: "a", type: "update", oldValue: 5 },
884   { object: obj, name: "a", type: "reconfigure" },
885   { object: obj, name: "a", type: "update", oldValue: 6 },
886   { object: obj, name: "a", type: "reconfigure", oldValue: 8 },
887   { object: obj, name: "a", type: "reconfigure", oldValue: 7 },
888   { object: obj, name: "a", type: "reconfigure" },
889   { object: obj, name: "a", type: "reconfigure" },
890   { object: obj, name: "a", type: "reconfigure" },
891   { object: obj, name: "a", type: "delete" },
892   { object: obj, name: "a", type: "add" },
893   { object: obj, name: "a", type: "reconfigure" },
894   { object: obj, name: "a", type: "update", oldValue: 9 },
895   { object: obj, name: "a", type: "update", oldValue: 10 },
896   { object: obj, name: "a", type: "update", oldValue: 11 },
897   { object: obj, name: "a", type: "update", oldValue: 12 },
898   { object: obj, name: "a", type: "delete", oldValue: 36 },
899   { object: obj, name: "a", type: "add" },
900 ]);
901
902
903 // Observing indexed properties.
904 reset();
905 var obj = {'1': 1}
906 Object.observe(obj, observer.callback);
907 obj[1] = 2;
908 obj[1] = 3;
909 delete obj[1];
910 obj[1] = 4;
911 obj[1] = 4;  // ignored
912 obj[1] = 5;
913 Object.defineProperty(obj, "1", {value: 6});
914 Object.defineProperty(obj, "1", {writable: false});
915 obj[1] = 7;  // ignored
916 Object.defineProperty(obj, "1", {value: 8});
917 Object.defineProperty(obj, "1", {value: 7, writable: true});
918 Object.defineProperty(obj, "1", {get: function() {}});
919 Object.defineProperty(obj, "1", {get: frozenFunction});
920 Object.defineProperty(obj, "1", {get: frozenFunction});  // ignored
921 Object.defineProperty(obj, "1", {get: frozenFunction, set: frozenFunction});
922 Object.defineProperty(obj, "1", {set: frozenFunction});  // ignored
923 Object.defineProperty(obj, "1", {get: undefined, set: frozenFunction});
924 delete obj[1];
925 delete obj[1];
926 Object.defineProperty(obj, "1", {get: function() {}, configurable: true});
927 Object.defineProperty(obj, "1", {value: 9, writable: true});
928 obj[1] = 10;
929 ++obj[1];
930 obj[1]++;
931 obj[1] *= 3;
932 delete obj[1];
933 Object.defineProperty(obj, "1", {value: 11, configurable: true});
934 Object.deliverChangeRecords(observer.callback);
935 observer.assertCallbackRecords([
936   { object: obj, name: "1", type: "update", oldValue: 1 },
937   { object: obj, name: "1", type: "update", oldValue: 2 },
938   { object: obj, name: "1", type: "delete", oldValue: 3 },
939   { object: obj, name: "1", type: "add" },
940   { object: obj, name: "1", type: "update", oldValue: 4 },
941   { object: obj, name: "1", type: "update", oldValue: 5 },
942   { object: obj, name: "1", type: "reconfigure" },
943   { object: obj, name: "1", type: "update", oldValue: 6 },
944   { object: obj, name: "1", type: "reconfigure", oldValue: 8 },
945   { object: obj, name: "1", type: "reconfigure", oldValue: 7 },
946   { object: obj, name: "1", type: "reconfigure" },
947   { object: obj, name: "1", type: "reconfigure" },
948   { object: obj, name: "1", type: "reconfigure" },
949   { object: obj, name: "1", type: "delete" },
950   { object: obj, name: "1", type: "add" },
951   { object: obj, name: "1", type: "reconfigure" },
952   { object: obj, name: "1", type: "update", oldValue: 9 },
953   { object: obj, name: "1", type: "update", oldValue: 10 },
954   { object: obj, name: "1", type: "update", oldValue: 11 },
955   { object: obj, name: "1", type: "update", oldValue: 12 },
956   { object: obj, name: "1", type: "delete", oldValue: 36 },
957   { object: obj, name: "1", type: "add" },
958 ]);
959
960
961 // Observing symbol properties (not).
962 print("*****")
963 reset();
964 var obj = {}
965 var symbol = Symbol("secret");
966 Object.observe(obj, observer.callback);
967 obj[symbol] = 3;
968 delete obj[symbol];
969 Object.defineProperty(obj, symbol, {get: function() {}, configurable: true});
970 Object.defineProperty(obj, symbol, {value: 6});
971 Object.defineProperty(obj, symbol, {writable: false});
972 delete obj[symbol];
973 Object.defineProperty(obj, symbol, {value: 7});
974 ++obj[symbol];
975 obj[symbol]++;
976 obj[symbol] *= 3;
977 delete obj[symbol];
978 obj.__defineSetter__(symbol, function() {});
979 obj.__defineGetter__(symbol, function() {});
980 Object.deliverChangeRecords(observer.callback);
981 observer.assertNotCalled();
982
983
984 // Test all kinds of objects generically.
985 function TestObserveConfigurable(obj, prop) {
986   reset();
987   Object.observe(obj, observer.callback);
988   Object.unobserve(obj, observer.callback);
989   obj[prop] = 1;
990   Object.observe(obj, observer.callback);
991   obj[prop] = 2;
992   obj[prop] = 3;
993   delete obj[prop];
994   obj[prop] = 4;
995   obj[prop] = 4;  // ignored
996   obj[prop] = 5;
997   Object.defineProperty(obj, prop, {value: 6});
998   Object.defineProperty(obj, prop, {writable: false});
999   obj[prop] = 7;  // ignored
1000   Object.defineProperty(obj, prop, {value: 8});
1001   Object.defineProperty(obj, prop, {value: 7, writable: true});
1002   Object.defineProperty(obj, prop, {get: function() {}});
1003   Object.defineProperty(obj, prop, {get: frozenFunction});
1004   Object.defineProperty(obj, prop, {get: frozenFunction});  // ignored
1005   Object.defineProperty(obj, prop, {get: frozenFunction, set: frozenFunction});
1006   Object.defineProperty(obj, prop, {set: frozenFunction});  // ignored
1007   Object.defineProperty(obj, prop, {get: undefined, set: frozenFunction});
1008   obj.__defineSetter__(prop, frozenFunction);  // ignored
1009   obj.__defineSetter__(prop, function() {});
1010   obj.__defineGetter__(prop, function() {});
1011   delete obj[prop];
1012   delete obj[prop];  // ignored
1013   obj.__defineGetter__(prop, function() {});
1014   delete obj[prop];
1015   Object.defineProperty(obj, prop, {get: function() {}, configurable: true});
1016   Object.defineProperty(obj, prop, {value: 9, writable: true});
1017   obj[prop] = 10;
1018   ++obj[prop];
1019   obj[prop]++;
1020   obj[prop] *= 3;
1021   delete obj[prop];
1022   Object.defineProperty(obj, prop, {value: 11, configurable: true});
1023   Object.deliverChangeRecords(observer.callback);
1024   observer.assertCallbackRecords([
1025     { object: obj, name: prop, type: "update", oldValue: 1 },
1026     { object: obj, name: prop, type: "update", oldValue: 2 },
1027     { object: obj, name: prop, type: "delete", oldValue: 3 },
1028     { object: obj, name: prop, type: "add" },
1029     { object: obj, name: prop, type: "update", oldValue: 4 },
1030     { object: obj, name: prop, type: "update", oldValue: 5 },
1031     { object: obj, name: prop, type: "reconfigure" },
1032     { object: obj, name: prop, type: "update", oldValue: 6 },
1033     { object: obj, name: prop, type: "reconfigure", oldValue: 8 },
1034     { object: obj, name: prop, type: "reconfigure", oldValue: 7 },
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: "reconfigure" },
1040     { object: obj, name: prop, type: "delete" },
1041     { object: obj, name: prop, type: "add" },
1042     { object: obj, name: prop, type: "delete" },
1043     { object: obj, name: prop, type: "add" },
1044     { object: obj, name: prop, type: "reconfigure" },
1045     { object: obj, name: prop, type: "update", oldValue: 9 },
1046     { object: obj, name: prop, type: "update", oldValue: 10 },
1047     { object: obj, name: prop, type: "update", oldValue: 11 },
1048     { object: obj, name: prop, type: "update", oldValue: 12 },
1049     { object: obj, name: prop, type: "delete", oldValue: 36 },
1050     { object: obj, name: prop, type: "add" },
1051   ]);
1052   Object.unobserve(obj, observer.callback);
1053   delete obj[prop];
1054 }
1055
1056 function TestObserveNonConfigurable(obj, prop, desc) {
1057   reset();
1058   Object.observe(obj, observer.callback);
1059   Object.unobserve(obj, observer.callback);
1060   obj[prop] = 1;
1061   Object.observe(obj, observer.callback);
1062   obj[prop] = 4;
1063   obj[prop] = 4;  // ignored
1064   obj[prop] = 5;
1065   Object.defineProperty(obj, prop, {value: 6});
1066   Object.defineProperty(obj, prop, {value: 6});  // ignored
1067   Object.defineProperty(obj, prop, {value: 7});
1068   Object.defineProperty(obj, prop, {enumerable: desc.enumerable});  // ignored
1069   Object.defineProperty(obj, prop, {writable: false});
1070   obj[prop] = 7;  // ignored
1071   Object.deliverChangeRecords(observer.callback);
1072   observer.assertCallbackRecords([
1073     { object: obj, name: prop, type: "update", oldValue: 1 },
1074     { object: obj, name: prop, type: "update", oldValue: 4 },
1075     { object: obj, name: prop, type: "update", oldValue: 5 },
1076     { object: obj, name: prop, type: "update", oldValue: 6 },
1077     { object: obj, name: prop, type: "reconfigure" },
1078   ]);
1079   Object.unobserve(obj, observer.callback);
1080 }
1081
1082 // TODO(rafaelw) Enable when ES6 Proxies are implemented
1083 /*
1084 function createProxy(create, x) {
1085   var handler = {
1086     getPropertyDescriptor: function(k) {
1087       for (var o = this.target; o; o = Object.getPrototypeOf(o)) {
1088         var desc = Object.getOwnPropertyDescriptor(o, k);
1089         if (desc) return desc;
1090       }
1091       return undefined;
1092     },
1093     getOwnPropertyDescriptor: function(k) {
1094       return Object.getOwnPropertyDescriptor(this.target, k);
1095     },
1096     defineProperty: function(k, desc) {
1097       var x = Object.defineProperty(this.target, k, desc);
1098       Object.deliverChangeRecords(this.callback);
1099       return x;
1100     },
1101     delete: function(k) {
1102       var x = delete this.target[k];
1103       Object.deliverChangeRecords(this.callback);
1104       return x;
1105     },
1106     getPropertyNames: function() {
1107       return Object.getOwnPropertyNames(this.target);
1108     },
1109     target: {isProxy: true},
1110     callback: function(changeRecords) {
1111       print("callback", stringifyNoThrow(handler.proxy), stringifyNoThrow(got));
1112       for (var i in changeRecords) {
1113         var got = changeRecords[i];
1114         var change = {object: handler.proxy, name: got.name, type: got.type};
1115         if ("oldValue" in got) change.oldValue = got.oldValue;
1116         Object.getNotifier(handler.proxy).notify(change);
1117       }
1118     },
1119   };
1120   Object.observe(handler.target, handler.callback);
1121   return handler.proxy = create(handler, x);
1122 }
1123 */
1124
1125 var objects = [
1126   {},
1127   [],
1128   function(){},
1129   (function(){ return arguments })(),
1130   (function(){ "use strict"; return arguments })(),
1131   Object(1), Object(true), Object("bla"),
1132   new Date(),
1133   Object, Function, Date, RegExp,
1134   new Set, new Map, new WeakMap,
1135   new ArrayBuffer(10), new Int32Array(5)
1136 // TODO(rafaelw) Enable when ES6 Proxies are implemented.
1137 //  createProxy(Proxy.create, null),
1138 //  createProxy(Proxy.createFunction, function(){}),
1139 ];
1140 var properties = ["a", "1", 1, "length", "setPrototype", "name", "caller"];
1141
1142 // Cases that yield non-standard results.
1143 function blacklisted(obj, prop) {
1144   return (obj instanceof Int32Array && prop == 1) ||
1145          (obj instanceof Int32Array && prop === "length") ||
1146          (obj instanceof ArrayBuffer && prop == 1)
1147 }
1148
1149 for (var i in objects) for (var j in properties) {
1150   var obj = objects[i];
1151   var prop = properties[j];
1152   if (blacklisted(obj, prop)) continue;
1153   var desc = Object.getOwnPropertyDescriptor(obj, prop);
1154   print("***", typeof obj, stringifyNoThrow(obj), prop);
1155   if (!desc || desc.configurable)
1156     TestObserveConfigurable(obj, prop);
1157   else if (desc.writable)
1158     TestObserveNonConfigurable(obj, prop, desc);
1159 }
1160
1161
1162 // Observing array length (including truncation)
1163 reset();
1164 var arr = ['a', 'b', 'c', 'd'];
1165 var arr2 = ['alpha', 'beta'];
1166 var arr3 = ['hello'];
1167 arr3[2] = 'goodbye';
1168 arr3.length = 6;
1169 Object.defineProperty(arr, '0', {configurable: false});
1170 Object.defineProperty(arr, '2', {get: function(){}});
1171 Object.defineProperty(arr2, '0', {get: function(){}, configurable: false});
1172 Object.observe(arr, observer.callback);
1173 Array.observe(arr, observer2.callback);
1174 Object.observe(arr2, observer.callback);
1175 Array.observe(arr2, observer2.callback);
1176 Object.observe(arr3, observer.callback);
1177 Array.observe(arr3, observer2.callback);
1178 arr.length = 2;
1179 arr.length = 0;
1180 arr.length = 10;
1181 Object.defineProperty(arr, 'length', {writable: false});
1182 arr2.length = 0;
1183 arr2.length = 1; // no change expected
1184 Object.defineProperty(arr2, 'length', {value: 1, writable: false});
1185 arr3.length = 0;
1186 ++arr3.length;
1187 arr3.length++;
1188 arr3.length /= 2;
1189 Object.defineProperty(arr3, 'length', {value: 5});
1190 arr3[4] = 5;
1191 Object.defineProperty(arr3, 'length', {value: 1, writable: false});
1192 Object.deliverChangeRecords(observer.callback);
1193 observer.assertCallbackRecords([
1194   { object: arr, name: '3', type: 'delete', oldValue: 'd' },
1195   { object: arr, name: '2', type: 'delete' },
1196   { object: arr, name: 'length', type: 'update', oldValue: 4 },
1197   { object: arr, name: '1', type: 'delete', oldValue: 'b' },
1198   { object: arr, name: 'length', type: 'update', oldValue: 2 },
1199   { object: arr, name: 'length', type: 'update', oldValue: 1 },
1200   { object: arr, name: 'length', type: 'reconfigure' },
1201   { object: arr2, name: '1', type: 'delete', oldValue: 'beta' },
1202   { object: arr2, name: 'length', type: 'update', oldValue: 2 },
1203   { object: arr2, name: 'length', type: 'reconfigure' },
1204   { object: arr3, name: '2', type: 'delete', oldValue: 'goodbye' },
1205   { object: arr3, name: '0', type: 'delete', oldValue: 'hello' },
1206   { object: arr3, name: 'length', type: 'update', oldValue: 6 },
1207   { object: arr3, name: 'length', type: 'update', oldValue: 0 },
1208   { object: arr3, name: 'length', type: 'update', oldValue: 1 },
1209   { object: arr3, name: 'length', type: 'update', oldValue: 2 },
1210   { object: arr3, name: 'length', type: 'update', oldValue: 1 },
1211   { object: arr3, name: '4', type: 'add' },
1212   { object: arr3, name: '4', type: 'delete', oldValue: 5 },
1213   // TODO(rafaelw): It breaks spec compliance to get two records here.
1214   // When the TODO in v8natives.js::DefineArrayProperty is addressed
1215   // which prevents DefineProperty from over-writing the magic length
1216   // property, these will collapse into a single record.
1217   { object: arr3, name: 'length', type: 'update', oldValue: 5 },
1218   { object: arr3, name: 'length', type: 'reconfigure' }
1219 ]);
1220 Object.deliverChangeRecords(observer2.callback);
1221 observer2.assertCallbackRecords([
1222   { object: arr, type: 'splice', index: 2, removed: [, 'd'], addedCount: 0 },
1223   { object: arr, type: 'splice', index: 1, removed: ['b'], addedCount: 0 },
1224   { object: arr, type: 'splice', index: 1, removed: [], addedCount: 9 },
1225   { object: arr2, type: 'splice', index: 1, removed: ['beta'], addedCount: 0 },
1226   { object: arr3, type: 'splice', index: 0, removed: ['hello',, 'goodbye',,,,], addedCount: 0 },
1227   { object: arr3, type: 'splice', index: 0, removed: [], addedCount: 1 },
1228   { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 1 },
1229   { object: arr3, type: 'splice', index: 1, removed: [,], addedCount: 0 },
1230   { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 4 },
1231   { object: arr3, name: '4', type: 'add' },
1232   { object: arr3, type: 'splice', index: 1, removed: [,,,5], addedCount: 0 }
1233 ]);
1234
1235
1236 // Updating length on large (slow) array
1237 reset();
1238 var slow_arr = new Array(1000000000);
1239 slow_arr[500000000] = 'hello';
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.assertNotCalled();
1690
1691
1692 // Check that changes in observation status are detected in all IC states and
1693 // in optimized code, especially in cases usually using fast elements.
1694 var mutation = [
1695   "a[i] = v",
1696   "a[i] ? ++a[i] : a[i] = v",
1697   "a[i] ? a[i]++ : a[i] = v",
1698   "a[i] ? a[i] += 1 : a[i] = v",
1699   "a[i] ? a[i] -= -1 : a[i] = v",
1700 ];
1701
1702 var props = [1, "1", "a"];
1703
1704 function TestFastElements(prop, mutation, prepopulate, polymorphic, optimize) {
1705   var setElement = eval(
1706     "(function setElement(a, i, v) { " + mutation + "; " +
1707     "/* " + [].join.call(arguments, " ") + " */" +
1708     "})"
1709   );
1710   print("TestFastElements:", setElement);
1711
1712   var arr = prepopulate ? [1, 2, 3, 4, 5] : [0];
1713   if (prepopulate) arr[prop] = 2;  // for non-element case
1714   setElement(arr, prop, 3);
1715   setElement(arr, prop, 4);
1716   if (polymorphic) setElement(["M", "i", "l", "n", "e", "r"], 0, "m");
1717   if (optimize) %OptimizeFunctionOnNextCall(setElement);
1718   setElement(arr, prop, 5);
1719
1720   reset();
1721   Object.observe(arr, observer.callback);
1722   setElement(arr, prop, 989898);
1723   Object.deliverChangeRecords(observer.callback);
1724   observer.assertCallbackRecords([
1725     { object: arr, name: "" + prop, type: 'update', oldValue: 5 }
1726   ]);
1727 }
1728
1729 for (var b1 = 0; b1 < 2; ++b1)
1730   for (var b2 = 0; b2 < 2; ++b2)
1731     for (var b3 = 0; b3 < 2; ++b3)
1732       for (var i in props)
1733         for (var j in mutation)
1734           TestFastElements(props[i], mutation[j], b1 != 0, b2 != 0, b3 != 0);
1735
1736
1737 var mutation = [
1738   "a.length = v",
1739   "a.length += newSize - oldSize",
1740   "a.length -= oldSize - newSize",
1741 ];
1742
1743 var mutationByIncr = [
1744   "++a.length",
1745   "a.length++",
1746 ];
1747
1748 function TestFastElementsLength(
1749   mutation, polymorphic, optimize, oldSize, newSize) {
1750   var setLength = eval(
1751     "(function setLength(a, v) { " + mutation + "; " +
1752     "/* " + [].join.call(arguments, " ") + " */"
1753     + "})"
1754   );
1755   print("TestFastElementsLength:", setLength);
1756
1757   function array(n) {
1758     var arr = new Array(n);
1759     for (var i = 0; i < n; ++i) arr[i] = i;
1760     return arr;
1761   }
1762
1763   setLength(array(oldSize), newSize);
1764   setLength(array(oldSize), newSize);
1765   if (polymorphic) setLength(array(oldSize).map(isNaN), newSize);
1766   if (optimize) %OptimizeFunctionOnNextCall(setLength);
1767   setLength(array(oldSize), newSize);
1768
1769   reset();
1770   var arr = array(oldSize);
1771   Object.observe(arr, observer.callback);
1772   setLength(arr, newSize);
1773   Object.deliverChangeRecords(observer.callback);
1774   if (oldSize === newSize) {
1775     observer.assertNotCalled();
1776   } else {
1777     var count = oldSize > newSize ? oldSize - newSize : 0;
1778     observer.assertRecordCount(count + 1);
1779     var lengthRecord = observer.records[count];
1780     assertSame(arr, lengthRecord.object);
1781     assertEquals('length', lengthRecord.name);
1782     assertEquals('update', lengthRecord.type);
1783     assertSame(oldSize, lengthRecord.oldValue);
1784   }
1785 }
1786
1787 for (var b1 = 0; b1 < 2; ++b1)
1788   for (var b2 = 0; b2 < 2; ++b2)
1789     for (var n1 = 0; n1 < 3; ++n1)
1790       for (var n2 = 0; n2 < 3; ++n2)
1791         for (var i in mutation)
1792           TestFastElementsLength(mutation[i], b1 != 0, b2 != 0, 20*n1, 20*n2);
1793
1794 for (var b1 = 0; b1 < 2; ++b1)
1795   for (var b2 = 0; b2 < 2; ++b2)
1796     for (var n = 0; n < 3; ++n)
1797       for (var i in mutationByIncr)
1798         TestFastElementsLength(mutationByIncr[i], b1 != 0, b2 != 0, 7*n, 7*n+1);