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 --harmony-object-observe
29// Flags: --allow-natives-syntax
30
31var allObservers = [];
32function reset() {
33  allObservers.forEach(function(observer) { observer.reset(); });
34}
35
36function stringifyNoThrow(arg) {
37  try {
38    return JSON.stringify(arg);
39  } catch (e) {
40    return '{<circular reference>}';
41  }
42}
43
44function 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
90var observer = createObserver();
91var observer2 = createObserver();
92
93assertEquals("function", typeof observer.callback);
94assertEquals("function", typeof observer2.callback);
95
96var obj = {};
97
98function frozenFunction() {}
99Object.freeze(frozenFunction);
100var nonFunction = {};
101var changeRecordWithAccessor = { type: 'foo' };
102var recordCreated = false;
103Object.defineProperty(changeRecordWithAccessor, 'name', {
104  get: function() {
105    recordCreated = true;
106    return "bar";
107  },
108  enumerable: true
109})
110
111
112// Object.observe
113assertThrows(function() { Object.observe("non-object", observer.callback); },
114             TypeError);
115assertThrows(function() { Object.observe(this, observer.callback); },
116             TypeError);
117assertThrows(function() { Object.observe(obj, nonFunction); }, TypeError);
118assertThrows(function() { Object.observe(obj, frozenFunction); }, TypeError);
119assertEquals(obj, Object.observe(obj, observer.callback, [1]));
120assertEquals(obj, Object.observe(obj, observer.callback, [true]));
121assertEquals(obj, Object.observe(obj, observer.callback, ['foo', null]));
122assertEquals(obj, Object.observe(obj, observer.callback, [undefined]));
123assertEquals(obj, Object.observe(obj, observer.callback,
124             ['foo', 'bar', 'baz']));
125assertEquals(obj, Object.observe(obj, observer.callback, []));
126assertEquals(obj, Object.observe(obj, observer.callback, undefined));
127assertEquals(obj, Object.observe(obj, observer.callback));
128
129// Object.unobserve
130assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError);
131assertThrows(function() { Object.unobserve(this, observer.callback); },
132             TypeError);
133assertThrows(function() { Object.unobserve(obj, nonFunction); }, TypeError);
134assertEquals(obj, Object.unobserve(obj, observer.callback));
135
136
137// Object.getNotifier
138var notifier = Object.getNotifier(obj);
139assertSame(notifier, Object.getNotifier(obj));
140assertEquals(null, Object.getNotifier(Object.freeze({})));
141assertThrows(function() { Object.getNotifier(this) }, TypeError);
142assertFalse(notifier.hasOwnProperty('notify'));
143assertEquals([], Object.keys(notifier));
144var notifyDesc = Object.getOwnPropertyDescriptor(notifier.__proto__, 'notify');
145assertTrue(notifyDesc.configurable);
146assertTrue(notifyDesc.writable);
147assertFalse(notifyDesc.enumerable);
148assertThrows(function() { notifier.notify({}); }, TypeError);
149assertThrows(function() { notifier.notify({ type: 4 }); }, TypeError);
150
151assertThrows(function() { notifier.performChange(1, function(){}); }, TypeError);
152assertThrows(function() { notifier.performChange(undefined, function(){}); }, TypeError);
153assertThrows(function() { notifier.performChange('foo', undefined); }, TypeError);
154assertThrows(function() { notifier.performChange('foo', 'bar'); }, TypeError);
155var global = this;
156notifier.performChange('foo', function() {
157  assertEquals(global, this);
158});
159
160var notify = notifier.notify;
161assertThrows(function() { notify.call(undefined, { type: 'a' }); }, TypeError);
162assertThrows(function() { notify.call(null, { type: 'a' }); }, TypeError);
163assertThrows(function() { notify.call(5, { type: 'a' }); }, TypeError);
164assertThrows(function() { notify.call('hello', { type: 'a' }); }, TypeError);
165assertThrows(function() { notify.call(false, { type: 'a' }); }, TypeError);
166assertThrows(function() { notify.call({}, { type: 'a' }); }, TypeError);
167assertFalse(recordCreated);
168notifier.notify(changeRecordWithAccessor);
169assertFalse(recordCreated);  // not observed yet
170
171
172// Object.deliverChangeRecords
173assertThrows(function() { Object.deliverChangeRecords(nonFunction); }, TypeError);
174
175Object.observe(obj, observer.callback);
176
177
178// notify uses to [[CreateOwnProperty]] to create changeRecord;
179reset();
180var protoExpandoAccessed = false;
181Object.defineProperty(Object.prototype, 'protoExpando',
182  {
183    configurable: true,
184    set: function() { protoExpandoAccessed = true; }
185  }
186);
187notifier.notify({ type: 'foo', protoExpando: 'val'});
188assertFalse(protoExpandoAccessed);
189delete Object.prototype.protoExpando;
190Object.deliverChangeRecords(observer.callback);
191
192
193// Multiple records are delivered.
194reset();
195notifier.notify({
196  type: 'update',
197  name: 'foo',
198  expando: 1
199});
200
201notifier.notify({
202  object: notifier,  // object property is ignored
203  type: 'delete',
204  name: 'bar',
205  expando2: 'str'
206});
207Object.deliverChangeRecords(observer.callback);
208observer.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
214reset();
215Object.observe(obj, observer.callback, [true, 1, null, undefined]);
216notifier = Object.getNotifier(obj);
217notifier.notify({ type: 'true' });
218notifier.notify({ type: 'false' });
219notifier.notify({ type: '1' });
220notifier.notify({ type: '-1' });
221notifier.notify({ type: 'null' });
222notifier.notify({ type: 'nill' });
223notifier.notify({ type: 'undefined' });
224notifier.notify({ type: 'defined' });
225Object.deliverChangeRecords(observer.callback);
226observer.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
234reset();
235Object.deliverChangeRecords(observer.callback);
236observer.assertNotCalled();
237
238
239// Multiple observation has no effect.
240reset();
241Object.observe(obj, observer.callback);
242Object.observe(obj, observer.callback);
243Object.getNotifier(obj).notify({
244  type: 'update',
245});
246Object.deliverChangeRecords(observer.callback);
247observer.assertCalled();
248
249
250// Observation can be stopped.
251reset();
252Object.unobserve(obj, observer.callback);
253Object.getNotifier(obj).notify({
254  type: 'update',
255});
256Object.deliverChangeRecords(observer.callback);
257observer.assertNotCalled();
258
259
260// Multiple unobservation has no effect
261reset();
262Object.unobserve(obj, observer.callback);
263Object.unobserve(obj, observer.callback);
264Object.getNotifier(obj).notify({
265  type: 'update',
266});
267Object.deliverChangeRecords(observer.callback);
268observer.assertNotCalled();
269
270
271// Re-observation works and only includes changeRecords after of call.
272reset();
273Object.getNotifier(obj).notify({
274  type: 'update',
275});
276Object.observe(obj, observer.callback);
277Object.getNotifier(obj).notify({
278  type: 'update',
279});
280records = undefined;
281Object.deliverChangeRecords(observer.callback);
282observer.assertRecordCount(1);
283
284// Get notifier prior to observing
285reset();
286var obj = {};
287Object.getNotifier(obj);
288Object.observe(obj, observer.callback);
289obj.id = 1;
290Object.deliverChangeRecords(observer.callback);
291observer.assertCallbackRecords([
292  { object: obj, type: 'add', name: 'id' },
293]);
294
295// The empty-string property is observable
296reset();
297var obj = {};
298Object.observe(obj, observer.callback);
299obj[''] = '';
300obj[''] = ' ';
301delete obj[''];
302Object.deliverChangeRecords(observer.callback);
303observer.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
310reset();
311var obj = { foo: 'bar'};
312Object.observe(obj, observer.callback);
313obj.baz = 'bat';
314Object.preventExtensions(obj);
315
316Object.deliverChangeRecords(observer.callback);
317observer.assertCallbackRecords([
318  { object: obj, type: 'add', name: 'baz' },
319  { object: obj, type: 'preventExtensions' },
320]);
321
322reset();
323var obj = { foo: 'bar'};
324Object.preventExtensions(obj);
325Object.observe(obj, observer.callback);
326Object.preventExtensions(obj);
327Object.deliverChangeRecords(observer.callback);
328observer.assertNotCalled();
329
330// Object.freeze
331reset();
332var obj = { a: 'a' };
333Object.defineProperty(obj, 'b', {
334  writable: false,
335  configurable: true,
336  value: 'b'
337});
338Object.defineProperty(obj, 'c', {
339  writable: true,
340  configurable: false,
341  value: 'c'
342});
343Object.defineProperty(obj, 'd', {
344  writable: false,
345  configurable: false,
346  value: 'd'
347});
348Object.observe(obj, observer.callback);
349Object.freeze(obj);
350
351Object.deliverChangeRecords(observer.callback);
352observer.assertCallbackRecords([
353  { object: obj, type: 'preventExtensions' },
354  { object: obj, type: 'reconfigure', name: 'a' },
355  { object: obj, type: 'reconfigure', name: 'b' },
356  { object: obj, type: 'reconfigure', name: 'c' },
357]);
358
359reset();
360var obj = { foo: 'bar'};
361Object.freeze(obj);
362Object.observe(obj, observer.callback);
363Object.freeze(obj);
364Object.deliverChangeRecords(observer.callback);
365observer.assertNotCalled();
366
367// Object.seal
368reset();
369var obj = { a: 'a' };
370Object.defineProperty(obj, 'b', {
371  writable: false,
372  configurable: true,
373  value: 'b'
374});
375Object.defineProperty(obj, 'c', {
376  writable: true,
377  configurable: false,
378  value: 'c'
379});
380Object.defineProperty(obj, 'd', {
381  writable: false,
382  configurable: false,
383  value: 'd'
384});
385Object.observe(obj, observer.callback);
386Object.seal(obj);
387
388Object.deliverChangeRecords(observer.callback);
389observer.assertCallbackRecords([
390  { object: obj, type: 'preventExtensions' },
391  { object: obj, type: 'reconfigure', name: 'a' },
392  { object: obj, type: 'reconfigure', name: 'b' },
393]);
394
395reset();
396var obj = { foo: 'bar'};
397Object.seal(obj);
398Object.observe(obj, observer.callback);
399Object.seal(obj);
400Object.deliverChangeRecords(observer.callback);
401observer.assertNotCalled();
402
403// Observing a continuous stream of changes, while itermittantly unobserving.
404reset();
405var obj = {};
406Object.observe(obj, observer.callback);
407Object.getNotifier(obj).notify({
408  type: 'update',
409  val: 1
410});
411
412Object.unobserve(obj, observer.callback);
413Object.getNotifier(obj).notify({
414  type: 'update',
415  val: 2
416});
417
418Object.observe(obj, observer.callback);
419Object.getNotifier(obj).notify({
420  type: 'update',
421  val: 3
422});
423
424Object.unobserve(obj, observer.callback);
425Object.getNotifier(obj).notify({
426  type: 'update',
427  val: 4
428});
429
430Object.observe(obj, observer.callback);
431Object.getNotifier(obj).notify({
432  type: 'update',
433  val: 5
434});
435
436Object.unobserve(obj, observer.callback);
437Object.deliverChangeRecords(observer.callback);
438observer.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
445reset();
446Object.observe(obj, observer.callback, ['somethingElse']);
447Object.getNotifier(obj).notify({
448  type: 'add'
449});
450Object.getNotifier(obj).notify({
451  type: 'update'
452});
453Object.getNotifier(obj).notify({
454  type: 'delete'
455});
456Object.getNotifier(obj).notify({
457  type: 'reconfigure'
458});
459Object.getNotifier(obj).notify({
460  type: 'setPrototype'
461});
462Object.deliverChangeRecords(observer.callback);
463observer.assertNotCalled();
464
465reset();
466Object.observe(obj, observer.callback, ['add', 'delete', 'setPrototype']);
467Object.getNotifier(obj).notify({
468  type: 'add'
469});
470Object.getNotifier(obj).notify({
471  type: 'update'
472});
473Object.getNotifier(obj).notify({
474  type: 'delete'
475});
476Object.getNotifier(obj).notify({
477  type: 'delete'
478});
479Object.getNotifier(obj).notify({
480  type: 'reconfigure'
481});
482Object.getNotifier(obj).notify({
483  type: 'setPrototype'
484});
485Object.deliverChangeRecords(observer.callback);
486observer.assertCallbackRecords([
487  { object: obj, type: 'add' },
488  { object: obj, type: 'delete' },
489  { object: obj, type: 'delete' },
490  { object: obj, type: 'setPrototype' }
491]);
492
493reset();
494Object.observe(obj, observer.callback, ['update', 'foo']);
495Object.getNotifier(obj).notify({
496  type: 'add'
497});
498Object.getNotifier(obj).notify({
499  type: 'update'
500});
501Object.getNotifier(obj).notify({
502  type: 'delete'
503});
504Object.getNotifier(obj).notify({
505  type: 'foo'
506});
507Object.getNotifier(obj).notify({
508  type: 'bar'
509});
510Object.getNotifier(obj).notify({
511  type: 'foo'
512});
513Object.deliverChangeRecords(observer.callback);
514observer.assertCallbackRecords([
515  { object: obj, type: 'update' },
516  { object: obj, type: 'foo' },
517  { object: obj, type: 'foo' }
518]);
519
520reset();
521function Thingy(a, b, c) {
522  this.a = a;
523  this.b = b;
524}
525
526Thingy.MULTIPLY = 'multiply';
527Thingy.INCREMENT = 'increment';
528Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply';
529
530Thingy.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
575Thingy.observe = function(thingy, callback) {
576  Object.observe(thingy, callback, [Thingy.INCREMENT,
577                                    Thingy.MULTIPLY,
578                                    Thingy.INCREMENT_AND_MULTIPLY,
579                                    'update']);
580}
581
582Thingy.unobserve = function(thingy, callback) {
583  Object.unobserve(thingy);
584}
585
586var thingy = new Thingy(2, 4);
587
588Object.observe(thingy, observer.callback);
589Thingy.observe(thingy, observer2.callback);
590thingy.increment(3);               // { a: 5, b: 7 }
591thingy.b++;                        // { a: 5, b: 8 }
592thingy.multiply(2);                // { a: 10, b: 16 }
593thingy.a++;                        // { a: 11, b: 16 }
594thingy.incrementAndMultiply(2, 2); // { a: 26, b: 36 }
595
596Object.deliverChangeRecords(observer.callback);
597Object.deliverChangeRecords(observer2.callback);
598observer.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]);
610observer2.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
624reset();
625
626function pushMultiple(arr) {
627  arr.push('a');
628  arr.push('b');
629  arr.push('c');
630}
631
632for (var i = 0; i < 5; i++) {
633  var arr = [];
634  pushMultiple(arr);
635}
636
637for (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
656reset();
657
658function popMultiple(arr) {
659  arr.pop();
660  arr.pop();
661  arr.pop();
662}
663
664for (var i = 0; i < 5; i++) {
665  var arr = ['a', 'b', 'c'];
666  popMultiple(arr);
667}
668
669for (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
687reset();
688function RecursiveThingy() {}
689
690RecursiveThingy.MULTIPLY_FIRST_N = 'multiplyFirstN';
691
692RecursiveThingy.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
713RecursiveThingy.observe = function(thingy, callback) {
714  Object.observe(thingy, callback, [RecursiveThingy.MULTIPLY_FIRST_N]);
715}
716
717RecursiveThingy.unobserve = function(thingy, callback) {
718  Object.unobserve(thingy);
719}
720
721var thingy = new RecursiveThingy;
722thingy.push(1, 2, 3, 4);
723
724Object.observe(thingy, observer.callback);
725RecursiveThingy.observe(thingy, observer2.callback);
726thingy.multiplyFirstN(2, 3);                // [2, 4, 6, 4]
727
728Object.deliverChangeRecords(observer.callback);
729Object.deliverChangeRecords(observer2.callback);
730observer.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]);
735observer2.assertCallbackRecords([
736  { object: thingy, type: RecursiveThingy.MULTIPLY_FIRST_N, multiplied: 2, n: 3 }
737]);
738
739reset();
740function DeckSuit() {
741  this.push('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'A', 'Q', 'K');
742}
743
744DeckSuit.SHUFFLE = 'shuffle';
745
746DeckSuit.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
771DeckSuit.observe = function(thingy, callback) {
772  Object.observe(thingy, callback, [DeckSuit.SHUFFLE]);
773}
774
775DeckSuit.unobserve = function(thingy, callback) {
776  Object.unobserve(thingy);
777}
778
779var deck = new DeckSuit;
780
781DeckSuit.observe(deck, observer2.callback);
782deck.shuffle();
783
784Object.deliverChangeRecords(observer2.callback);
785observer2.assertCallbackRecords([
786  { object: deck, type: DeckSuit.SHUFFLE }
787]);
788
789// Observing multiple objects; records appear in order.
790reset();
791var obj2 = {};
792var obj3 = {}
793Object.observe(obj, observer.callback);
794Object.observe(obj3, observer.callback);
795Object.observe(obj2, observer.callback);
796Object.getNotifier(obj).notify({
797  type: 'add',
798});
799Object.getNotifier(obj2).notify({
800  type: 'update',
801});
802Object.getNotifier(obj3).notify({
803  type: 'delete',
804});
805Object.observe(obj3, observer.callback);
806Object.deliverChangeRecords(observer.callback);
807observer.assertCallbackRecords([
808  { object: obj, type: 'add' },
809  { object: obj2, type: 'update' },
810  { object: obj3, type: 'delete' }
811]);
812
813
814// Recursive observation.
815var obj = {a: 1};
816var callbackCount = 0;
817function recursiveObserver(r) {
818  assertEquals(1, r.length);
819  ++callbackCount;
820  if (r[0].oldValue < 100) ++obj[r[0].name];
821}
822Object.observe(obj, recursiveObserver);
823++obj.a;
824Object.deliverChangeRecords(recursiveObserver);
825assertEquals(100, callbackCount);
826
827var obj1 = {a: 1};
828var obj2 = {a: 1};
829var recordCount = 0;
830function recursiveObserver2(r) {
831  recordCount += r.length;
832  if (r[0].oldValue < 100) {
833    ++obj1.a;
834    ++obj2.a;
835  }
836}
837Object.observe(obj1, recursiveObserver2);
838Object.observe(obj2, recursiveObserver2);
839++obj1.a;
840Object.deliverChangeRecords(recursiveObserver2);
841assertEquals(199, recordCount);
842
843
844// Observing named properties.
845reset();
846var obj = {a: 1}
847Object.observe(obj, observer.callback);
848obj.a = 2;
849obj["a"] = 3;
850delete obj.a;
851obj.a = 4;
852obj.a = 4;  // ignored
853obj.a = 5;
854Object.defineProperty(obj, "a", {value: 6});
855Object.defineProperty(obj, "a", {writable: false});
856obj.a = 7;  // ignored
857Object.defineProperty(obj, "a", {value: 8});
858Object.defineProperty(obj, "a", {value: 7, writable: true});
859Object.defineProperty(obj, "a", {get: function() {}});
860Object.defineProperty(obj, "a", {get: frozenFunction});
861Object.defineProperty(obj, "a", {get: frozenFunction});  // ignored
862Object.defineProperty(obj, "a", {get: frozenFunction, set: frozenFunction});
863Object.defineProperty(obj, "a", {set: frozenFunction});  // ignored
864Object.defineProperty(obj, "a", {get: undefined, set: frozenFunction});
865delete obj.a;
866delete obj.a;
867Object.defineProperty(obj, "a", {get: function() {}, configurable: true});
868Object.defineProperty(obj, "a", {value: 9, writable: true});
869obj.a = 10;
870++obj.a;
871obj.a++;
872obj.a *= 3;
873delete obj.a;
874Object.defineProperty(obj, "a", {value: 11, configurable: true});
875Object.deliverChangeRecords(observer.callback);
876observer.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.
903reset();
904var obj = {'1': 1}
905Object.observe(obj, observer.callback);
906obj[1] = 2;
907obj[1] = 3;
908delete obj[1];
909obj[1] = 4;
910obj[1] = 4;  // ignored
911obj[1] = 5;
912Object.defineProperty(obj, "1", {value: 6});
913Object.defineProperty(obj, "1", {writable: false});
914obj[1] = 7;  // ignored
915Object.defineProperty(obj, "1", {value: 8});
916Object.defineProperty(obj, "1", {value: 7, writable: true});
917Object.defineProperty(obj, "1", {get: function() {}});
918Object.defineProperty(obj, "1", {get: frozenFunction});
919Object.defineProperty(obj, "1", {get: frozenFunction});  // ignored
920Object.defineProperty(obj, "1", {get: frozenFunction, set: frozenFunction});
921Object.defineProperty(obj, "1", {set: frozenFunction});  // ignored
922Object.defineProperty(obj, "1", {get: undefined, set: frozenFunction});
923delete obj[1];
924delete obj[1];
925Object.defineProperty(obj, "1", {get: function() {}, configurable: true});
926Object.defineProperty(obj, "1", {value: 9, writable: true});
927obj[1] = 10;
928++obj[1];
929obj[1]++;
930obj[1] *= 3;
931delete obj[1];
932Object.defineProperty(obj, "1", {value: 11, configurable: true});
933Object.deliverChangeRecords(observer.callback);
934observer.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).
961print("*****")
962reset();
963var obj = {}
964var symbol = Symbol("secret");
965Object.observe(obj, observer.callback);
966obj[symbol] = 3;
967delete obj[symbol];
968Object.defineProperty(obj, symbol, {get: function() {}, configurable: true});
969Object.defineProperty(obj, symbol, {value: 6});
970Object.defineProperty(obj, symbol, {writable: false});
971delete obj[symbol];
972Object.defineProperty(obj, symbol, {value: 7});
973++obj[symbol];
974obj[symbol]++;
975obj[symbol] *= 3;
976delete obj[symbol];
977obj.__defineSetter__(symbol, function() {});
978obj.__defineGetter__(symbol, function() {});
979Object.deliverChangeRecords(observer.callback);
980observer.assertNotCalled();
981
982
983// Test all kinds of objects generically.
984function 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
1055function 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/*
1083function 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
1124var 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];
1139var properties = ["a", "1", 1, "length", "setPrototype", "name", "caller"];
1140
1141// Cases that yield non-standard results.
1142function blacklisted(obj, prop) {
1143  return (obj instanceof Int32Array && prop == 1) ||
1144         (obj instanceof Int32Array && prop === "length") ||
1145         (obj instanceof ArrayBuffer && prop == 1) ||
1146         (obj instanceof Function && prop === "name") ||  // Has its own test.
1147         (obj instanceof Function && prop === "length");  // Has its own test.
1148}
1149
1150for (var i in objects) for (var j in properties) {
1151  var obj = objects[i];
1152  var prop = properties[j];
1153  if (blacklisted(obj, prop)) continue;
1154  var desc = Object.getOwnPropertyDescriptor(obj, prop);
1155  print("***", typeof obj, stringifyNoThrow(obj), prop);
1156  if (!desc || desc.configurable)
1157    TestObserveConfigurable(obj, prop);
1158  else if (desc.writable)
1159    TestObserveNonConfigurable(obj, prop, desc);
1160}
1161
1162
1163// Observing array length (including truncation)
1164reset();
1165var arr = ['a', 'b', 'c', 'd'];
1166var arr2 = ['alpha', 'beta'];
1167var arr3 = ['hello'];
1168arr3[2] = 'goodbye';
1169arr3.length = 6;
1170Object.defineProperty(arr, '0', {configurable: false});
1171Object.defineProperty(arr, '2', {get: function(){}});
1172Object.defineProperty(arr2, '0', {get: function(){}, configurable: false});
1173Object.observe(arr, observer.callback);
1174Array.observe(arr, observer2.callback);
1175Object.observe(arr2, observer.callback);
1176Array.observe(arr2, observer2.callback);
1177Object.observe(arr3, observer.callback);
1178Array.observe(arr3, observer2.callback);
1179arr.length = 2;
1180arr.length = 0;
1181arr.length = 10;
1182Object.defineProperty(arr, 'length', {writable: false});
1183arr2.length = 0;
1184arr2.length = 1; // no change expected
1185Object.defineProperty(arr2, 'length', {value: 1, writable: false});
1186arr3.length = 0;
1187++arr3.length;
1188arr3.length++;
1189arr3.length /= 2;
1190Object.defineProperty(arr3, 'length', {value: 5});
1191arr3[4] = 5;
1192Object.defineProperty(arr3, 'length', {value: 1, writable: false});
1193Object.deliverChangeRecords(observer.callback);
1194observer.assertCallbackRecords([
1195  { object: arr, name: '3', type: 'delete', oldValue: 'd' },
1196  { object: arr, name: '2', type: 'delete' },
1197  { object: arr, name: 'length', type: 'update', oldValue: 4 },
1198  { object: arr, name: '1', type: 'delete', oldValue: 'b' },
1199  { object: arr, name: 'length', type: 'update', oldValue: 2 },
1200  { object: arr, name: 'length', type: 'update', oldValue: 1 },
1201  { object: arr, name: 'length', type: 'reconfigure' },
1202  { object: arr2, name: '1', type: 'delete', oldValue: 'beta' },
1203  { object: arr2, name: 'length', type: 'update', oldValue: 2 },
1204  { object: arr2, name: 'length', type: 'reconfigure' },
1205  { object: arr3, name: '2', type: 'delete', oldValue: 'goodbye' },
1206  { object: arr3, name: '0', type: 'delete', oldValue: 'hello' },
1207  { object: arr3, name: 'length', type: 'update', oldValue: 6 },
1208  { object: arr3, name: 'length', type: 'update', oldValue: 0 },
1209  { object: arr3, name: 'length', type: 'update', oldValue: 1 },
1210  { object: arr3, name: 'length', type: 'update', oldValue: 2 },
1211  { object: arr3, name: 'length', type: 'update', oldValue: 1 },
1212  { object: arr3, name: '4', type: 'add' },
1213  { object: arr3, name: '4', type: 'delete', oldValue: 5 },
1214  // TODO(rafaelw): It breaks spec compliance to get two records here.
1215  // When the TODO in v8natives.js::DefineArrayProperty is addressed
1216  // which prevents DefineProperty from over-writing the magic length
1217  // property, these will collapse into a single record.
1218  { object: arr3, name: 'length', type: 'update', oldValue: 5 },
1219  { object: arr3, name: 'length', type: 'reconfigure' }
1220]);
1221Object.deliverChangeRecords(observer2.callback);
1222observer2.assertCallbackRecords([
1223  { object: arr, type: 'splice', index: 2, removed: [, 'd'], addedCount: 0 },
1224  { object: arr, type: 'splice', index: 1, removed: ['b'], addedCount: 0 },
1225  { object: arr, type: 'splice', index: 1, removed: [], addedCount: 9 },
1226  { object: arr2, type: 'splice', index: 1, removed: ['beta'], addedCount: 0 },
1227  { object: arr3, type: 'splice', index: 0, removed: ['hello',, 'goodbye',,,,], addedCount: 0 },
1228  { object: arr3, type: 'splice', index: 0, removed: [], addedCount: 1 },
1229  { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 1 },
1230  { object: arr3, type: 'splice', index: 1, removed: [,], addedCount: 0 },
1231  { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 4 },
1232  { object: arr3, name: '4', type: 'add' },
1233  { object: arr3, type: 'splice', index: 1, removed: [,,,5], addedCount: 0 }
1234]);
1235
1236
1237// Updating length on large (slow) array
1238reset();
1239var slow_arr = %NormalizeElements([]);
1240slow_arr[500000000] = 'hello';
1241slow_arr.length = 1000000000;
1242Object.observe(slow_arr, observer.callback);
1243var spliceRecords;
1244function slowSpliceCallback(records) {
1245  spliceRecords = records;
1246}
1247Array.observe(slow_arr, slowSpliceCallback);
1248slow_arr.length = 100;
1249Object.deliverChangeRecords(observer.callback);
1250observer.assertCallbackRecords([
1251  { object: slow_arr, name: '500000000', type: 'delete', oldValue: 'hello' },
1252  { object: slow_arr, name: 'length', type: 'update', oldValue: 1000000000 },
1253]);
1254Object.deliverChangeRecords(slowSpliceCallback);
1255assertEquals(spliceRecords.length, 1);
1256// Have to custom assert this splice record because the removed array is huge.
1257var splice = spliceRecords[0];
1258assertSame(splice.object, slow_arr);
1259assertEquals(splice.type, 'splice');
1260assertEquals(splice.index, 100);
1261assertEquals(splice.addedCount, 0);
1262var array_keys = %GetArrayKeys(splice.removed, splice.removed.length);
1263assertEquals(array_keys.length, 1);
1264assertEquals(array_keys[0], 499999900);
1265assertEquals(splice.removed[499999900], 'hello');
1266assertEquals(splice.removed.length, 999999900);
1267
1268
1269// Assignments in loops (checking different IC states).
1270reset();
1271var obj = {};
1272Object.observe(obj, observer.callback);
1273for (var i = 0; i < 5; i++) {
1274  obj["a" + i] = i;
1275}
1276Object.deliverChangeRecords(observer.callback);
1277observer.assertCallbackRecords([
1278  { object: obj, name: "a0", type: "add" },
1279  { object: obj, name: "a1", type: "add" },
1280  { object: obj, name: "a2", type: "add" },
1281  { object: obj, name: "a3", type: "add" },
1282  { object: obj, name: "a4", type: "add" },
1283]);
1284
1285reset();
1286var obj = {};
1287Object.observe(obj, observer.callback);
1288for (var i = 0; i < 5; i++) {
1289  obj[i] = i;
1290}
1291Object.deliverChangeRecords(observer.callback);
1292observer.assertCallbackRecords([
1293  { object: obj, name: "0", type: "add" },
1294  { object: obj, name: "1", type: "add" },
1295  { object: obj, name: "2", type: "add" },
1296  { object: obj, name: "3", type: "add" },
1297  { object: obj, name: "4", type: "add" },
1298]);
1299
1300
1301// Adding elements past the end of an array should notify on length for
1302// Object.observe and emit "splices" for Array.observe.
1303reset();
1304var arr = [1, 2, 3];
1305Object.observe(arr, observer.callback);
1306Array.observe(arr, observer2.callback);
1307arr[3] = 10;
1308arr[100] = 20;
1309Object.defineProperty(arr, '200', {value: 7});
1310Object.defineProperty(arr, '400', {get: function(){}});
1311arr[50] = 30; // no length change expected
1312Object.deliverChangeRecords(observer.callback);
1313observer.assertCallbackRecords([
1314  { object: arr, name: '3', type: 'add' },
1315  { object: arr, name: 'length', type: 'update', oldValue: 3 },
1316  { object: arr, name: '100', type: 'add' },
1317  { object: arr, name: 'length', type: 'update', oldValue: 4 },
1318  { object: arr, name: '200', type: 'add' },
1319  { object: arr, name: 'length', type: 'update', oldValue: 101 },
1320  { object: arr, name: '400', type: 'add' },
1321  { object: arr, name: 'length', type: 'update', oldValue: 201 },
1322  { object: arr, name: '50', type: 'add' },
1323]);
1324Object.deliverChangeRecords(observer2.callback);
1325observer2.assertCallbackRecords([
1326  { object: arr, type: 'splice', index: 3, removed: [], addedCount: 1 },
1327  { object: arr, type: 'splice', index: 4, removed: [], addedCount: 97 },
1328  { object: arr, type: 'splice', index: 101, removed: [], addedCount: 100 },
1329  { object: arr, type: 'splice', index: 201, removed: [], addedCount: 200 },
1330  { object: arr, type: 'add', name: '50' },
1331]);
1332
1333
1334// Tests for array methods, first on arrays and then on plain objects
1335//
1336// === ARRAYS ===
1337//
1338// Push
1339reset();
1340var array = [1, 2];
1341Object.observe(array, observer.callback);
1342Array.observe(array, observer2.callback);
1343array.push(3, 4);
1344array.push(5);
1345Object.deliverChangeRecords(observer.callback);
1346observer.assertCallbackRecords([
1347  { object: array, name: '2', type: 'add' },
1348  { object: array, name: 'length', type: 'update', oldValue: 2 },
1349  { object: array, name: '3', type: 'add' },
1350  { object: array, name: 'length', type: 'update', oldValue: 3 },
1351  { object: array, name: '4', type: 'add' },
1352  { object: array, name: 'length', type: 'update', oldValue: 4 },
1353]);
1354Object.deliverChangeRecords(observer2.callback);
1355observer2.assertCallbackRecords([
1356  { object: array, type: 'splice', index: 2, removed: [], addedCount: 2 },
1357  { object: array, type: 'splice', index: 4, removed: [], addedCount: 1 }
1358]);
1359
1360// Pop
1361reset();
1362var array = [1, 2];
1363Object.observe(array, observer.callback);
1364array.pop();
1365array.pop();
1366Object.deliverChangeRecords(observer.callback);
1367observer.assertCallbackRecords([
1368  { object: array, name: '1', type: 'delete', oldValue: 2 },
1369  { object: array, name: 'length', type: 'update', oldValue: 2 },
1370  { object: array, name: '0', type: 'delete', oldValue: 1 },
1371  { object: array, name: 'length', type: 'update', oldValue: 1 },
1372]);
1373
1374// Shift
1375reset();
1376var array = [1, 2];
1377Object.observe(array, observer.callback);
1378array.shift();
1379array.shift();
1380Object.deliverChangeRecords(observer.callback);
1381observer.assertCallbackRecords([
1382  { object: array, name: '0', type: 'update', oldValue: 1 },
1383  { object: array, name: '1', type: 'delete', oldValue: 2 },
1384  { object: array, name: 'length', type: 'update', oldValue: 2 },
1385  { object: array, name: '0', type: 'delete', oldValue: 2 },
1386  { object: array, name: 'length', type: 'update', oldValue: 1 },
1387]);
1388
1389// Unshift
1390reset();
1391var array = [1, 2];
1392Object.observe(array, observer.callback);
1393array.unshift(3, 4);
1394Object.deliverChangeRecords(observer.callback);
1395observer.assertCallbackRecords([
1396  { object: array, name: '3', type: 'add' },
1397  { object: array, name: 'length', type: 'update', oldValue: 2 },
1398  { object: array, name: '2', type: 'add' },
1399  { object: array, name: '0', type: 'update', oldValue: 1 },
1400  { object: array, name: '1', type: 'update', oldValue: 2 },
1401]);
1402
1403// Splice
1404reset();
1405var array = [1, 2, 3];
1406Object.observe(array, observer.callback);
1407array.splice(1, 1, 4, 5);
1408Object.deliverChangeRecords(observer.callback);
1409observer.assertCallbackRecords([
1410  { object: array, name: '3', type: 'add' },
1411  { object: array, name: 'length', type: 'update', oldValue: 3 },
1412  { object: array, name: '1', type: 'update', oldValue: 2 },
1413  { object: array, name: '2', type: 'update', oldValue: 3 },
1414]);
1415
1416// Sort
1417reset();
1418var array = [3, 2, 1];
1419Object.observe(array, observer.callback);
1420array.sort();
1421assertEquals(1, array[0]);
1422assertEquals(2, array[1]);
1423assertEquals(3, array[2]);
1424Object.deliverChangeRecords(observer.callback);
1425observer.assertCallbackRecords([
1426  { object: array, name: '1', type: 'update', oldValue: 2 },
1427  { object: array, name: '0', type: 'update', oldValue: 3 },
1428  { object: array, name: '2', type: 'update', oldValue: 1 },
1429  { object: array, name: '1', type: 'update', oldValue: 3 },
1430  { object: array, name: '0', type: 'update', oldValue: 2 },
1431]);
1432
1433// Splice emitted after Array mutation methods
1434function MockArray(initial, observer) {
1435  for (var i = 0; i < initial.length; i++)
1436    this[i] = initial[i];
1437
1438  this.length_ = initial.length;
1439  this.observer = observer;
1440}
1441MockArray.prototype = {
1442  set length(length) {
1443    Object.getNotifier(this).notify({ type: 'lengthChange' });
1444    this.length_ = length;
1445    Object.observe(this, this.observer.callback, ['splice']);
1446  },
1447  get length() {
1448    return this.length_;
1449  }
1450}
1451
1452reset();
1453var array = new MockArray([], observer);
1454Object.observe(array, observer.callback, ['lengthChange']);
1455Array.prototype.push.call(array, 1);
1456Object.deliverChangeRecords(observer.callback);
1457observer.assertCallbackRecords([
1458  { object: array, type: 'lengthChange' },
1459  { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 },
1460]);
1461
1462reset();
1463var array = new MockArray([1], observer);
1464Object.observe(array, observer.callback, ['lengthChange']);
1465Array.prototype.pop.call(array);
1466Object.deliverChangeRecords(observer.callback);
1467observer.assertCallbackRecords([
1468  { object: array, type: 'lengthChange' },
1469  { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
1470]);
1471
1472reset();
1473var array = new MockArray([1], observer);
1474Object.observe(array, observer.callback, ['lengthChange']);
1475Array.prototype.shift.call(array);
1476Object.deliverChangeRecords(observer.callback);
1477observer.assertCallbackRecords([
1478  { object: array, type: 'lengthChange' },
1479  { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
1480]);
1481
1482reset();
1483var array = new MockArray([], observer);
1484Object.observe(array, observer.callback, ['lengthChange']);
1485Array.prototype.unshift.call(array, 1);
1486Object.deliverChangeRecords(observer.callback);
1487observer.assertCallbackRecords([
1488  { object: array, type: 'lengthChange' },
1489  { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 },
1490]);
1491
1492reset();
1493var array = new MockArray([0, 1, 2], observer);
1494Object.observe(array, observer.callback, ['lengthChange']);
1495Array.prototype.splice.call(array, 1, 1);
1496Object.deliverChangeRecords(observer.callback);
1497observer.assertCallbackRecords([
1498  { object: array, type: 'lengthChange' },
1499  { object: array, type: 'splice', index: 1, removed: [1], addedCount: 0 },
1500]);
1501
1502//
1503// === PLAIN OBJECTS ===
1504//
1505// Push
1506reset()
1507var array = {0: 1, 1: 2, length: 2}
1508Object.observe(array, observer.callback);
1509Array.prototype.push.call(array, 3, 4);
1510Object.deliverChangeRecords(observer.callback);
1511observer.assertCallbackRecords([
1512  { object: array, name: '2', type: 'add' },
1513  { object: array, name: '3', type: 'add' },
1514  { object: array, name: 'length', type: 'update', oldValue: 2 },
1515]);
1516
1517// Pop
1518reset();
1519var array = [1, 2];
1520Object.observe(array, observer.callback);
1521Array.observe(array, observer2.callback);
1522array.pop();
1523array.pop();
1524array.pop();
1525Object.deliverChangeRecords(observer.callback);
1526observer.assertCallbackRecords([
1527  { object: array, name: '1', type: 'delete', oldValue: 2 },
1528  { object: array, name: 'length', type: 'update', oldValue: 2 },
1529  { object: array, name: '0', type: 'delete', oldValue: 1 },
1530  { object: array, name: 'length', type: 'update', oldValue: 1 },
1531]);
1532Object.deliverChangeRecords(observer2.callback);
1533observer2.assertCallbackRecords([
1534  { object: array, type: 'splice', index: 1, removed: [2], addedCount: 0 },
1535  { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }
1536]);
1537
1538// Shift
1539reset();
1540var array = [1, 2];
1541Object.observe(array, observer.callback);
1542Array.observe(array, observer2.callback);
1543array.shift();
1544array.shift();
1545array.shift();
1546Object.deliverChangeRecords(observer.callback);
1547observer.assertCallbackRecords([
1548  { object: array, name: '0', type: 'update', oldValue: 1 },
1549  { object: array, name: '1', type: 'delete', oldValue: 2 },
1550  { object: array, name: 'length', type: 'update', oldValue: 2 },
1551  { object: array, name: '0', type: 'delete', oldValue: 2 },
1552  { object: array, name: 'length', type: 'update', oldValue: 1 },
1553]);
1554Object.deliverChangeRecords(observer2.callback);
1555observer2.assertCallbackRecords([
1556  { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
1557  { object: array, type: 'splice', index: 0, removed: [2], addedCount: 0 }
1558]);
1559
1560// Unshift
1561reset();
1562var array = [1, 2];
1563Object.observe(array, observer.callback);
1564Array.observe(array, observer2.callback);
1565array.unshift(3, 4);
1566array.unshift(5);
1567Object.deliverChangeRecords(observer.callback);
1568observer.assertCallbackRecords([
1569  { object: array, name: '3', type: 'add' },
1570  { object: array, name: 'length', type: 'update', oldValue: 2 },
1571  { object: array, name: '2', type: 'add' },
1572  { object: array, name: '0', type: 'update', oldValue: 1 },
1573  { object: array, name: '1', type: 'update', oldValue: 2 },
1574  { object: array, name: '4', type: 'add' },
1575  { object: array, name: 'length', type: 'update', oldValue: 4 },
1576  { object: array, name: '3', type: 'update', oldValue: 2 },
1577  { object: array, name: '2', type: 'update', oldValue: 1 },
1578  { object: array, name: '1', type: 'update', oldValue: 4 },
1579  { object: array, name: '0', type: 'update', oldValue: 3 },
1580]);
1581Object.deliverChangeRecords(observer2.callback);
1582observer2.assertCallbackRecords([
1583  { object: array, type: 'splice', index: 0, removed: [], addedCount: 2 },
1584  { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }
1585]);
1586
1587// Splice
1588reset();
1589var array = [1, 2, 3];
1590Object.observe(array, observer.callback);
1591Array.observe(array, observer2.callback);
1592array.splice(1, 0, 4, 5); // 1 4 5 2 3
1593array.splice(0, 2); // 5 2 3
1594array.splice(1, 2, 6, 7); // 5 6 7
1595array.splice(2, 0);
1596Object.deliverChangeRecords(observer.callback);
1597observer.assertCallbackRecords([
1598  { object: array, name: '4', type: 'add' },
1599  { object: array, name: 'length', type: 'update', oldValue: 3 },
1600  { object: array, name: '3', type: 'add' },
1601  { object: array, name: '1', type: 'update', oldValue: 2 },
1602  { object: array, name: '2', type: 'update', oldValue: 3 },
1603
1604  { object: array, name: '0', type: 'update', oldValue: 1 },
1605  { object: array, name: '1', type: 'update', oldValue: 4 },
1606  { object: array, name: '2', type: 'update', oldValue: 5 },
1607  { object: array, name: '4', type: 'delete', oldValue: 3 },
1608  { object: array, name: '3', type: 'delete', oldValue: 2 },
1609  { object: array, name: 'length', type: 'update', oldValue: 5 },
1610
1611  { object: array, name: '1', type: 'update', oldValue: 2 },
1612  { object: array, name: '2', type: 'update', oldValue: 3 },
1613]);
1614Object.deliverChangeRecords(observer2.callback);
1615observer2.assertCallbackRecords([
1616  { object: array, type: 'splice', index: 1, removed: [], addedCount: 2 },
1617  { object: array, type: 'splice', index: 0, removed: [1, 4], addedCount: 0 },
1618  { object: array, type: 'splice', index: 1, removed: [2, 3], addedCount: 2 },
1619]);
1620
1621// Exercise StoreIC_ArrayLength
1622reset();
1623var dummy = {};
1624Object.observe(dummy, observer.callback);
1625Object.unobserve(dummy, observer.callback);
1626var array = [0];
1627Object.observe(array, observer.callback);
1628array.splice(0, 1);
1629Object.deliverChangeRecords(observer.callback);
1630observer.assertCallbackRecords([
1631  { object: array, name: '0', type: 'delete', oldValue: 0 },
1632  { object: array, name: 'length', type: 'update', oldValue: 1},
1633]);
1634
1635
1636// __proto__
1637reset();
1638var obj = {};
1639Object.observe(obj, observer.callback);
1640var p = {foo: 'yes'};
1641var q = {bar: 'no'};
1642obj.__proto__ = p;
1643obj.__proto__ = p;  // ignored
1644obj.__proto__ = null;
1645obj.__proto__ = q;  // the __proto__ accessor is gone
1646// TODO(adamk): Add tests for objects with hidden prototypes
1647// once we support observing the global object.
1648Object.deliverChangeRecords(observer.callback);
1649observer.assertCallbackRecords([
1650  { object: obj, name: '__proto__', type: 'setPrototype',
1651    oldValue: Object.prototype },
1652  { object: obj, name: '__proto__', type: 'setPrototype', oldValue: p },
1653  { object: obj, name: '__proto__', type: 'add' },
1654]);
1655
1656
1657// Function.prototype
1658reset();
1659var fun = function(){};
1660Object.observe(fun, observer.callback);
1661var myproto = {foo: 'bar'};
1662fun.prototype = myproto;
1663fun.prototype = 7;
1664fun.prototype = 7;  // ignored
1665Object.defineProperty(fun, 'prototype', {value: 8});
1666Object.deliverChangeRecords(observer.callback);
1667observer.assertRecordCount(3);
1668// Manually examine the first record in order to test
1669// lazy creation of oldValue
1670assertSame(fun, observer.records[0].object);
1671assertEquals('prototype', observer.records[0].name);
1672assertEquals('update', observer.records[0].type);
1673// The only existing reference to the oldValue object is in this
1674// record, so to test that lazy creation happened correctly
1675// we compare its constructor to our function (one of the invariants
1676// ensured when creating an object via AllocateFunctionPrototype).
1677assertSame(fun, observer.records[0].oldValue.constructor);
1678observer.records.splice(0, 1);
1679observer.assertCallbackRecords([
1680  { object: fun, name: 'prototype', type: 'update', oldValue: myproto },
1681  { object: fun, name: 'prototype', type: 'update', oldValue: 7 },
1682]);
1683
1684// Function.prototype should not be observable except on the object itself
1685reset();
1686var fun = function(){};
1687var obj = { __proto__: fun };
1688Object.observe(obj, observer.callback);
1689obj.prototype = 7;
1690Object.deliverChangeRecords(observer.callback);
1691observer.assertRecordCount(1);
1692observer.assertCallbackRecords([
1693  { object: obj, name: 'prototype', type: 'add' },
1694]);
1695
1696// Check that changes in observation status are detected in all IC states and
1697// in optimized code, especially in cases usually using fast elements.
1698var mutation = [
1699  "a[i] = v",
1700  "a[i] ? ++a[i] : a[i] = v",
1701  "a[i] ? a[i]++ : a[i] = v",
1702  "a[i] ? a[i] += 1 : a[i] = v",
1703  "a[i] ? a[i] -= -1 : a[i] = v",
1704];
1705
1706var props = [1, "1", "a"];
1707
1708function TestFastElements(prop, mutation, prepopulate, polymorphic, optimize) {
1709  var setElement = eval(
1710    "(function setElement(a, i, v) { " + mutation + "; " +
1711    "/* " + [].join.call(arguments, " ") + " */" +
1712    "})"
1713  );
1714  print("TestFastElements:", setElement);
1715
1716  var arr = prepopulate ? [1, 2, 3, 4, 5] : [0];
1717  if (prepopulate) arr[prop] = 2;  // for non-element case
1718  setElement(arr, prop, 3);
1719  setElement(arr, prop, 4);
1720  if (polymorphic) setElement(["M", "i", "l", "n", "e", "r"], 0, "m");
1721  if (optimize) %OptimizeFunctionOnNextCall(setElement);
1722  setElement(arr, prop, 5);
1723
1724  reset();
1725  Object.observe(arr, observer.callback);
1726  setElement(arr, prop, 989898);
1727  Object.deliverChangeRecords(observer.callback);
1728  observer.assertCallbackRecords([
1729    { object: arr, name: "" + prop, type: 'update', oldValue: 5 }
1730  ]);
1731}
1732
1733for (var b1 = 0; b1 < 2; ++b1)
1734  for (var b2 = 0; b2 < 2; ++b2)
1735    for (var b3 = 0; b3 < 2; ++b3)
1736      for (var i in props)
1737        for (var j in mutation)
1738          TestFastElements(props[i], mutation[j], b1 != 0, b2 != 0, b3 != 0);
1739
1740
1741var mutation = [
1742  "a.length = v",
1743  "a.length += newSize - oldSize",
1744  "a.length -= oldSize - newSize",
1745];
1746
1747var mutationByIncr = [
1748  "++a.length",
1749  "a.length++",
1750];
1751
1752function TestFastElementsLength(
1753  mutation, polymorphic, optimize, oldSize, newSize) {
1754  var setLength = eval(
1755    "(function setLength(a, v) { " + mutation + "; " +
1756    "/* " + [].join.call(arguments, " ") + " */"
1757    + "})"
1758  );
1759  print("TestFastElementsLength:", setLength);
1760
1761  function array(n) {
1762    var arr = new Array(n);
1763    for (var i = 0; i < n; ++i) arr[i] = i;
1764    return arr;
1765  }
1766
1767  setLength(array(oldSize), newSize);
1768  setLength(array(oldSize), newSize);
1769  if (polymorphic) setLength(array(oldSize).map(isNaN), newSize);
1770  if (optimize) %OptimizeFunctionOnNextCall(setLength);
1771  setLength(array(oldSize), newSize);
1772
1773  reset();
1774  var arr = array(oldSize);
1775  Object.observe(arr, observer.callback);
1776  setLength(arr, newSize);
1777  Object.deliverChangeRecords(observer.callback);
1778  if (oldSize === newSize) {
1779    observer.assertNotCalled();
1780  } else {
1781    var count = oldSize > newSize ? oldSize - newSize : 0;
1782    observer.assertRecordCount(count + 1);
1783    var lengthRecord = observer.records[count];
1784    assertSame(arr, lengthRecord.object);
1785    assertEquals('length', lengthRecord.name);
1786    assertEquals('update', lengthRecord.type);
1787    assertSame(oldSize, lengthRecord.oldValue);
1788  }
1789}
1790
1791for (var b1 = 0; b1 < 2; ++b1)
1792  for (var b2 = 0; b2 < 2; ++b2)
1793    for (var n1 = 0; n1 < 3; ++n1)
1794      for (var n2 = 0; n2 < 3; ++n2)
1795        for (var i in mutation)
1796          TestFastElementsLength(mutation[i], b1 != 0, b2 != 0, 20*n1, 20*n2);
1797
1798for (var b1 = 0; b1 < 2; ++b1)
1799  for (var b2 = 0; b2 < 2; ++b2)
1800    for (var n = 0; n < 3; ++n)
1801      for (var i in mutationByIncr)
1802        TestFastElementsLength(mutationByIncr[i], b1 != 0, b2 != 0, 7*n, 7*n+1);
1803
1804
1805(function TestFunctionName() {
1806  reset();
1807
1808  function fun() {}
1809  Object.observe(fun, observer.callback);
1810  fun.name = 'x';  // No change. Not writable.
1811  Object.defineProperty(fun, 'name', {value: 'a'});
1812  Object.defineProperty(fun, 'name', {writable: true});
1813  fun.name = 'b';
1814  delete fun.name;
1815  fun.name = 'x';  // No change. Function.prototype.name is non writable
1816  Object.defineProperty(Function.prototype, 'name', {writable: true});
1817  fun.name = 'c';
1818  fun.name = 'c';  // Same, no update.
1819  Object.deliverChangeRecords(observer.callback);
1820  observer.assertCallbackRecords([
1821    { object: fun, type: 'update', name: 'name', oldValue: 'fun' },
1822    { object: fun, type: 'reconfigure', name: 'name'},
1823    { object: fun, type: 'update', name: 'name', oldValue: 'a' },
1824    { object: fun, type: 'delete', name: 'name', oldValue: 'b' },
1825    { object: fun, type: 'add', name: 'name' },
1826  ]);
1827})();
1828
1829
1830(function TestFunctionLength() {
1831  reset();
1832
1833  function fun(x) {}
1834  Object.observe(fun, observer.callback);
1835  fun.length = 'x';  // No change. Not writable.
1836  Object.defineProperty(fun, 'length', {value: 'a'});
1837  Object.defineProperty(fun, 'length', {writable: true});
1838  fun.length = 'b';
1839  delete fun.length;
1840  fun.length = 'x';  // No change. Function.prototype.length is non writable
1841  Object.defineProperty(Function.prototype, 'length', {writable: true});
1842  fun.length = 'c';
1843  fun.length = 'c';  // Same, no update.
1844  Object.deliverChangeRecords(observer.callback);
1845  observer.assertCallbackRecords([
1846    { object: fun, type: 'update', name: 'length', oldValue: 1 },
1847    { object: fun, type: 'reconfigure', name: 'length'},
1848    { object: fun, type: 'update', name: 'length', oldValue: 'a' },
1849    { object: fun, type: 'delete', name: 'length', oldValue: 'b' },
1850    { object: fun, type: 'add', name: 'length' },
1851  ]);
1852})();
1853
1854
1855(function TestObserveInvalidAcceptMessage() {
1856  var ex;
1857  try {
1858    Object.observe({}, function(){}, "not an object");
1859  } catch (e) {
1860    ex = e;
1861  }
1862  assertInstanceof(ex, TypeError);
1863  assertEquals("Third argument to Object.observe must be an array of strings.",
1864               ex.message);
1865})()
1866