~webapps/unity-js-scopes/node.js

« back to all changes in this revision

Viewing changes to deps/v8/test/mjsunit/es7/object-observe.js

  • Committer: Marcus Tomlinson
  • Date: 2015-11-13 07:59:04 UTC
  • Revision ID: marcus.tomlinson@canonical.com-20151113075904-h0swczmoq1rvstfc
Node v4 (stable)

Show diffs side-by-side

added added

removed removed

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