The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.

Please select the desired protocol below to get the URL.

This URL has Read-Only access.

Statistics
| Branch: | Revision:

main_repo / deps / v8 / test / mjsunit / harmony / object-observe.js @ f230a1cf

History | View | Annotate | Download (53.7 KB)

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 --harmony-collections
29
// Flags: --harmony-symbols --allow-natives-syntax
30

    
31
var allObservers = [];
32
function reset() {
33
  allObservers.forEach(function(observer) { observer.reset(); });
34
}
35

    
36
function stringifyNoThrow(arg) {
37
  try {
38
    return JSON.stringify(arg);
39
  } catch (e) {
40
    return '{<circular reference>}';
41
  }
42
}
43

    
44
function createObserver() {
45
  "use strict";  // So that |this| in callback can be undefined.
46

    
47
  var observer = {
48
    records: undefined,
49
    callbackCount: 0,
50
    reset: function() {
51
      this.records = undefined;
52
      this.callbackCount = 0;
53
    },
54
    assertNotCalled: function() {
55
      assertEquals(undefined, this.records);
56
      assertEquals(0, this.callbackCount);
57
    },
58
    assertCalled: function() {
59
      assertEquals(1, this.callbackCount);
60
    },
61
    assertRecordCount: function(count) {
62
      this.assertCalled();
63
      assertEquals(count, this.records.length);
64
    },
65
    assertCallbackRecords: function(recs) {
66
      this.assertRecordCount(recs.length);
67
      for (var i = 0; i < recs.length; i++) {
68
        if ('name' in recs[i]) recs[i].name = String(recs[i].name);
69
        print(i, stringifyNoThrow(this.records[i]), stringifyNoThrow(recs[i]));
70
        assertSame(this.records[i].object, recs[i].object);
71
        assertEquals('string', typeof recs[i].type);
72
        assertPropertiesEqual(this.records[i], recs[i]);
73
      }
74
    }
75
  };
76

    
77
  observer.callback = function(r) {
78
    assertEquals(undefined, this);
79
    assertEquals('object', typeof r);
80
    assertTrue(r instanceof Array)
81
    observer.records = r;
82
    observer.callbackCount++;
83
  };
84

    
85
  observer.reset();
86
  allObservers.push(observer);
87
  return observer;
88
}
89

    
90
var observer = createObserver();
91
var observer2 = createObserver();
92

    
93
assertEquals("function", typeof observer.callback);
94
assertEquals("function", typeof observer2.callback);
95

    
96
var obj = {};
97

    
98
function frozenFunction() {}
99
Object.freeze(frozenFunction);
100
var nonFunction = {};
101
var changeRecordWithAccessor = { type: 'foo' };
102
var recordCreated = false;
103
Object.defineProperty(changeRecordWithAccessor, 'name', {
104
  get: function() {
105
    recordCreated = true;
106
    return "bar";
107
  },
108
  enumerable: true
109
})
110

    
111

    
112
// Object.observe
113
assertThrows(function() { Object.observe("non-object", observer.callback); },
114
             TypeError);
115
assertThrows(function() { Object.observe(obj, nonFunction); }, TypeError);
116
assertThrows(function() { Object.observe(obj, frozenFunction); }, TypeError);
117
assertEquals(obj, Object.observe(obj, observer.callback, [1]));
118
assertEquals(obj, Object.observe(obj, observer.callback, [true]));
119
assertEquals(obj, Object.observe(obj, observer.callback, ['foo', null]));
120
assertEquals(obj, Object.observe(obj, observer.callback, [undefined]));
121
assertEquals(obj, Object.observe(obj, observer.callback,
122
             ['foo', 'bar', 'baz']));
123
assertEquals(obj, Object.observe(obj, observer.callback, []));
124
assertEquals(obj, Object.observe(obj, observer.callback, undefined));
125
assertEquals(obj, Object.observe(obj, observer.callback));
126

    
127
// Object.unobserve
128
assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError);
129
assertThrows(function() { Object.unobserve(obj, nonFunction); }, TypeError);
130
assertEquals(obj, Object.unobserve(obj, observer.callback));
131

    
132

    
133
// Object.getNotifier
134
var notifier = Object.getNotifier(obj);
135
assertSame(notifier, Object.getNotifier(obj));
136
assertEquals(null, Object.getNotifier(Object.freeze({})));
137
assertFalse(notifier.hasOwnProperty('notify'));
138
assertEquals([], Object.keys(notifier));
139
var notifyDesc = Object.getOwnPropertyDescriptor(notifier.__proto__, 'notify');
140
assertTrue(notifyDesc.configurable);
141
assertTrue(notifyDesc.writable);
142
assertFalse(notifyDesc.enumerable);
143
assertThrows(function() { notifier.notify({}); }, TypeError);
144
assertThrows(function() { notifier.notify({ type: 4 }); }, TypeError);
145

    
146
assertThrows(function() { notifier.performChange(1, function(){}); }, TypeError);
147
assertThrows(function() { notifier.performChange(undefined, function(){}); }, TypeError);
148
assertThrows(function() { notifier.performChange('foo', undefined); }, TypeError);
149
assertThrows(function() { notifier.performChange('foo', 'bar'); }, TypeError);
150
notifier.performChange('foo', function() {
151
  assertEquals(undefined, this);
152
});
153

    
154
var notify = notifier.notify;
155
assertThrows(function() { notify.call(undefined, { type: 'a' }); }, TypeError);
156
assertThrows(function() { notify.call(null, { type: 'a' }); }, TypeError);
157
assertThrows(function() { notify.call(5, { type: 'a' }); }, TypeError);
158
assertThrows(function() { notify.call('hello', { type: 'a' }); }, TypeError);
159
assertThrows(function() { notify.call(false, { type: 'a' }); }, TypeError);
160
assertThrows(function() { notify.call({}, { type: 'a' }); }, TypeError);
161
assertFalse(recordCreated);
162
notifier.notify(changeRecordWithAccessor);
163
assertFalse(recordCreated);  // not observed yet
164

    
165

    
166
// Object.deliverChangeRecords
167
assertThrows(function() { Object.deliverChangeRecords(nonFunction); }, TypeError);
168

    
169
Object.observe(obj, observer.callback);
170

    
171

    
172
// notify uses to [[CreateOwnProperty]] to create changeRecord;
173
reset();
174
var protoExpandoAccessed = false;
175
Object.defineProperty(Object.prototype, 'protoExpando',
176
  {
177
    configurable: true,
178
    set: function() { protoExpandoAccessed = true; }
179
  }
180
);
181
notifier.notify({ type: 'foo', protoExpando: 'val'});
182
assertFalse(protoExpandoAccessed);
183
delete Object.prototype.protoExpando;
184
Object.deliverChangeRecords(observer.callback);
185

    
186

    
187
// Multiple records are delivered.
188
reset();
189
notifier.notify({
190
  type: 'updated',
191
  name: 'foo',
192
  expando: 1
193
});
194

    
195
notifier.notify({
196
  object: notifier,  // object property is ignored
197
  type: 'deleted',
198
  name: 'bar',
199
  expando2: 'str'
200
});
201
Object.deliverChangeRecords(observer.callback);
202
observer.assertCallbackRecords([
203
  { object: obj, name: 'foo', type: 'updated', expando: 1 },
204
  { object: obj, name: 'bar', type: 'deleted', expando2: 'str' }
205
]);
206

    
207
// Non-string accept values are coerced to strings
208
reset();
209
Object.observe(obj, observer.callback, [true, 1, null, undefined]);
210
notifier = Object.getNotifier(obj);
211
notifier.notify({ type: 'true' });
212
notifier.notify({ type: 'false' });
213
notifier.notify({ type: '1' });
214
notifier.notify({ type: '-1' });
215
notifier.notify({ type: 'null' });
216
notifier.notify({ type: 'nill' });
217
notifier.notify({ type: 'undefined' });
218
notifier.notify({ type: 'defined' });
219
Object.deliverChangeRecords(observer.callback);
220
observer.assertCallbackRecords([
221
  { object: obj, type: 'true' },
222
  { object: obj, type: '1' },
223
  { object: obj, type: 'null' },
224
  { object: obj, type: 'undefined' }
225
]);
226

    
227
// No delivery takes place if no records are pending
228
reset();
229
Object.deliverChangeRecords(observer.callback);
230
observer.assertNotCalled();
231

    
232

    
233
// Multiple observation has no effect.
234
reset();
235
Object.observe(obj, observer.callback);
236
Object.observe(obj, observer.callback);
237
Object.getNotifier(obj).notify({
238
  type: 'updated',
239
});
240
Object.deliverChangeRecords(observer.callback);
241
observer.assertCalled();
242

    
243

    
244
// Observation can be stopped.
245
reset();
246
Object.unobserve(obj, observer.callback);
247
Object.getNotifier(obj).notify({
248
  type: 'updated',
249
});
250
Object.deliverChangeRecords(observer.callback);
251
observer.assertNotCalled();
252

    
253

    
254
// Multiple unobservation has no effect
255
reset();
256
Object.unobserve(obj, observer.callback);
257
Object.unobserve(obj, observer.callback);
258
Object.getNotifier(obj).notify({
259
  type: 'updated',
260
});
261
Object.deliverChangeRecords(observer.callback);
262
observer.assertNotCalled();
263

    
264

    
265
// Re-observation works and only includes changeRecords after of call.
266
reset();
267
Object.getNotifier(obj).notify({
268
  type: 'updated',
269
});
270
Object.observe(obj, observer.callback);
271
Object.getNotifier(obj).notify({
272
  type: 'updated',
273
});
274
records = undefined;
275
Object.deliverChangeRecords(observer.callback);
276
observer.assertRecordCount(1);
277

    
278
// Get notifier prior to observing
279
reset();
280
var obj = {};
281
Object.getNotifier(obj);
282
Object.observe(obj, observer.callback);
283
obj.id = 1;
284
Object.deliverChangeRecords(observer.callback);
285
observer.assertCallbackRecords([
286
  { object: obj, type: 'new', name: 'id' },
287
]);
288

    
289
// The empty-string property is observable
290
reset();
291
var obj = {};
292
Object.observe(obj, observer.callback);
293
obj[''] = '';
294
obj[''] = ' ';
295
delete obj[''];
296
Object.deliverChangeRecords(observer.callback);
297
observer.assertCallbackRecords([
298
  { object: obj, type: 'new', name: '' },
299
  { object: obj, type: 'updated', name: '', oldValue: '' },
300
  { object: obj, type: 'deleted', name: '', oldValue: ' ' },
301
]);
302

    
303
// Observing a continuous stream of changes, while itermittantly unobserving.
304
reset();
305
Object.observe(obj, observer.callback);
306
Object.getNotifier(obj).notify({
307
  type: 'updated',
308
  val: 1
309
});
310

    
311
Object.unobserve(obj, observer.callback);
312
Object.getNotifier(obj).notify({
313
  type: 'updated',
314
  val: 2
315
});
316

    
317
Object.observe(obj, observer.callback);
318
Object.getNotifier(obj).notify({
319
  type: 'updated',
320
  val: 3
321
});
322

    
323
Object.unobserve(obj, observer.callback);
324
Object.getNotifier(obj).notify({
325
  type: 'updated',
326
  val: 4
327
});
328

    
329
Object.observe(obj, observer.callback);
330
Object.getNotifier(obj).notify({
331
  type: 'updated',
332
  val: 5
333
});
334

    
335
Object.unobserve(obj, observer.callback);
336
Object.deliverChangeRecords(observer.callback);
337
observer.assertCallbackRecords([
338
  { object: obj, type: 'updated', val: 1 },
339
  { object: obj, type: 'updated', val: 3 },
340
  { object: obj, type: 'updated', val: 5 }
341
]);
342

    
343
// Accept
344
reset();
345
Object.observe(obj, observer.callback, ['somethingElse']);
346
Object.getNotifier(obj).notify({
347
  type: 'new'
348
});
349
Object.getNotifier(obj).notify({
350
  type: 'updated'
351
});
352
Object.getNotifier(obj).notify({
353
  type: 'deleted'
354
});
355
Object.getNotifier(obj).notify({
356
  type: 'reconfigured'
357
});
358
Object.getNotifier(obj).notify({
359
  type: 'prototype'
360
});
361
Object.deliverChangeRecords(observer.callback);
362
observer.assertNotCalled();
363

    
364
reset();
365
Object.observe(obj, observer.callback, ['new', 'deleted', 'prototype']);
366
Object.getNotifier(obj).notify({
367
  type: 'new'
368
});
369
Object.getNotifier(obj).notify({
370
  type: 'updated'
371
});
372
Object.getNotifier(obj).notify({
373
  type: 'deleted'
374
});
375
Object.getNotifier(obj).notify({
376
  type: 'deleted'
377
});
378
Object.getNotifier(obj).notify({
379
  type: 'reconfigured'
380
});
381
Object.getNotifier(obj).notify({
382
  type: 'prototype'
383
});
384
Object.deliverChangeRecords(observer.callback);
385
observer.assertCallbackRecords([
386
  { object: obj, type: 'new' },
387
  { object: obj, type: 'deleted' },
388
  { object: obj, type: 'deleted' },
389
  { object: obj, type: 'prototype' }
390
]);
391

    
392
reset();
393
Object.observe(obj, observer.callback, ['updated', 'foo']);
394
Object.getNotifier(obj).notify({
395
  type: 'new'
396
});
397
Object.getNotifier(obj).notify({
398
  type: 'updated'
399
});
400
Object.getNotifier(obj).notify({
401
  type: 'deleted'
402
});
403
Object.getNotifier(obj).notify({
404
  type: 'foo'
405
});
406
Object.getNotifier(obj).notify({
407
  type: 'bar'
408
});
409
Object.getNotifier(obj).notify({
410
  type: 'foo'
411
});
412
Object.deliverChangeRecords(observer.callback);
413
observer.assertCallbackRecords([
414
  { object: obj, type: 'updated' },
415
  { object: obj, type: 'foo' },
416
  { object: obj, type: 'foo' }
417
]);
418

    
419
reset();
420
function Thingy(a, b, c) {
421
  this.a = a;
422
  this.b = b;
423
}
424

    
425
Thingy.MULTIPLY = 'multiply';
426
Thingy.INCREMENT = 'increment';
427
Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply';
428

    
429
Thingy.prototype = {
430
  increment: function(amount) {
431
    var notifier = Object.getNotifier(this);
432

    
433
    var self = this;
434
    notifier.performChange(Thingy.INCREMENT, function() {
435
      self.a += amount;
436
      self.b += amount;
437
    });
438

    
439
    notifier.notify({
440
      object: this,
441
      type: Thingy.INCREMENT,
442
      incremented: amount
443
    });
444
  },
445

    
446
  multiply: function(amount) {
447
    var notifier = Object.getNotifier(this);
448

    
449
    var self = this;
450
    notifier.performChange(Thingy.MULTIPLY, function() {
451
      self.a *= amount;
452
      self.b *= amount;
453
    });
454

    
455
    notifier.notify({
456
      object: this,
457
      type: Thingy.MULTIPLY,
458
      multiplied: amount
459
    });
460
  },
461

    
462
  incrementAndMultiply: function(incAmount, multAmount) {
463
    var notifier = Object.getNotifier(this);
464

    
465
    var self = this;
466
    notifier.performChange(Thingy.INCREMENT_AND_MULTIPLY, function() {
467
      self.increment(incAmount);
468
      self.multiply(multAmount);
469
    });
470

    
471
    notifier.notify({
472
      object: this,
473
      type: Thingy.INCREMENT_AND_MULTIPLY,
474
      incremented: incAmount,
475
      multiplied: multAmount
476
    });
477
  }
478
}
479

    
480
Thingy.observe = function(thingy, callback) {
481
  Object.observe(thingy, callback, [Thingy.INCREMENT,
482
                                    Thingy.MULTIPLY,
483
                                    Thingy.INCREMENT_AND_MULTIPLY,
484
                                    'updated']);
485
}
486

    
487
Thingy.unobserve = function(thingy, callback) {
488
  Object.unobserve(thingy);
489
}
490

    
491
var thingy = new Thingy(2, 4);
492

    
493
Object.observe(thingy, observer.callback);
494
Thingy.observe(thingy, observer2.callback);
495
thingy.increment(3);               // { a: 5, b: 7 }
496
thingy.b++;                        // { a: 5, b: 8 }
497
thingy.multiply(2);                // { a: 10, b: 16 }
498
thingy.a++;                        // { a: 11, b: 16 }
499
thingy.incrementAndMultiply(2, 2); // { a: 26, b: 36 }
500

    
501
Object.deliverChangeRecords(observer.callback);
502
Object.deliverChangeRecords(observer2.callback);
503
observer.assertCallbackRecords([
504
  { object: thingy, type: 'updated', name: 'a', oldValue: 2 },
505
  { object: thingy, type: 'updated', name: 'b', oldValue: 4 },
506
  { object: thingy, type: 'updated', name: 'b', oldValue: 7 },
507
  { object: thingy, type: 'updated', name: 'a', oldValue: 5 },
508
  { object: thingy, type: 'updated', name: 'b', oldValue: 8 },
509
  { object: thingy, type: 'updated', name: 'a', oldValue: 10 },
510
  { object: thingy, type: 'updated', name: 'a', oldValue: 11 },
511
  { object: thingy, type: 'updated', name: 'b', oldValue: 16 },
512
  { object: thingy, type: 'updated', name: 'a', oldValue: 13 },
513
  { object: thingy, type: 'updated', name: 'b', oldValue: 18 },
514
]);
515
observer2.assertCallbackRecords([
516
  { object: thingy, type: Thingy.INCREMENT, incremented: 3 },
517
  { object: thingy, type: 'updated', name: 'b', oldValue: 7 },
518
  { object: thingy, type: Thingy.MULTIPLY, multiplied: 2 },
519
  { object: thingy, type: 'updated', name: 'a', oldValue: 10 },
520
  {
521
    object: thingy,
522
    type: Thingy.INCREMENT_AND_MULTIPLY,
523
    incremented: 2,
524
    multiplied: 2
525
  }
526
]);
527

    
528

    
529
reset();
530
function RecursiveThingy() {}
531

    
532
RecursiveThingy.MULTIPLY_FIRST_N = 'multiplyFirstN';
533

    
534
RecursiveThingy.prototype = {
535
  __proto__: Array.prototype,
536

    
537
  multiplyFirstN: function(amount, n) {
538
    if (!n)
539
      return;
540
    var notifier = Object.getNotifier(this);
541
    var self = this;
542
    notifier.performChange(RecursiveThingy.MULTIPLY_FIRST_N, function() {
543
      self[n-1] = self[n-1]*amount;
544
      self.multiplyFirstN(amount, n-1);
545
    });
546

    
547
    notifier.notify({
548
      object: this,
549
      type: RecursiveThingy.MULTIPLY_FIRST_N,
550
      multiplied: amount,
551
      n: n
552
    });
553
  },
554
}
555

    
556
RecursiveThingy.observe = function(thingy, callback) {
557
  Object.observe(thingy, callback, [RecursiveThingy.MULTIPLY_FIRST_N]);
558
}
559

    
560
RecursiveThingy.unobserve = function(thingy, callback) {
561
  Object.unobserve(thingy);
562
}
563

    
564
var thingy = new RecursiveThingy;
565
thingy.push(1, 2, 3, 4);
566

    
567
Object.observe(thingy, observer.callback);
568
RecursiveThingy.observe(thingy, observer2.callback);
569
thingy.multiplyFirstN(2, 3);                // [2, 4, 6, 4]
570

    
571
Object.deliverChangeRecords(observer.callback);
572
Object.deliverChangeRecords(observer2.callback);
573
observer.assertCallbackRecords([
574
  { object: thingy, type: 'updated', name: '2', oldValue: 3 },
575
  { object: thingy, type: 'updated', name: '1', oldValue: 2 },
576
  { object: thingy, type: 'updated', name: '0', oldValue: 1 }
577
]);
578
observer2.assertCallbackRecords([
579
  { object: thingy, type: RecursiveThingy.MULTIPLY_FIRST_N, multiplied: 2, n: 3 }
580
]);
581

    
582
reset();
583
function DeckSuit() {
584
  this.push('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'A', 'Q', 'K');
585
}
586

    
587
DeckSuit.SHUFFLE = 'shuffle';
588

    
589
DeckSuit.prototype = {
590
  __proto__: Array.prototype,
591

    
592
  shuffle: function() {
593
    var notifier = Object.getNotifier(this);
594
    var self = this;
595
    notifier.performChange(DeckSuit.SHUFFLE, function() {
596
      self.reverse();
597
      self.sort(function() { return Math.random()* 2 - 1; });
598
      var cut = self.splice(0, 6);
599
      Array.prototype.push.apply(self, cut);
600
      self.reverse();
601
      self.sort(function() { return Math.random()* 2 - 1; });
602
      var cut = self.splice(0, 6);
603
      Array.prototype.push.apply(self, cut);
604
      self.reverse();
605
      self.sort(function() { return Math.random()* 2 - 1; });
606
    });
607

    
608
    notifier.notify({
609
      object: this,
610
      type: DeckSuit.SHUFFLE
611
    });
612
  },
613
}
614

    
615
DeckSuit.observe = function(thingy, callback) {
616
  Object.observe(thingy, callback, [DeckSuit.SHUFFLE]);
617
}
618

    
619
DeckSuit.unobserve = function(thingy, callback) {
620
  Object.unobserve(thingy);
621
}
622

    
623
var deck = new DeckSuit;
624

    
625
DeckSuit.observe(deck, observer2.callback);
626
deck.shuffle();
627

    
628
Object.deliverChangeRecords(observer2.callback);
629
observer2.assertCallbackRecords([
630
  { object: deck, type: DeckSuit.SHUFFLE }
631
]);
632

    
633
// Observing multiple objects; records appear in order.
634
reset();
635
var obj2 = {};
636
var obj3 = {}
637
Object.observe(obj, observer.callback);
638
Object.observe(obj3, observer.callback);
639
Object.observe(obj2, observer.callback);
640
Object.getNotifier(obj).notify({
641
  type: 'new',
642
});
643
Object.getNotifier(obj2).notify({
644
  type: 'updated',
645
});
646
Object.getNotifier(obj3).notify({
647
  type: 'deleted',
648
});
649
Object.observe(obj3, observer.callback);
650
Object.deliverChangeRecords(observer.callback);
651
observer.assertCallbackRecords([
652
  { object: obj, type: 'new' },
653
  { object: obj2, type: 'updated' },
654
  { object: obj3, type: 'deleted' }
655
]);
656

    
657

    
658
// Recursive observation.
659
var obj = {a: 1};
660
var callbackCount = 0;
661
function recursiveObserver(r) {
662
  assertEquals(1, r.length);
663
  ++callbackCount;
664
  if (r[0].oldValue < 100) ++obj[r[0].name];
665
}
666
Object.observe(obj, recursiveObserver);
667
++obj.a;
668
Object.deliverChangeRecords(recursiveObserver);
669
assertEquals(100, callbackCount);
670

    
671
var obj1 = {a: 1};
672
var obj2 = {a: 1};
673
var recordCount = 0;
674
function recursiveObserver2(r) {
675
  recordCount += r.length;
676
  if (r[0].oldValue < 100) {
677
    ++obj1.a;
678
    ++obj2.a;
679
  }
680
}
681
Object.observe(obj1, recursiveObserver2);
682
Object.observe(obj2, recursiveObserver2);
683
++obj1.a;
684
Object.deliverChangeRecords(recursiveObserver2);
685
assertEquals(199, recordCount);
686

    
687

    
688
// Observing named properties.
689
reset();
690
var obj = {a: 1}
691
Object.observe(obj, observer.callback);
692
obj.a = 2;
693
obj["a"] = 3;
694
delete obj.a;
695
obj.a = 4;
696
obj.a = 4;  // ignored
697
obj.a = 5;
698
Object.defineProperty(obj, "a", {value: 6});
699
Object.defineProperty(obj, "a", {writable: false});
700
obj.a = 7;  // ignored
701
Object.defineProperty(obj, "a", {value: 8});
702
Object.defineProperty(obj, "a", {value: 7, writable: true});
703
Object.defineProperty(obj, "a", {get: function() {}});
704
Object.defineProperty(obj, "a", {get: frozenFunction});
705
Object.defineProperty(obj, "a", {get: frozenFunction});  // ignored
706
Object.defineProperty(obj, "a", {get: frozenFunction, set: frozenFunction});
707
Object.defineProperty(obj, "a", {set: frozenFunction});  // ignored
708
Object.defineProperty(obj, "a", {get: undefined, set: frozenFunction});
709
delete obj.a;
710
delete obj.a;
711
Object.defineProperty(obj, "a", {get: function() {}, configurable: true});
712
Object.defineProperty(obj, "a", {value: 9, writable: true});
713
obj.a = 10;
714
++obj.a;
715
obj.a++;
716
obj.a *= 3;
717
delete obj.a;
718
Object.defineProperty(obj, "a", {value: 11, configurable: true});
719
Object.deliverChangeRecords(observer.callback);
720
observer.assertCallbackRecords([
721
  { object: obj, name: "a", type: "updated", oldValue: 1 },
722
  { object: obj, name: "a", type: "updated", oldValue: 2 },
723
  { object: obj, name: "a", type: "deleted", oldValue: 3 },
724
  { object: obj, name: "a", type: "new" },
725
  { object: obj, name: "a", type: "updated", oldValue: 4 },
726
  { object: obj, name: "a", type: "updated", oldValue: 5 },
727
  { object: obj, name: "a", type: "reconfigured" },
728
  { object: obj, name: "a", type: "updated", oldValue: 6 },
729
  { object: obj, name: "a", type: "reconfigured", oldValue: 8 },
730
  { object: obj, name: "a", type: "reconfigured", oldValue: 7 },
731
  { object: obj, name: "a", type: "reconfigured" },
732
  { object: obj, name: "a", type: "reconfigured" },
733
  { object: obj, name: "a", type: "reconfigured" },
734
  { object: obj, name: "a", type: "deleted" },
735
  { object: obj, name: "a", type: "new" },
736
  { object: obj, name: "a", type: "reconfigured" },
737
  { object: obj, name: "a", type: "updated", oldValue: 9 },
738
  { object: obj, name: "a", type: "updated", oldValue: 10 },
739
  { object: obj, name: "a", type: "updated", oldValue: 11 },
740
  { object: obj, name: "a", type: "updated", oldValue: 12 },
741
  { object: obj, name: "a", type: "deleted", oldValue: 36 },
742
  { object: obj, name: "a", type: "new" },
743
]);
744

    
745

    
746
// Observing indexed properties.
747
reset();
748
var obj = {'1': 1}
749
Object.observe(obj, observer.callback);
750
obj[1] = 2;
751
obj[1] = 3;
752
delete obj[1];
753
obj[1] = 4;
754
obj[1] = 4;  // ignored
755
obj[1] = 5;
756
Object.defineProperty(obj, "1", {value: 6});
757
Object.defineProperty(obj, "1", {writable: false});
758
obj[1] = 7;  // ignored
759
Object.defineProperty(obj, "1", {value: 8});
760
Object.defineProperty(obj, "1", {value: 7, writable: true});
761
Object.defineProperty(obj, "1", {get: function() {}});
762
Object.defineProperty(obj, "1", {get: frozenFunction});
763
Object.defineProperty(obj, "1", {get: frozenFunction});  // ignored
764
Object.defineProperty(obj, "1", {get: frozenFunction, set: frozenFunction});
765
Object.defineProperty(obj, "1", {set: frozenFunction});  // ignored
766
Object.defineProperty(obj, "1", {get: undefined, set: frozenFunction});
767
delete obj[1];
768
delete obj[1];
769
Object.defineProperty(obj, "1", {get: function() {}, configurable: true});
770
Object.defineProperty(obj, "1", {value: 9, writable: true});
771
obj[1] = 10;
772
++obj[1];
773
obj[1]++;
774
obj[1] *= 3;
775
delete obj[1];
776
Object.defineProperty(obj, "1", {value: 11, configurable: true});
777
Object.deliverChangeRecords(observer.callback);
778
observer.assertCallbackRecords([
779
  { object: obj, name: "1", type: "updated", oldValue: 1 },
780
  { object: obj, name: "1", type: "updated", oldValue: 2 },
781
  { object: obj, name: "1", type: "deleted", oldValue: 3 },
782
  { object: obj, name: "1", type: "new" },
783
  { object: obj, name: "1", type: "updated", oldValue: 4 },
784
  { object: obj, name: "1", type: "updated", oldValue: 5 },
785
  { object: obj, name: "1", type: "reconfigured" },
786
  { object: obj, name: "1", type: "updated", oldValue: 6 },
787
  { object: obj, name: "1", type: "reconfigured", oldValue: 8 },
788
  { object: obj, name: "1", type: "reconfigured", oldValue: 7 },
789
  { object: obj, name: "1", type: "reconfigured" },
790
  { object: obj, name: "1", type: "reconfigured" },
791
  { object: obj, name: "1", type: "reconfigured" },
792
  { object: obj, name: "1", type: "deleted" },
793
  { object: obj, name: "1", type: "new" },
794
  { object: obj, name: "1", type: "reconfigured" },
795
  { object: obj, name: "1", type: "updated", oldValue: 9 },
796
  { object: obj, name: "1", type: "updated", oldValue: 10 },
797
  { object: obj, name: "1", type: "updated", oldValue: 11 },
798
  { object: obj, name: "1", type: "updated", oldValue: 12 },
799
  { object: obj, name: "1", type: "deleted", oldValue: 36 },
800
  { object: obj, name: "1", type: "new" },
801
]);
802

    
803

    
804
// Observing symbol properties (not).
805
print("*****")
806
reset();
807
var obj = {}
808
var symbol = Symbol("secret");
809
Object.observe(obj, observer.callback);
810
obj[symbol] = 3;
811
delete obj[symbol];
812
Object.defineProperty(obj, symbol, {get: function() {}, configurable: true});
813
Object.defineProperty(obj, symbol, {value: 6});
814
Object.defineProperty(obj, symbol, {writable: false});
815
delete obj[symbol];
816
Object.defineProperty(obj, symbol, {value: 7});
817
++obj[symbol];
818
obj[symbol]++;
819
obj[symbol] *= 3;
820
delete obj[symbol];
821
obj.__defineSetter__(symbol, function() {});
822
obj.__defineGetter__(symbol, function() {});
823
Object.deliverChangeRecords(observer.callback);
824
observer.assertNotCalled();
825

    
826

    
827
// Test all kinds of objects generically.
828
function TestObserveConfigurable(obj, prop) {
829
  reset();
830
  Object.observe(obj, observer.callback);
831
  Object.unobserve(obj, observer.callback);
832
  obj[prop] = 1;
833
  Object.observe(obj, observer.callback);
834
  obj[prop] = 2;
835
  obj[prop] = 3;
836
  delete obj[prop];
837
  obj[prop] = 4;
838
  obj[prop] = 4;  // ignored
839
  obj[prop] = 5;
840
  Object.defineProperty(obj, prop, {value: 6});
841
  Object.defineProperty(obj, prop, {writable: false});
842
  obj[prop] = 7;  // ignored
843
  Object.defineProperty(obj, prop, {value: 8});
844
  Object.defineProperty(obj, prop, {value: 7, writable: true});
845
  Object.defineProperty(obj, prop, {get: function() {}});
846
  Object.defineProperty(obj, prop, {get: frozenFunction});
847
  Object.defineProperty(obj, prop, {get: frozenFunction});  // ignored
848
  Object.defineProperty(obj, prop, {get: frozenFunction, set: frozenFunction});
849
  Object.defineProperty(obj, prop, {set: frozenFunction});  // ignored
850
  Object.defineProperty(obj, prop, {get: undefined, set: frozenFunction});
851
  obj.__defineSetter__(prop, frozenFunction);  // ignored
852
  obj.__defineSetter__(prop, function() {});
853
  obj.__defineGetter__(prop, function() {});
854
  delete obj[prop];
855
  delete obj[prop];  // ignored
856
  obj.__defineGetter__(prop, function() {});
857
  delete obj[prop];
858
  Object.defineProperty(obj, prop, {get: function() {}, configurable: true});
859
  Object.defineProperty(obj, prop, {value: 9, writable: true});
860
  obj[prop] = 10;
861
  ++obj[prop];
862
  obj[prop]++;
863
  obj[prop] *= 3;
864
  delete obj[prop];
865
  Object.defineProperty(obj, prop, {value: 11, configurable: true});
866
  Object.deliverChangeRecords(observer.callback);
867
  observer.assertCallbackRecords([
868
    { object: obj, name: prop, type: "updated", oldValue: 1 },
869
    { object: obj, name: prop, type: "updated", oldValue: 2 },
870
    { object: obj, name: prop, type: "deleted", oldValue: 3 },
871
    { object: obj, name: prop, type: "new" },
872
    { object: obj, name: prop, type: "updated", oldValue: 4 },
873
    { object: obj, name: prop, type: "updated", oldValue: 5 },
874
    { object: obj, name: prop, type: "reconfigured" },
875
    { object: obj, name: prop, type: "updated", oldValue: 6 },
876
    { object: obj, name: prop, type: "reconfigured", oldValue: 8 },
877
    { object: obj, name: prop, type: "reconfigured", oldValue: 7 },
878
    { object: obj, name: prop, type: "reconfigured" },
879
    { object: obj, name: prop, type: "reconfigured" },
880
    { object: obj, name: prop, type: "reconfigured" },
881
    { object: obj, name: prop, type: "reconfigured" },
882
    { object: obj, name: prop, type: "reconfigured" },
883
    { object: obj, name: prop, type: "deleted" },
884
    { object: obj, name: prop, type: "new" },
885
    { object: obj, name: prop, type: "deleted" },
886
    { object: obj, name: prop, type: "new" },
887
    { object: obj, name: prop, type: "reconfigured" },
888
    { object: obj, name: prop, type: "updated", oldValue: 9 },
889
    { object: obj, name: prop, type: "updated", oldValue: 10 },
890
    { object: obj, name: prop, type: "updated", oldValue: 11 },
891
    { object: obj, name: prop, type: "updated", oldValue: 12 },
892
    { object: obj, name: prop, type: "deleted", oldValue: 36 },
893
    { object: obj, name: prop, type: "new" },
894
  ]);
895
  Object.unobserve(obj, observer.callback);
896
  delete obj[prop];
897
}
898

    
899
function TestObserveNonConfigurable(obj, prop, desc) {
900
  reset();
901
  Object.observe(obj, observer.callback);
902
  Object.unobserve(obj, observer.callback);
903
  obj[prop] = 1;
904
  Object.observe(obj, observer.callback);
905
  obj[prop] = 4;
906
  obj[prop] = 4;  // ignored
907
  obj[prop] = 5;
908
  Object.defineProperty(obj, prop, {value: 6});
909
  Object.defineProperty(obj, prop, {value: 6});  // ignored
910
  Object.defineProperty(obj, prop, {value: 7});
911
  Object.defineProperty(obj, prop, {enumerable: desc.enumerable});  // ignored
912
  Object.defineProperty(obj, prop, {writable: false});
913
  obj[prop] = 7;  // ignored
914
  Object.deliverChangeRecords(observer.callback);
915
  observer.assertCallbackRecords([
916
    { object: obj, name: prop, type: "updated", oldValue: 1 },
917
    { object: obj, name: prop, type: "updated", oldValue: 4 },
918
    { object: obj, name: prop, type: "updated", oldValue: 5 },
919
    { object: obj, name: prop, type: "updated", oldValue: 6 },
920
    { object: obj, name: prop, type: "reconfigured" },
921
  ]);
922
  Object.unobserve(obj, observer.callback);
923
}
924

    
925
function createProxy(create, x) {
926
  var handler = {
927
    getPropertyDescriptor: function(k) {
928
      for (var o = this.target; o; o = Object.getPrototypeOf(o)) {
929
        var desc = Object.getOwnPropertyDescriptor(o, k);
930
        if (desc) return desc;
931
      }
932
      return undefined;
933
    },
934
    getOwnPropertyDescriptor: function(k) {
935
      return Object.getOwnPropertyDescriptor(this.target, k);
936
    },
937
    defineProperty: function(k, desc) {
938
      var x = Object.defineProperty(this.target, k, desc);
939
      Object.deliverChangeRecords(this.callback);
940
      return x;
941
    },
942
    delete: function(k) {
943
      var x = delete this.target[k];
944
      Object.deliverChangeRecords(this.callback);
945
      return x;
946
    },
947
    getPropertyNames: function() {
948
      return Object.getOwnPropertyNames(this.target);
949
    },
950
    target: {isProxy: true},
951
    callback: function(changeRecords) {
952
      print("callback", stringifyNoThrow(handler.proxy), stringifyNoThrow(got));
953
      for (var i in changeRecords) {
954
        var got = changeRecords[i];
955
        var change = {object: handler.proxy, name: got.name, type: got.type};
956
        if ("oldValue" in got) change.oldValue = got.oldValue;
957
        Object.getNotifier(handler.proxy).notify(change);
958
      }
959
    },
960
  };
961
  Object.observe(handler.target, handler.callback);
962
  return handler.proxy = create(handler, x);
963
}
964

    
965
var objects = [
966
  {},
967
  [],
968
  this,  // global object
969
  function(){},
970
  (function(){ return arguments })(),
971
  (function(){ "use strict"; return arguments })(),
972
  Object(1), Object(true), Object("bla"),
973
  new Date(),
974
  Object, Function, Date, RegExp,
975
  new Set, new Map, new WeakMap,
976
  new ArrayBuffer(10), new Int32Array(5),
977
  createProxy(Proxy.create, null),
978
  createProxy(Proxy.createFunction, function(){}),
979
];
980
var properties = ["a", "1", 1, "length", "prototype", "name", "caller"];
981

    
982
// Cases that yield non-standard results.
983
function blacklisted(obj, prop) {
984
  return (obj instanceof Int32Array && prop == 1) ||
985
         (obj instanceof Int32Array && prop === "length") ||
986
         (obj instanceof ArrayBuffer && prop == 1)
987
}
988

    
989
for (var i in objects) for (var j in properties) {
990
  var obj = objects[i];
991
  var prop = properties[j];
992
  if (blacklisted(obj, prop)) continue;
993
  var desc = Object.getOwnPropertyDescriptor(obj, prop);
994
  print("***", typeof obj, stringifyNoThrow(obj), prop);
995
  if (!desc || desc.configurable)
996
    TestObserveConfigurable(obj, prop);
997
  else if (desc.writable)
998
    TestObserveNonConfigurable(obj, prop, desc);
999
}
1000

    
1001

    
1002
// Observing array length (including truncation)
1003
reset();
1004
var arr = ['a', 'b', 'c', 'd'];
1005
var arr2 = ['alpha', 'beta'];
1006
var arr3 = ['hello'];
1007
arr3[2] = 'goodbye';
1008
arr3.length = 6;
1009
Object.defineProperty(arr, '0', {configurable: false});
1010
Object.defineProperty(arr, '2', {get: function(){}});
1011
Object.defineProperty(arr2, '0', {get: function(){}, configurable: false});
1012
Object.observe(arr, observer.callback);
1013
Array.observe(arr, observer2.callback);
1014
Object.observe(arr2, observer.callback);
1015
Array.observe(arr2, observer2.callback);
1016
Object.observe(arr3, observer.callback);
1017
Array.observe(arr3, observer2.callback);
1018
arr.length = 2;
1019
arr.length = 0;
1020
arr.length = 10;
1021
Object.defineProperty(arr, 'length', {writable: false});
1022
arr2.length = 0;
1023
arr2.length = 1; // no change expected
1024
Object.defineProperty(arr2, 'length', {value: 1, writable: false});
1025
arr3.length = 0;
1026
++arr3.length;
1027
arr3.length++;
1028
arr3.length /= 2;
1029
Object.defineProperty(arr3, 'length', {value: 5});
1030
arr3[4] = 5;
1031
Object.defineProperty(arr3, 'length', {value: 1, writable: false});
1032
Object.deliverChangeRecords(observer.callback);
1033
observer.assertCallbackRecords([
1034
  { object: arr, name: '3', type: 'deleted', oldValue: 'd' },
1035
  { object: arr, name: '2', type: 'deleted' },
1036
  { object: arr, name: 'length', type: 'updated', oldValue: 4 },
1037
  { object: arr, name: '1', type: 'deleted', oldValue: 'b' },
1038
  { object: arr, name: 'length', type: 'updated', oldValue: 2 },
1039
  { object: arr, name: 'length', type: 'updated', oldValue: 1 },
1040
  { object: arr, name: 'length', type: 'reconfigured' },
1041
  { object: arr2, name: '1', type: 'deleted', oldValue: 'beta' },
1042
  { object: arr2, name: 'length', type: 'updated', oldValue: 2 },
1043
  { object: arr2, name: 'length', type: 'reconfigured' },
1044
  { object: arr3, name: '2', type: 'deleted', oldValue: 'goodbye' },
1045
  { object: arr3, name: '0', type: 'deleted', oldValue: 'hello' },
1046
  { object: arr3, name: 'length', type: 'updated', oldValue: 6 },
1047
  { object: arr3, name: 'length', type: 'updated', oldValue: 0 },
1048
  { object: arr3, name: 'length', type: 'updated', oldValue: 1 },
1049
  { object: arr3, name: 'length', type: 'updated', oldValue: 2 },
1050
  { object: arr3, name: 'length', type: 'updated', oldValue: 1 },
1051
  { object: arr3, name: '4', type: 'new' },
1052
  { object: arr3, name: '4', type: 'deleted', oldValue: 5 },
1053
  // TODO(rafaelw): It breaks spec compliance to get two records here.
1054
  // When the TODO in v8natives.js::DefineArrayProperty is addressed
1055
  // which prevents DefineProperty from over-writing the magic length
1056
  // property, these will collapse into a single record.
1057
  { object: arr3, name: 'length', type: 'updated', oldValue: 5 },
1058
  { object: arr3, name: 'length', type: 'reconfigured' }
1059
]);
1060
Object.deliverChangeRecords(observer2.callback);
1061
observer2.assertCallbackRecords([
1062
  { object: arr, type: 'splice', index: 2, removed: [, 'd'], addedCount: 0 },
1063
  { object: arr, type: 'splice', index: 1, removed: ['b'], addedCount: 0 },
1064
  { object: arr, type: 'splice', index: 1, removed: [], addedCount: 9 },
1065
  { object: arr2, type: 'splice', index: 1, removed: ['beta'], addedCount: 0 },
1066
  { object: arr3, type: 'splice', index: 0, removed: ['hello',, 'goodbye',,,,], addedCount: 0 },
1067
  { object: arr3, type: 'splice', index: 0, removed: [], addedCount: 1 },
1068
  { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 1 },
1069
  { object: arr3, type: 'splice', index: 1, removed: [,], addedCount: 0 },
1070
  { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 4 },
1071
  { object: arr3, name: '4', type: 'new' },
1072
  { object: arr3, type: 'splice', index: 1, removed: [,,,5], addedCount: 0 }
1073
]);
1074

    
1075

    
1076
// Updating length on large (slow) array
1077
reset();
1078
var slow_arr = new Array(1000000000);
1079
slow_arr[500000000] = 'hello';
1080
Object.observe(slow_arr, observer.callback);
1081
var spliceRecords;
1082
function slowSpliceCallback(records) {
1083
  spliceRecords = records;
1084
}
1085
Array.observe(slow_arr, slowSpliceCallback);
1086
slow_arr.length = 100;
1087
Object.deliverChangeRecords(observer.callback);
1088
observer.assertCallbackRecords([
1089
  { object: slow_arr, name: '500000000', type: 'deleted', oldValue: 'hello' },
1090
  { object: slow_arr, name: 'length', type: 'updated', oldValue: 1000000000 },
1091
]);
1092
Object.deliverChangeRecords(slowSpliceCallback);
1093
assertEquals(spliceRecords.length, 1);
1094
// Have to custom assert this splice record because the removed array is huge.
1095
var splice = spliceRecords[0];
1096
assertSame(splice.object, slow_arr);
1097
assertEquals(splice.type, 'splice');
1098
assertEquals(splice.index, 100);
1099
assertEquals(splice.addedCount, 0);
1100
var array_keys = %GetArrayKeys(splice.removed, splice.removed.length);
1101
assertEquals(array_keys.length, 1);
1102
assertEquals(array_keys[0], 499999900);
1103
assertEquals(splice.removed[499999900], 'hello');
1104
assertEquals(splice.removed.length, 999999900);
1105

    
1106

    
1107
// Assignments in loops (checking different IC states).
1108
reset();
1109
var obj = {};
1110
Object.observe(obj, observer.callback);
1111
for (var i = 0; i < 5; i++) {
1112
  obj["a" + i] = i;
1113
}
1114
Object.deliverChangeRecords(observer.callback);
1115
observer.assertCallbackRecords([
1116
  { object: obj, name: "a0", type: "new" },
1117
  { object: obj, name: "a1", type: "new" },
1118
  { object: obj, name: "a2", type: "new" },
1119
  { object: obj, name: "a3", type: "new" },
1120
  { object: obj, name: "a4", type: "new" },
1121
]);
1122

    
1123
reset();
1124
var obj = {};
1125
Object.observe(obj, observer.callback);
1126
for (var i = 0; i < 5; i++) {
1127
  obj[i] = i;
1128
}
1129
Object.deliverChangeRecords(observer.callback);
1130
observer.assertCallbackRecords([
1131
  { object: obj, name: "0", type: "new" },
1132
  { object: obj, name: "1", type: "new" },
1133
  { object: obj, name: "2", type: "new" },
1134
  { object: obj, name: "3", type: "new" },
1135
  { object: obj, name: "4", type: "new" },
1136
]);
1137

    
1138

    
1139
// Adding elements past the end of an array should notify on length for
1140
// Object.observe and emit "splices" for Array.observe.
1141
reset();
1142
var arr = [1, 2, 3];
1143
Object.observe(arr, observer.callback);
1144
Array.observe(arr, observer2.callback);
1145
arr[3] = 10;
1146
arr[100] = 20;
1147
Object.defineProperty(arr, '200', {value: 7});
1148
Object.defineProperty(arr, '400', {get: function(){}});
1149
arr[50] = 30; // no length change expected
1150
Object.deliverChangeRecords(observer.callback);
1151
observer.assertCallbackRecords([
1152
  { object: arr, name: '3', type: 'new' },
1153
  { object: arr, name: 'length', type: 'updated', oldValue: 3 },
1154
  { object: arr, name: '100', type: 'new' },
1155
  { object: arr, name: 'length', type: 'updated', oldValue: 4 },
1156
  { object: arr, name: '200', type: 'new' },
1157
  { object: arr, name: 'length', type: 'updated', oldValue: 101 },
1158
  { object: arr, name: '400', type: 'new' },
1159
  { object: arr, name: 'length', type: 'updated', oldValue: 201 },
1160
  { object: arr, name: '50', type: 'new' },
1161
]);
1162
Object.deliverChangeRecords(observer2.callback);
1163
observer2.assertCallbackRecords([
1164
  { object: arr, type: 'splice', index: 3, removed: [], addedCount: 1 },
1165
  { object: arr, type: 'splice', index: 4, removed: [], addedCount: 97 },
1166
  { object: arr, type: 'splice', index: 101, removed: [], addedCount: 100 },
1167
  { object: arr, type: 'splice', index: 201, removed: [], addedCount: 200 },
1168
  { object: arr, type: 'new', name: '50' },
1169
]);
1170

    
1171

    
1172
// Tests for array methods, first on arrays and then on plain objects
1173
//
1174
// === ARRAYS ===
1175
//
1176
// Push
1177
reset();
1178
var array = [1, 2];
1179
Object.observe(array, observer.callback);
1180
Array.observe(array, observer2.callback);
1181
array.push(3, 4);
1182
array.push(5);
1183
Object.deliverChangeRecords(observer.callback);
1184
observer.assertCallbackRecords([
1185
  { object: array, name: '2', type: 'new' },
1186
  { object: array, name: 'length', type: 'updated', oldValue: 2 },
1187
  { object: array, name: '3', type: 'new' },
1188
  { object: array, name: 'length', type: 'updated', oldValue: 3 },
1189
  { object: array, name: '4', type: 'new' },
1190
  { object: array, name: 'length', type: 'updated', oldValue: 4 },
1191
]);
1192
Object.deliverChangeRecords(observer2.callback);
1193
observer2.assertCallbackRecords([
1194
  { object: array, type: 'splice', index: 2, removed: [], addedCount: 2 },
1195
  { object: array, type: 'splice', index: 4, removed: [], addedCount: 1 }
1196
]);
1197

    
1198
// Pop
1199
reset();
1200
var array = [1, 2];
1201
Object.observe(array, observer.callback);
1202
array.pop();
1203
array.pop();
1204
Object.deliverChangeRecords(observer.callback);
1205
observer.assertCallbackRecords([
1206
  { object: array, name: '1', type: 'deleted', oldValue: 2 },
1207
  { object: array, name: 'length', type: 'updated', oldValue: 2 },
1208
  { object: array, name: '0', type: 'deleted', oldValue: 1 },
1209
  { object: array, name: 'length', type: 'updated', oldValue: 1 },
1210
]);
1211

    
1212
// Shift
1213
reset();
1214
var array = [1, 2];
1215
Object.observe(array, observer.callback);
1216
array.shift();
1217
array.shift();
1218
Object.deliverChangeRecords(observer.callback);
1219
observer.assertCallbackRecords([
1220
  { object: array, name: '0', type: 'updated', oldValue: 1 },
1221
  { object: array, name: '1', type: 'deleted', oldValue: 2 },
1222
  { object: array, name: 'length', type: 'updated', oldValue: 2 },
1223
  { object: array, name: '0', type: 'deleted', oldValue: 2 },
1224
  { object: array, name: 'length', type: 'updated', oldValue: 1 },
1225
]);
1226

    
1227
// Unshift
1228
reset();
1229
var array = [1, 2];
1230
Object.observe(array, observer.callback);
1231
array.unshift(3, 4);
1232
Object.deliverChangeRecords(observer.callback);
1233
observer.assertCallbackRecords([
1234
  { object: array, name: '3', type: 'new' },
1235
  { object: array, name: 'length', type: 'updated', oldValue: 2 },
1236
  { object: array, name: '2', type: 'new' },
1237
  { object: array, name: '0', type: 'updated', oldValue: 1 },
1238
  { object: array, name: '1', type: 'updated', oldValue: 2 },
1239
]);
1240

    
1241
// Splice
1242
reset();
1243
var array = [1, 2, 3];
1244
Object.observe(array, observer.callback);
1245
array.splice(1, 1, 4, 5);
1246
Object.deliverChangeRecords(observer.callback);
1247
observer.assertCallbackRecords([
1248
  { object: array, name: '3', type: 'new' },
1249
  { object: array, name: 'length', type: 'updated', oldValue: 3 },
1250
  { object: array, name: '1', type: 'updated', oldValue: 2 },
1251
  { object: array, name: '2', type: 'updated', oldValue: 3 },
1252
]);
1253

    
1254
// Sort
1255
reset();
1256
var array = [3, 2, 1];
1257
Object.observe(array, observer.callback);
1258
array.sort();
1259
assertEquals(1, array[0]);
1260
assertEquals(2, array[1]);
1261
assertEquals(3, array[2]);
1262
Object.deliverChangeRecords(observer.callback);
1263
observer.assertCallbackRecords([
1264
  { object: array, name: '1', type: 'updated', oldValue: 2 },
1265
  { object: array, name: '0', type: 'updated', oldValue: 3 },
1266
  { object: array, name: '2', type: 'updated', oldValue: 1 },
1267
  { object: array, name: '1', type: 'updated', oldValue: 3 },
1268
  { object: array, name: '0', type: 'updated', oldValue: 2 },
1269
]);
1270

    
1271
// Splice emitted after Array mutation methods
1272
function MockArray(initial, observer) {
1273
  for (var i = 0; i < initial.length; i++)
1274
    this[i] = initial[i];
1275

    
1276
  this.length_ = initial.length;
1277
  this.observer = observer;
1278
}
1279
MockArray.prototype = {
1280
  set length(length) {
1281
    Object.getNotifier(this).notify({ type: 'lengthChange' });
1282
    this.length_ = length;
1283
    Object.observe(this, this.observer.callback, ['splice']);
1284
  },
1285
  get length() {
1286
    return this.length_;
1287
  }
1288
}
1289

    
1290
reset();
1291
var array = new MockArray([], observer);
1292
Object.observe(array, observer.callback, ['lengthChange']);
1293
Array.prototype.push.call(array, 1);
1294
Object.deliverChangeRecords(observer.callback);
1295
observer.assertCallbackRecords([
1296
  { object: array, type: 'lengthChange' },
1297
  { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 },
1298
]);
1299

    
1300
reset();
1301
var array = new MockArray([1], observer);
1302
Object.observe(array, observer.callback, ['lengthChange']);
1303
Array.prototype.pop.call(array);
1304
Object.deliverChangeRecords(observer.callback);
1305
observer.assertCallbackRecords([
1306
  { object: array, type: 'lengthChange' },
1307
  { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
1308
]);
1309

    
1310
reset();
1311
var array = new MockArray([1], observer);
1312
Object.observe(array, observer.callback, ['lengthChange']);
1313
Array.prototype.shift.call(array);
1314
Object.deliverChangeRecords(observer.callback);
1315
observer.assertCallbackRecords([
1316
  { object: array, type: 'lengthChange' },
1317
  { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
1318
]);
1319

    
1320
reset();
1321
var array = new MockArray([], observer);
1322
Object.observe(array, observer.callback, ['lengthChange']);
1323
Array.prototype.unshift.call(array, 1);
1324
Object.deliverChangeRecords(observer.callback);
1325
observer.assertCallbackRecords([
1326
  { object: array, type: 'lengthChange' },
1327
  { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 },
1328
]);
1329

    
1330
reset();
1331
var array = new MockArray([0, 1, 2], observer);
1332
Object.observe(array, observer.callback, ['lengthChange']);
1333
Array.prototype.splice.call(array, 1, 1);
1334
Object.deliverChangeRecords(observer.callback);
1335
observer.assertCallbackRecords([
1336
  { object: array, type: 'lengthChange' },
1337
  { object: array, type: 'splice', index: 1, removed: [1], addedCount: 0 },
1338
]);
1339

    
1340
//
1341
// === PLAIN OBJECTS ===
1342
//
1343
// Push
1344
reset()
1345
var array = {0: 1, 1: 2, length: 2}
1346
Object.observe(array, observer.callback);
1347
Array.prototype.push.call(array, 3, 4);
1348
Object.deliverChangeRecords(observer.callback);
1349
observer.assertCallbackRecords([
1350
  { object: array, name: '2', type: 'new' },
1351
  { object: array, name: '3', type: 'new' },
1352
  { object: array, name: 'length', type: 'updated', oldValue: 2 },
1353
]);
1354

    
1355
// Pop
1356
reset();
1357
var array = [1, 2];
1358
Object.observe(array, observer.callback);
1359
Array.observe(array, observer2.callback);
1360
array.pop();
1361
array.pop();
1362
array.pop();
1363
Object.deliverChangeRecords(observer.callback);
1364
observer.assertCallbackRecords([
1365
  { object: array, name: '1', type: 'deleted', oldValue: 2 },
1366
  { object: array, name: 'length', type: 'updated', oldValue: 2 },
1367
  { object: array, name: '0', type: 'deleted', oldValue: 1 },
1368
  { object: array, name: 'length', type: 'updated', oldValue: 1 },
1369
]);
1370
Object.deliverChangeRecords(observer2.callback);
1371
observer2.assertCallbackRecords([
1372
  { object: array, type: 'splice', index: 1, removed: [2], addedCount: 0 },
1373
  { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }
1374
]);
1375

    
1376
// Shift
1377
reset();
1378
var array = [1, 2];
1379
Object.observe(array, observer.callback);
1380
Array.observe(array, observer2.callback);
1381
array.shift();
1382
array.shift();
1383
array.shift();
1384
Object.deliverChangeRecords(observer.callback);
1385
observer.assertCallbackRecords([
1386
  { object: array, name: '0', type: 'updated', oldValue: 1 },
1387
  { object: array, name: '1', type: 'deleted', oldValue: 2 },
1388
  { object: array, name: 'length', type: 'updated', oldValue: 2 },
1389
  { object: array, name: '0', type: 'deleted', oldValue: 2 },
1390
  { object: array, name: 'length', type: 'updated', oldValue: 1 },
1391
]);
1392
Object.deliverChangeRecords(observer2.callback);
1393
observer2.assertCallbackRecords([
1394
  { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
1395
  { object: array, type: 'splice', index: 0, removed: [2], addedCount: 0 }
1396
]);
1397

    
1398
// Unshift
1399
reset();
1400
var array = [1, 2];
1401
Object.observe(array, observer.callback);
1402
Array.observe(array, observer2.callback);
1403
array.unshift(3, 4);
1404
array.unshift(5);
1405
Object.deliverChangeRecords(observer.callback);
1406
observer.assertCallbackRecords([
1407
  { object: array, name: '3', type: 'new' },
1408
  { object: array, name: 'length', type: 'updated', oldValue: 2 },
1409
  { object: array, name: '2', type: 'new' },
1410
  { object: array, name: '0', type: 'updated', oldValue: 1 },
1411
  { object: array, name: '1', type: 'updated', oldValue: 2 },
1412
  { object: array, name: '4', type: 'new' },
1413
  { object: array, name: 'length', type: 'updated', oldValue: 4 },
1414
  { object: array, name: '3', type: 'updated', oldValue: 2 },
1415
  { object: array, name: '2', type: 'updated', oldValue: 1 },
1416
  { object: array, name: '1', type: 'updated', oldValue: 4 },
1417
  { object: array, name: '0', type: 'updated', oldValue: 3 },
1418
]);
1419
Object.deliverChangeRecords(observer2.callback);
1420
observer2.assertCallbackRecords([
1421
  { object: array, type: 'splice', index: 0, removed: [], addedCount: 2 },
1422
  { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }
1423
]);
1424

    
1425
// Splice
1426
reset();
1427
var array = [1, 2, 3];
1428
Object.observe(array, observer.callback);
1429
Array.observe(array, observer2.callback);
1430
array.splice(1, 0, 4, 5); // 1 4 5 2 3
1431
array.splice(0, 2); // 5 2 3
1432
array.splice(1, 2, 6, 7); // 5 6 7
1433
array.splice(2, 0);
1434
Object.deliverChangeRecords(observer.callback);
1435
observer.assertCallbackRecords([
1436
  { object: array, name: '4', type: 'new' },
1437
  { object: array, name: 'length', type: 'updated', oldValue: 3 },
1438
  { object: array, name: '3', type: 'new' },
1439
  { object: array, name: '1', type: 'updated', oldValue: 2 },
1440
  { object: array, name: '2', type: 'updated', oldValue: 3 },
1441

    
1442
  { object: array, name: '0', type: 'updated', oldValue: 1 },
1443
  { object: array, name: '1', type: 'updated', oldValue: 4 },
1444
  { object: array, name: '2', type: 'updated', oldValue: 5 },
1445
  { object: array, name: '4', type: 'deleted', oldValue: 3 },
1446
  { object: array, name: '3', type: 'deleted', oldValue: 2 },
1447
  { object: array, name: 'length', type: 'updated', oldValue: 5 },
1448

    
1449
  { object: array, name: '1', type: 'updated', oldValue: 2 },
1450
  { object: array, name: '2', type: 'updated', oldValue: 3 },
1451
]);
1452
Object.deliverChangeRecords(observer2.callback);
1453
observer2.assertCallbackRecords([
1454
  { object: array, type: 'splice', index: 1, removed: [], addedCount: 2 },
1455
  { object: array, type: 'splice', index: 0, removed: [1, 4], addedCount: 0 },
1456
  { object: array, type: 'splice', index: 1, removed: [2, 3], addedCount: 2 },
1457
]);
1458

    
1459
// Exercise StoreIC_ArrayLength
1460
reset();
1461
var dummy = {};
1462
Object.observe(dummy, observer.callback);
1463
Object.unobserve(dummy, observer.callback);
1464
var array = [0];
1465
Object.observe(array, observer.callback);
1466
array.splice(0, 1);
1467
Object.deliverChangeRecords(observer.callback);
1468
observer.assertCallbackRecords([
1469
  { object: array, name: '0', type: 'deleted', oldValue: 0 },
1470
  { object: array, name: 'length', type: 'updated', oldValue: 1},
1471
]);
1472

    
1473

    
1474
// __proto__
1475
reset();
1476
var obj = {};
1477
Object.observe(obj, observer.callback);
1478
var p = {foo: 'yes'};
1479
var q = {bar: 'no'};
1480
obj.__proto__ = p;
1481
obj.__proto__ = p;  // ignored
1482
obj.__proto__ = null;
1483
obj.__proto__ = q;  // the __proto__ accessor is gone
1484
// TODO(adamk): Add tests for objects with hidden prototypes
1485
// once we support observing the global object.
1486
Object.deliverChangeRecords(observer.callback);
1487
observer.assertCallbackRecords([
1488
  { object: obj, name: '__proto__', type: 'prototype',
1489
    oldValue: Object.prototype },
1490
  { object: obj, name: '__proto__', type: 'prototype', oldValue: p },
1491
  { object: obj, name: '__proto__', type: 'new' },
1492
]);
1493

    
1494

    
1495
// Function.prototype
1496
reset();
1497
var fun = function(){};
1498
Object.observe(fun, observer.callback);
1499
var myproto = {foo: 'bar'};
1500
fun.prototype = myproto;
1501
fun.prototype = 7;
1502
fun.prototype = 7;  // ignored
1503
Object.defineProperty(fun, 'prototype', {value: 8});
1504
Object.deliverChangeRecords(observer.callback);
1505
observer.assertRecordCount(3);
1506
// Manually examine the first record in order to test
1507
// lazy creation of oldValue
1508
assertSame(fun, observer.records[0].object);
1509
assertEquals('prototype', observer.records[0].name);
1510
assertEquals('updated', observer.records[0].type);
1511
// The only existing reference to the oldValue object is in this
1512
// record, so to test that lazy creation happened correctly
1513
// we compare its constructor to our function (one of the invariants
1514
// ensured when creating an object via AllocateFunctionPrototype).
1515
assertSame(fun, observer.records[0].oldValue.constructor);
1516
observer.records.splice(0, 1);
1517
observer.assertCallbackRecords([
1518
  { object: fun, name: 'prototype', type: 'updated', oldValue: myproto },
1519
  { object: fun, name: 'prototype', type: 'updated', oldValue: 7 },
1520
]);
1521

    
1522
// Function.prototype should not be observable except on the object itself
1523
reset();
1524
var fun = function(){};
1525
var obj = { __proto__: fun };
1526
Object.observe(obj, observer.callback);
1527
obj.prototype = 7;
1528
Object.deliverChangeRecords(observer.callback);
1529
observer.assertNotCalled();
1530

    
1531

    
1532
// Check that changes in observation status are detected in all IC states and
1533
// in optimized code, especially in cases usually using fast elements.
1534
var mutation = [
1535
  "a[i] = v",
1536
  "a[i] ? ++a[i] : a[i] = v",
1537
  "a[i] ? a[i]++ : a[i] = v",
1538
  "a[i] ? a[i] += 1 : a[i] = v",
1539
  "a[i] ? a[i] -= -1 : a[i] = v",
1540
];
1541

    
1542
var props = [1, "1", "a"];
1543

    
1544
function TestFastElements(prop, mutation, prepopulate, polymorphic, optimize) {
1545
  var setElement = eval(
1546
    "(function setElement(a, i, v) { " + mutation + "; " +
1547
    "/* " + [].join.call(arguments, " ") + " */" +
1548
    "})"
1549
  );
1550
  print("TestFastElements:", setElement);
1551

    
1552
  var arr = prepopulate ? [1, 2, 3, 4, 5] : [0];
1553
  if (prepopulate) arr[prop] = 2;  // for non-element case
1554
  setElement(arr, prop, 3);
1555
  setElement(arr, prop, 4);
1556
  if (polymorphic) setElement(["M", "i", "l", "n", "e", "r"], 0, "m");
1557
  if (optimize) %OptimizeFunctionOnNextCall(setElement);
1558
  setElement(arr, prop, 5);
1559

    
1560
  reset();
1561
  Object.observe(arr, observer.callback);
1562
  setElement(arr, prop, 989898);
1563
  Object.deliverChangeRecords(observer.callback);
1564
  observer.assertCallbackRecords([
1565
    { object: arr, name: "" + prop, type: 'updated', oldValue: 5 }
1566
  ]);
1567
}
1568

    
1569
for (var b1 = 0; b1 < 2; ++b1)
1570
  for (var b2 = 0; b2 < 2; ++b2)
1571
    for (var b3 = 0; b3 < 2; ++b3)
1572
      for (var i in props)
1573
        for (var j in mutation)
1574
          TestFastElements(props[i], mutation[j], b1 != 0, b2 != 0, b3 != 0);
1575

    
1576

    
1577
var mutation = [
1578
  "a.length = v",
1579
  "a.length += newSize - oldSize",
1580
  "a.length -= oldSize - newSize",
1581
];
1582

    
1583
var mutationByIncr = [
1584
  "++a.length",
1585
  "a.length++",
1586
];
1587

    
1588
function TestFastElementsLength(
1589
  mutation, polymorphic, optimize, oldSize, newSize) {
1590
  var setLength = eval(
1591
    "(function setLength(a, v) { " + mutation + "; " +
1592
    "/* " + [].join.call(arguments, " ") + " */"
1593
    + "})"
1594
  );
1595
  print("TestFastElementsLength:", setLength);
1596

    
1597
  function array(n) {
1598
    var arr = new Array(n);
1599
    for (var i = 0; i < n; ++i) arr[i] = i;
1600
    return arr;
1601
  }
1602

    
1603
  setLength(array(oldSize), newSize);
1604
  setLength(array(oldSize), newSize);
1605
  if (polymorphic) setLength(array(oldSize).map(isNaN), newSize);
1606
  if (optimize) %OptimizeFunctionOnNextCall(setLength);
1607
  setLength(array(oldSize), newSize);
1608

    
1609
  reset();
1610
  var arr = array(oldSize);
1611
  Object.observe(arr, observer.callback);
1612
  setLength(arr, newSize);
1613
  Object.deliverChangeRecords(observer.callback);
1614
  if (oldSize === newSize) {
1615
    observer.assertNotCalled();
1616
  } else {
1617
    var count = oldSize > newSize ? oldSize - newSize : 0;
1618
    observer.assertRecordCount(count + 1);
1619
    var lengthRecord = observer.records[count];
1620
    assertSame(arr, lengthRecord.object);
1621
    assertEquals('length', lengthRecord.name);
1622
    assertEquals('updated', lengthRecord.type);
1623
    assertSame(oldSize, lengthRecord.oldValue);
1624
  }
1625
}
1626

    
1627
for (var b1 = 0; b1 < 2; ++b1)
1628
  for (var b2 = 0; b2 < 2; ++b2)
1629
    for (var n1 = 0; n1 < 3; ++n1)
1630
      for (var n2 = 0; n2 < 3; ++n2)
1631
        for (var i in mutation)
1632
          TestFastElementsLength(mutation[i], b1 != 0, b2 != 0, 20*n1, 20*n2);
1633

    
1634
for (var b1 = 0; b1 < 2; ++b1)
1635
  for (var b2 = 0; b2 < 2; ++b2)
1636
    for (var n = 0; n < 3; ++n)
1637
      for (var i in mutationByIncr)
1638
        TestFastElementsLength(mutationByIncr[i], b1 != 0, b2 != 0, 7*n, 7*n+1);