Home | History | Annotate | Download | only in harmony
      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: 'update',
    191   name: 'foo',
    192   expando: 1
    193 });
    194 
    195 notifier.notify({
    196   object: notifier,  // object property is ignored
    197   type: 'delete',
    198   name: 'bar',
    199   expando2: 'str'
    200 });
    201 Object.deliverChangeRecords(observer.callback);
    202 observer.assertCallbackRecords([
    203   { object: obj, name: 'foo', type: 'update', expando: 1 },
    204   { object: obj, name: 'bar', type: 'delete', 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: 'update',
    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: 'update',
    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: 'update',
    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: 'update',
    269 });
    270 Object.observe(obj, observer.callback);
    271 Object.getNotifier(obj).notify({
    272   type: 'update',
    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: 'add', 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: 'add', name: '' },
    299   { object: obj, type: 'update', name: '', oldValue: '' },
    300   { object: obj, type: 'delete', name: '', oldValue: ' ' },
    301 ]);
    302 
    303 // Object.preventExtensions
    304 reset();
    305 var obj = { foo: 'bar'};
    306 Object.observe(obj, observer.callback);
    307 obj.baz = 'bat';
    308 Object.preventExtensions(obj);
    309 
    310 Object.deliverChangeRecords(observer.callback);
    311 observer.assertCallbackRecords([
    312   { object: obj, type: 'add', name: 'baz' },
    313   { object: obj, type: 'preventExtensions' },
    314 ]);
    315 
    316 reset();
    317 var obj = { foo: 'bar'};
    318 Object.preventExtensions(obj);
    319 Object.observe(obj, observer.callback);
    320 Object.preventExtensions(obj);
    321 Object.deliverChangeRecords(observer.callback);
    322 observer.assertNotCalled();
    323 
    324 // Object.freeze
    325 reset();
    326 var obj = { a: 'a' };
    327 Object.defineProperty(obj, 'b', {
    328   writable: false,
    329   configurable: true,
    330   value: 'b'
    331 });
    332 Object.defineProperty(obj, 'c', {
    333   writable: true,
    334   configurable: false,
    335   value: 'c'
    336 });
    337 Object.defineProperty(obj, 'd', {
    338   writable: false,
    339   configurable: false,
    340   value: 'd'
    341 });
    342 Object.observe(obj, observer.callback);
    343 Object.freeze(obj);
    344 
    345 Object.deliverChangeRecords(observer.callback);
    346 observer.assertCallbackRecords([
    347   { object: obj, type: 'reconfigure', name: 'a' },
    348   { object: obj, type: 'reconfigure', name: 'b' },
    349   { object: obj, type: 'reconfigure', name: 'c' },
    350   { object: obj, type: 'preventExtensions' },
    351 ]);
    352 
    353 reset();
    354 var obj = { foo: 'bar'};
    355 Object.freeze(obj);
    356 Object.observe(obj, observer.callback);
    357 Object.freeze(obj);
    358 Object.deliverChangeRecords(observer.callback);
    359 observer.assertNotCalled();
    360 
    361 // Object.seal
    362 reset();
    363 var obj = { a: 'a' };
    364 Object.defineProperty(obj, 'b', {
    365   writable: false,
    366   configurable: true,
    367   value: 'b'
    368 });
    369 Object.defineProperty(obj, 'c', {
    370   writable: true,
    371   configurable: false,
    372   value: 'c'
    373 });
    374 Object.defineProperty(obj, 'd', {
    375   writable: false,
    376   configurable: false,
    377   value: 'd'
    378 });
    379 Object.observe(obj, observer.callback);
    380 Object.seal(obj);
    381 
    382 Object.deliverChangeRecords(observer.callback);
    383 observer.assertCallbackRecords([
    384   { object: obj, type: 'reconfigure', name: 'a' },
    385   { object: obj, type: 'reconfigure', name: 'b' },
    386   { object: obj, type: 'preventExtensions' },
    387 ]);
    388 
    389 reset();
    390 var obj = { foo: 'bar'};
    391 Object.seal(obj);
    392 Object.observe(obj, observer.callback);
    393 Object.seal(obj);
    394 Object.deliverChangeRecords(observer.callback);
    395 observer.assertNotCalled();
    396 
    397 // Observing a continuous stream of changes, while itermittantly unobserving.
    398 reset();
    399 var obj = {};
    400 Object.observe(obj, observer.callback);
    401 Object.getNotifier(obj).notify({
    402   type: 'update',
    403   val: 1
    404 });
    405 
    406 Object.unobserve(obj, observer.callback);
    407 Object.getNotifier(obj).notify({
    408   type: 'update',
    409   val: 2
    410 });
    411 
    412 Object.observe(obj, observer.callback);
    413 Object.getNotifier(obj).notify({
    414   type: 'update',
    415   val: 3
    416 });
    417 
    418 Object.unobserve(obj, observer.callback);
    419 Object.getNotifier(obj).notify({
    420   type: 'update',
    421   val: 4
    422 });
    423 
    424 Object.observe(obj, observer.callback);
    425 Object.getNotifier(obj).notify({
    426   type: 'update',
    427   val: 5
    428 });
    429 
    430 Object.unobserve(obj, observer.callback);
    431 Object.deliverChangeRecords(observer.callback);
    432 observer.assertCallbackRecords([
    433   { object: obj, type: 'update', val: 1 },
    434   { object: obj, type: 'update', val: 3 },
    435   { object: obj, type: 'update', val: 5 }
    436 ]);
    437 
    438 // Accept
    439 reset();
    440 Object.observe(obj, observer.callback, ['somethingElse']);
    441 Object.getNotifier(obj).notify({
    442   type: 'add'
    443 });
    444 Object.getNotifier(obj).notify({
    445   type: 'update'
    446 });
    447 Object.getNotifier(obj).notify({
    448   type: 'delete'
    449 });
    450 Object.getNotifier(obj).notify({
    451   type: 'reconfigure'
    452 });
    453 Object.getNotifier(obj).notify({
    454   type: 'setPrototype'
    455 });
    456 Object.deliverChangeRecords(observer.callback);
    457 observer.assertNotCalled();
    458 
    459 reset();
    460 Object.observe(obj, observer.callback, ['add', 'delete', 'setPrototype']);
    461 Object.getNotifier(obj).notify({
    462   type: 'add'
    463 });
    464 Object.getNotifier(obj).notify({
    465   type: 'update'
    466 });
    467 Object.getNotifier(obj).notify({
    468   type: 'delete'
    469 });
    470 Object.getNotifier(obj).notify({
    471   type: 'delete'
    472 });
    473 Object.getNotifier(obj).notify({
    474   type: 'reconfigure'
    475 });
    476 Object.getNotifier(obj).notify({
    477   type: 'setPrototype'
    478 });
    479 Object.deliverChangeRecords(observer.callback);
    480 observer.assertCallbackRecords([
    481   { object: obj, type: 'add' },
    482   { object: obj, type: 'delete' },
    483   { object: obj, type: 'delete' },
    484   { object: obj, type: 'setPrototype' }
    485 ]);
    486 
    487 reset();
    488 Object.observe(obj, observer.callback, ['update', 'foo']);
    489 Object.getNotifier(obj).notify({
    490   type: 'add'
    491 });
    492 Object.getNotifier(obj).notify({
    493   type: 'update'
    494 });
    495 Object.getNotifier(obj).notify({
    496   type: 'delete'
    497 });
    498 Object.getNotifier(obj).notify({
    499   type: 'foo'
    500 });
    501 Object.getNotifier(obj).notify({
    502   type: 'bar'
    503 });
    504 Object.getNotifier(obj).notify({
    505   type: 'foo'
    506 });
    507 Object.deliverChangeRecords(observer.callback);
    508 observer.assertCallbackRecords([
    509   { object: obj, type: 'update' },
    510   { object: obj, type: 'foo' },
    511   { object: obj, type: 'foo' }
    512 ]);
    513 
    514 reset();
    515 function Thingy(a, b, c) {
    516   this.a = a;
    517   this.b = b;
    518 }
    519 
    520 Thingy.MULTIPLY = 'multiply';
    521 Thingy.INCREMENT = 'increment';
    522 Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply';
    523 
    524 Thingy.prototype = {
    525   increment: function(amount) {
    526     var notifier = Object.getNotifier(this);
    527 
    528     var self = this;
    529     notifier.performChange(Thingy.INCREMENT, function() {
    530       self.a += amount;
    531       self.b += amount;
    532 
    533       return {
    534         incremented: amount
    535       };  // implicit notify
    536     });
    537   },
    538 
    539   multiply: function(amount) {
    540     var notifier = Object.getNotifier(this);
    541 
    542     var self = this;
    543     notifier.performChange(Thingy.MULTIPLY, function() {
    544       self.a *= amount;
    545       self.b *= amount;
    546 
    547       return {
    548         multiplied: amount
    549       };  // implicit notify
    550     });
    551   },
    552 
    553   incrementAndMultiply: function(incAmount, multAmount) {
    554     var notifier = Object.getNotifier(this);
    555 
    556     var self = this;
    557     notifier.performChange(Thingy.INCREMENT_AND_MULTIPLY, function() {
    558       self.increment(incAmount);
    559       self.multiply(multAmount);
    560 
    561       return {
    562         incremented: incAmount,
    563         multiplied: multAmount
    564       };  // implicit notify
    565     });
    566   }
    567 }
    568 
    569 Thingy.observe = function(thingy, callback) {
    570   Object.observe(thingy, callback, [Thingy.INCREMENT,
    571                                     Thingy.MULTIPLY,
    572                                     Thingy.INCREMENT_AND_MULTIPLY,
    573                                     'update']);
    574 }
    575 
    576 Thingy.unobserve = function(thingy, callback) {
    577   Object.unobserve(thingy);
    578 }
    579 
    580 var thingy = new Thingy(2, 4);
    581 
    582 Object.observe(thingy, observer.callback);
    583 Thingy.observe(thingy, observer2.callback);
    584 thingy.increment(3);               // { a: 5, b: 7 }
    585 thingy.b++;                        // { a: 5, b: 8 }
    586 thingy.multiply(2);                // { a: 10, b: 16 }
    587 thingy.a++;                        // { a: 11, b: 16 }
    588 thingy.incrementAndMultiply(2, 2); // { a: 26, b: 36 }
    589 
    590 Object.deliverChangeRecords(observer.callback);
    591 Object.deliverChangeRecords(observer2.callback);
    592 observer.assertCallbackRecords([
    593   { object: thingy, type: 'update', name: 'a', oldValue: 2 },
    594   { object: thingy, type: 'update', name: 'b', oldValue: 4 },
    595   { object: thingy, type: 'update', name: 'b', oldValue: 7 },
    596   { object: thingy, type: 'update', name: 'a', oldValue: 5 },
    597   { object: thingy, type: 'update', name: 'b', oldValue: 8 },
    598   { object: thingy, type: 'update', name: 'a', oldValue: 10 },
    599   { object: thingy, type: 'update', name: 'a', oldValue: 11 },
    600   { object: thingy, type: 'update', name: 'b', oldValue: 16 },
    601   { object: thingy, type: 'update', name: 'a', oldValue: 13 },
    602   { object: thingy, type: 'update', name: 'b', oldValue: 18 },
    603 ]);
    604 observer2.assertCallbackRecords([
    605   { object: thingy, type: Thingy.INCREMENT, incremented: 3 },
    606   { object: thingy, type: 'update', name: 'b', oldValue: 7 },
    607   { object: thingy, type: Thingy.MULTIPLY, multiplied: 2 },
    608   { object: thingy, type: 'update', name: 'a', oldValue: 10 },
    609   {
    610     object: thingy,
    611     type: Thingy.INCREMENT_AND_MULTIPLY,
    612     incremented: 2,
    613     multiplied: 2
    614   }
    615 ]);
    616 
    617 // ArrayPush cached stub
    618 reset();
    619 
    620 function pushMultiple(arr) {
    621   arr.push('a');
    622   arr.push('b');
    623   arr.push('c');
    624 }
    625 
    626 for (var i = 0; i < 5; i++) {
    627   var arr = [];
    628   pushMultiple(arr);
    629 }
    630 
    631 for (var i = 0; i < 5; i++) {
    632   reset();
    633   var arr = [];
    634   Object.observe(arr, observer.callback);
    635   pushMultiple(arr);
    636   Object.unobserve(arr, observer.callback);
    637   Object.deliverChangeRecords(observer.callback);
    638   observer.assertCallbackRecords([
    639     { object: arr, type: 'add', name: '0' },
    640     { object: arr, type: 'update', name: 'length', oldValue: 0 },
    641     { object: arr, type: 'add', name: '1' },
    642     { object: arr, type: 'update', name: 'length', oldValue: 1 },
    643     { object: arr, type: 'add', name: '2' },
    644     { object: arr, type: 'update', name: 'length', oldValue: 2 },
    645   ]);
    646 }
    647 
    648 
    649 // ArrayPop cached stub
    650 reset();
    651 
    652 function popMultiple(arr) {
    653   arr.pop();
    654   arr.pop();
    655   arr.pop();
    656 }
    657 
    658 for (var i = 0; i < 5; i++) {
    659   var arr = ['a', 'b', 'c'];
    660   popMultiple(arr);
    661 }
    662 
    663 for (var i = 0; i < 5; i++) {
    664   reset();
    665   var arr = ['a', 'b', 'c'];
    666   Object.observe(arr, observer.callback);
    667   popMultiple(arr);
    668   Object.unobserve(arr, observer.callback);
    669   Object.deliverChangeRecords(observer.callback);
    670   observer.assertCallbackRecords([
    671     { object: arr, type: 'delete', name: '2', oldValue: 'c' },
    672     { object: arr, type: 'update', name: 'length', oldValue: 3 },
    673     { object: arr, type: 'delete', name: '1', oldValue: 'b' },
    674     { object: arr, type: 'update', name: 'length', oldValue: 2 },
    675     { object: arr, type: 'delete', name: '0', oldValue: 'a' },
    676     { object: arr, type: 'update', name: 'length', oldValue: 1 },
    677   ]);
    678 }
    679 
    680 
    681 reset();
    682 function RecursiveThingy() {}
    683 
    684 RecursiveThingy.MULTIPLY_FIRST_N = 'multiplyFirstN';
    685 
    686 RecursiveThingy.prototype = {
    687   __proto__: Array.prototype,
    688 
    689   multiplyFirstN: function(amount, n) {
    690     if (!n)
    691       return;
    692     var notifier = Object.getNotifier(this);
    693     var self = this;
    694     notifier.performChange(RecursiveThingy.MULTIPLY_FIRST_N, function() {
    695       self[n-1] = self[n-1]*amount;
    696       self.multiplyFirstN(amount, n-1);
    697     });
    698 
    699     notifier.notify({
    700       type: RecursiveThingy.MULTIPLY_FIRST_N,
    701       multiplied: amount,
    702       n: n
    703     });
    704   },
    705 }
    706 
    707 RecursiveThingy.observe = function(thingy, callback) {
    708   Object.observe(thingy, callback, [RecursiveThingy.MULTIPLY_FIRST_N]);
    709 }
    710 
    711 RecursiveThingy.unobserve = function(thingy, callback) {
    712   Object.unobserve(thingy);
    713 }
    714 
    715 var thingy = new RecursiveThingy;
    716 thingy.push(1, 2, 3, 4);
    717 
    718 Object.observe(thingy, observer.callback);
    719 RecursiveThingy.observe(thingy, observer2.callback);
    720 thingy.multiplyFirstN(2, 3);                // [2, 4, 6, 4]
    721 
    722 Object.deliverChangeRecords(observer.callback);
    723 Object.deliverChangeRecords(observer2.callback);
    724 observer.assertCallbackRecords([
    725   { object: thingy, type: 'update', name: '2', oldValue: 3 },
    726   { object: thingy, type: 'update', name: '1', oldValue: 2 },
    727   { object: thingy, type: 'update', name: '0', oldValue: 1 }
    728 ]);
    729 observer2.assertCallbackRecords([
    730   { object: thingy, type: RecursiveThingy.MULTIPLY_FIRST_N, multiplied: 2, n: 3 }
    731 ]);
    732 
    733 reset();
    734 function DeckSuit() {
    735   this.push('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'A', 'Q', 'K');
    736 }
    737 
    738 DeckSuit.SHUFFLE = 'shuffle';
    739 
    740 DeckSuit.prototype = {
    741   __proto__: Array.prototype,
    742 
    743   shuffle: function() {
    744     var notifier = Object.getNotifier(this);
    745     var self = this;
    746     notifier.performChange(DeckSuit.SHUFFLE, function() {
    747       self.reverse();
    748       self.sort(function() { return Math.random()* 2 - 1; });
    749       var cut = self.splice(0, 6);
    750       Array.prototype.push.apply(self, cut);
    751       self.reverse();
    752       self.sort(function() { return Math.random()* 2 - 1; });
    753       var cut = self.splice(0, 6);
    754       Array.prototype.push.apply(self, cut);
    755       self.reverse();
    756       self.sort(function() { return Math.random()* 2 - 1; });
    757     });
    758 
    759     notifier.notify({
    760       type: DeckSuit.SHUFFLE
    761     });
    762   },
    763 }
    764 
    765 DeckSuit.observe = function(thingy, callback) {
    766   Object.observe(thingy, callback, [DeckSuit.SHUFFLE]);
    767 }
    768 
    769 DeckSuit.unobserve = function(thingy, callback) {
    770   Object.unobserve(thingy);
    771 }
    772 
    773 var deck = new DeckSuit;
    774 
    775 DeckSuit.observe(deck, observer2.callback);
    776 deck.shuffle();
    777 
    778 Object.deliverChangeRecords(observer2.callback);
    779 observer2.assertCallbackRecords([
    780   { object: deck, type: DeckSuit.SHUFFLE }
    781 ]);
    782 
    783 // Observing multiple objects; records appear in order.
    784 reset();
    785 var obj2 = {};
    786 var obj3 = {}
    787 Object.observe(obj, observer.callback);
    788 Object.observe(obj3, observer.callback);
    789 Object.observe(obj2, observer.callback);
    790 Object.getNotifier(obj).notify({
    791   type: 'add',
    792 });
    793 Object.getNotifier(obj2).notify({
    794   type: 'update',
    795 });
    796 Object.getNotifier(obj3).notify({
    797   type: 'delete',
    798 });
    799 Object.observe(obj3, observer.callback);
    800 Object.deliverChangeRecords(observer.callback);
    801 observer.assertCallbackRecords([
    802   { object: obj, type: 'add' },
    803   { object: obj2, type: 'update' },
    804   { object: obj3, type: 'delete' }
    805 ]);
    806 
    807 
    808 // Recursive observation.
    809 var obj = {a: 1};
    810 var callbackCount = 0;
    811 function recursiveObserver(r) {
    812   assertEquals(1, r.length);
    813   ++callbackCount;
    814   if (r[0].oldValue < 100) ++obj[r[0].name];
    815 }
    816 Object.observe(obj, recursiveObserver);
    817 ++obj.a;
    818 Object.deliverChangeRecords(recursiveObserver);
    819 assertEquals(100, callbackCount);
    820 
    821 var obj1 = {a: 1};
    822 var obj2 = {a: 1};
    823 var recordCount = 0;
    824 function recursiveObserver2(r) {
    825   recordCount += r.length;
    826   if (r[0].oldValue < 100) {
    827     ++obj1.a;
    828     ++obj2.a;
    829   }
    830 }
    831 Object.observe(obj1, recursiveObserver2);
    832 Object.observe(obj2, recursiveObserver2);
    833 ++obj1.a;
    834 Object.deliverChangeRecords(recursiveObserver2);
    835 assertEquals(199, recordCount);
    836 
    837 
    838 // Observing named properties.
    839 reset();
    840 var obj = {a: 1}
    841 Object.observe(obj, observer.callback);
    842 obj.a = 2;
    843 obj["a"] = 3;
    844 delete obj.a;
    845 obj.a = 4;
    846 obj.a = 4;  // ignored
    847 obj.a = 5;
    848 Object.defineProperty(obj, "a", {value: 6});
    849 Object.defineProperty(obj, "a", {writable: false});
    850 obj.a = 7;  // ignored
    851 Object.defineProperty(obj, "a", {value: 8});
    852 Object.defineProperty(obj, "a", {value: 7, writable: true});
    853 Object.defineProperty(obj, "a", {get: function() {}});
    854 Object.defineProperty(obj, "a", {get: frozenFunction});
    855 Object.defineProperty(obj, "a", {get: frozenFunction});  // ignored
    856 Object.defineProperty(obj, "a", {get: frozenFunction, set: frozenFunction});
    857 Object.defineProperty(obj, "a", {set: frozenFunction});  // ignored
    858 Object.defineProperty(obj, "a", {get: undefined, set: frozenFunction});
    859 delete obj.a;
    860 delete obj.a;
    861 Object.defineProperty(obj, "a", {get: function() {}, configurable: true});
    862 Object.defineProperty(obj, "a", {value: 9, writable: true});
    863 obj.a = 10;
    864 ++obj.a;
    865 obj.a++;
    866 obj.a *= 3;
    867 delete obj.a;
    868 Object.defineProperty(obj, "a", {value: 11, configurable: true});
    869 Object.deliverChangeRecords(observer.callback);
    870 observer.assertCallbackRecords([
    871   { object: obj, name: "a", type: "update", oldValue: 1 },
    872   { object: obj, name: "a", type: "update", oldValue: 2 },
    873   { object: obj, name: "a", type: "delete", oldValue: 3 },
    874   { object: obj, name: "a", type: "add" },
    875   { object: obj, name: "a", type: "update", oldValue: 4 },
    876   { object: obj, name: "a", type: "update", oldValue: 5 },
    877   { object: obj, name: "a", type: "reconfigure" },
    878   { object: obj, name: "a", type: "update", oldValue: 6 },
    879   { object: obj, name: "a", type: "reconfigure", oldValue: 8 },
    880   { object: obj, name: "a", type: "reconfigure", oldValue: 7 },
    881   { object: obj, name: "a", type: "reconfigure" },
    882   { object: obj, name: "a", type: "reconfigure" },
    883   { object: obj, name: "a", type: "reconfigure" },
    884   { object: obj, name: "a", type: "delete" },
    885   { object: obj, name: "a", type: "add" },
    886   { object: obj, name: "a", type: "reconfigure" },
    887   { object: obj, name: "a", type: "update", oldValue: 9 },
    888   { object: obj, name: "a", type: "update", oldValue: 10 },
    889   { object: obj, name: "a", type: "update", oldValue: 11 },
    890   { object: obj, name: "a", type: "update", oldValue: 12 },
    891   { object: obj, name: "a", type: "delete", oldValue: 36 },
    892   { object: obj, name: "a", type: "add" },
    893 ]);
    894 
    895 
    896 // Observing indexed properties.
    897 reset();
    898 var obj = {'1': 1}
    899 Object.observe(obj, observer.callback);
    900 obj[1] = 2;
    901 obj[1] = 3;
    902 delete obj[1];
    903 obj[1] = 4;
    904 obj[1] = 4;  // ignored
    905 obj[1] = 5;
    906 Object.defineProperty(obj, "1", {value: 6});
    907 Object.defineProperty(obj, "1", {writable: false});
    908 obj[1] = 7;  // ignored
    909 Object.defineProperty(obj, "1", {value: 8});
    910 Object.defineProperty(obj, "1", {value: 7, writable: true});
    911 Object.defineProperty(obj, "1", {get: function() {}});
    912 Object.defineProperty(obj, "1", {get: frozenFunction});
    913 Object.defineProperty(obj, "1", {get: frozenFunction});  // ignored
    914 Object.defineProperty(obj, "1", {get: frozenFunction, set: frozenFunction});
    915 Object.defineProperty(obj, "1", {set: frozenFunction});  // ignored
    916 Object.defineProperty(obj, "1", {get: undefined, set: frozenFunction});
    917 delete obj[1];
    918 delete obj[1];
    919 Object.defineProperty(obj, "1", {get: function() {}, configurable: true});
    920 Object.defineProperty(obj, "1", {value: 9, writable: true});
    921 obj[1] = 10;
    922 ++obj[1];
    923 obj[1]++;
    924 obj[1] *= 3;
    925 delete obj[1];
    926 Object.defineProperty(obj, "1", {value: 11, configurable: true});
    927 Object.deliverChangeRecords(observer.callback);
    928 observer.assertCallbackRecords([
    929   { object: obj, name: "1", type: "update", oldValue: 1 },
    930   { object: obj, name: "1", type: "update", oldValue: 2 },
    931   { object: obj, name: "1", type: "delete", oldValue: 3 },
    932   { object: obj, name: "1", type: "add" },
    933   { object: obj, name: "1", type: "update", oldValue: 4 },
    934   { object: obj, name: "1", type: "update", oldValue: 5 },
    935   { object: obj, name: "1", type: "reconfigure" },
    936   { object: obj, name: "1", type: "update", oldValue: 6 },
    937   { object: obj, name: "1", type: "reconfigure", oldValue: 8 },
    938   { object: obj, name: "1", type: "reconfigure", oldValue: 7 },
    939   { object: obj, name: "1", type: "reconfigure" },
    940   { object: obj, name: "1", type: "reconfigure" },
    941   { object: obj, name: "1", type: "reconfigure" },
    942   { object: obj, name: "1", type: "delete" },
    943   { object: obj, name: "1", type: "add" },
    944   { object: obj, name: "1", type: "reconfigure" },
    945   { object: obj, name: "1", type: "update", oldValue: 9 },
    946   { object: obj, name: "1", type: "update", oldValue: 10 },
    947   { object: obj, name: "1", type: "update", oldValue: 11 },
    948   { object: obj, name: "1", type: "update", oldValue: 12 },
    949   { object: obj, name: "1", type: "delete", oldValue: 36 },
    950   { object: obj, name: "1", type: "add" },
    951 ]);
    952 
    953 
    954 // Observing symbol properties (not).
    955 print("*****")
    956 reset();
    957 var obj = {}
    958 var symbol = Symbol("secret");
    959 Object.observe(obj, observer.callback);
    960 obj[symbol] = 3;
    961 delete obj[symbol];
    962 Object.defineProperty(obj, symbol, {get: function() {}, configurable: true});
    963 Object.defineProperty(obj, symbol, {value: 6});
    964 Object.defineProperty(obj, symbol, {writable: false});
    965 delete obj[symbol];
    966 Object.defineProperty(obj, symbol, {value: 7});
    967 ++obj[symbol];
    968 obj[symbol]++;
    969 obj[symbol] *= 3;
    970 delete obj[symbol];
    971 obj.__defineSetter__(symbol, function() {});
    972 obj.__defineGetter__(symbol, function() {});
    973 Object.deliverChangeRecords(observer.callback);
    974 observer.assertNotCalled();
    975 
    976 
    977 // Test all kinds of objects generically.
    978 function TestObserveConfigurable(obj, prop) {
    979   reset();
    980   Object.observe(obj, observer.callback);
    981   Object.unobserve(obj, observer.callback);
    982   obj[prop] = 1;
    983   Object.observe(obj, observer.callback);
    984   obj[prop] = 2;
    985   obj[prop] = 3;
    986   delete obj[prop];
    987   obj[prop] = 4;
    988   obj[prop] = 4;  // ignored
    989   obj[prop] = 5;
    990   Object.defineProperty(obj, prop, {value: 6});
    991   Object.defineProperty(obj, prop, {writable: false});
    992   obj[prop] = 7;  // ignored
    993   Object.defineProperty(obj, prop, {value: 8});
    994   Object.defineProperty(obj, prop, {value: 7, writable: true});
    995   Object.defineProperty(obj, prop, {get: function() {}});
    996   Object.defineProperty(obj, prop, {get: frozenFunction});
    997   Object.defineProperty(obj, prop, {get: frozenFunction});  // ignored
    998   Object.defineProperty(obj, prop, {get: frozenFunction, set: frozenFunction});
    999   Object.defineProperty(obj, prop, {set: frozenFunction});  // ignored
   1000   Object.defineProperty(obj, prop, {get: undefined, set: frozenFunction});
   1001   obj.__defineSetter__(prop, frozenFunction);  // ignored
   1002   obj.__defineSetter__(prop, function() {});
   1003   obj.__defineGetter__(prop, function() {});
   1004   delete obj[prop];
   1005   delete obj[prop];  // ignored
   1006   obj.__defineGetter__(prop, function() {});
   1007   delete obj[prop];
   1008   Object.defineProperty(obj, prop, {get: function() {}, configurable: true});
   1009   Object.defineProperty(obj, prop, {value: 9, writable: true});
   1010   obj[prop] = 10;
   1011   ++obj[prop];
   1012   obj[prop]++;
   1013   obj[prop] *= 3;
   1014   delete obj[prop];
   1015   Object.defineProperty(obj, prop, {value: 11, configurable: true});
   1016   Object.deliverChangeRecords(observer.callback);
   1017   observer.assertCallbackRecords([
   1018     { object: obj, name: prop, type: "update", oldValue: 1 },
   1019     { object: obj, name: prop, type: "update", oldValue: 2 },
   1020     { object: obj, name: prop, type: "delete", oldValue: 3 },
   1021     { object: obj, name: prop, type: "add" },
   1022     { object: obj, name: prop, type: "update", oldValue: 4 },
   1023     { object: obj, name: prop, type: "update", oldValue: 5 },
   1024     { object: obj, name: prop, type: "reconfigure" },
   1025     { object: obj, name: prop, type: "update", oldValue: 6 },
   1026     { object: obj, name: prop, type: "reconfigure", oldValue: 8 },
   1027     { object: obj, name: prop, type: "reconfigure", oldValue: 7 },
   1028     { object: obj, name: prop, type: "reconfigure" },
   1029     { object: obj, name: prop, type: "reconfigure" },
   1030     { object: obj, name: prop, type: "reconfigure" },
   1031     { object: obj, name: prop, type: "reconfigure" },
   1032     { object: obj, name: prop, type: "reconfigure" },
   1033     { object: obj, name: prop, type: "delete" },
   1034     { object: obj, name: prop, type: "add" },
   1035     { object: obj, name: prop, type: "delete" },
   1036     { object: obj, name: prop, type: "add" },
   1037     { object: obj, name: prop, type: "reconfigure" },
   1038     { object: obj, name: prop, type: "update", oldValue: 9 },
   1039     { object: obj, name: prop, type: "update", oldValue: 10 },
   1040     { object: obj, name: prop, type: "update", oldValue: 11 },
   1041     { object: obj, name: prop, type: "update", oldValue: 12 },
   1042     { object: obj, name: prop, type: "delete", oldValue: 36 },
   1043     { object: obj, name: prop, type: "add" },
   1044   ]);
   1045   Object.unobserve(obj, observer.callback);
   1046   delete obj[prop];
   1047 }
   1048 
   1049 function TestObserveNonConfigurable(obj, prop, desc) {
   1050   reset();
   1051   Object.observe(obj, observer.callback);
   1052   Object.unobserve(obj, observer.callback);
   1053   obj[prop] = 1;
   1054   Object.observe(obj, observer.callback);
   1055   obj[prop] = 4;
   1056   obj[prop] = 4;  // ignored
   1057   obj[prop] = 5;
   1058   Object.defineProperty(obj, prop, {value: 6});
   1059   Object.defineProperty(obj, prop, {value: 6});  // ignored
   1060   Object.defineProperty(obj, prop, {value: 7});
   1061   Object.defineProperty(obj, prop, {enumerable: desc.enumerable});  // ignored
   1062   Object.defineProperty(obj, prop, {writable: false});
   1063   obj[prop] = 7;  // ignored
   1064   Object.deliverChangeRecords(observer.callback);
   1065   observer.assertCallbackRecords([
   1066     { object: obj, name: prop, type: "update", oldValue: 1 },
   1067     { object: obj, name: prop, type: "update", oldValue: 4 },
   1068     { object: obj, name: prop, type: "update", oldValue: 5 },
   1069     { object: obj, name: prop, type: "update", oldValue: 6 },
   1070     { object: obj, name: prop, type: "reconfigure" },
   1071   ]);
   1072   Object.unobserve(obj, observer.callback);
   1073 }
   1074 
   1075 function createProxy(create, x) {
   1076   var handler = {
   1077     getPropertyDescriptor: function(k) {
   1078       for (var o = this.target; o; o = Object.getPrototypeOf(o)) {
   1079         var desc = Object.getOwnPropertyDescriptor(o, k);
   1080         if (desc) return desc;
   1081       }
   1082       return undefined;
   1083     },
   1084     getOwnPropertyDescriptor: function(k) {
   1085       return Object.getOwnPropertyDescriptor(this.target, k);
   1086     },
   1087     defineProperty: function(k, desc) {
   1088       var x = Object.defineProperty(this.target, k, desc);
   1089       Object.deliverChangeRecords(this.callback);
   1090       return x;
   1091     },
   1092     delete: function(k) {
   1093       var x = delete this.target[k];
   1094       Object.deliverChangeRecords(this.callback);
   1095       return x;
   1096     },
   1097     getPropertyNames: function() {
   1098       return Object.getOwnPropertyNames(this.target);
   1099     },
   1100     target: {isProxy: true},
   1101     callback: function(changeRecords) {
   1102       print("callback", stringifyNoThrow(handler.proxy), stringifyNoThrow(got));
   1103       for (var i in changeRecords) {
   1104         var got = changeRecords[i];
   1105         var change = {object: handler.proxy, name: got.name, type: got.type};
   1106         if ("oldValue" in got) change.oldValue = got.oldValue;
   1107         Object.getNotifier(handler.proxy).notify(change);
   1108       }
   1109     },
   1110   };
   1111   Object.observe(handler.target, handler.callback);
   1112   return handler.proxy = create(handler, x);
   1113 }
   1114 
   1115 var objects = [
   1116   {},
   1117   [],
   1118   this,  // global object
   1119   function(){},
   1120   (function(){ return arguments })(),
   1121   (function(){ "use strict"; return arguments })(),
   1122   Object(1), Object(true), Object("bla"),
   1123   new Date(),
   1124   Object, Function, Date, RegExp,
   1125   new Set, new Map, new WeakMap,
   1126   new ArrayBuffer(10), new Int32Array(5),
   1127   createProxy(Proxy.create, null),
   1128   createProxy(Proxy.createFunction, function(){}),
   1129 ];
   1130 var properties = ["a", "1", 1, "length", "setPrototype", "name", "caller"];
   1131 
   1132 // Cases that yield non-standard results.
   1133 function blacklisted(obj, prop) {
   1134   return (obj instanceof Int32Array && prop == 1) ||
   1135          (obj instanceof Int32Array && prop === "length") ||
   1136          (obj instanceof ArrayBuffer && prop == 1)
   1137 }
   1138 
   1139 for (var i in objects) for (var j in properties) {
   1140   var obj = objects[i];
   1141   var prop = properties[j];
   1142   if (blacklisted(obj, prop)) continue;
   1143   var desc = Object.getOwnPropertyDescriptor(obj, prop);
   1144   print("***", typeof obj, stringifyNoThrow(obj), prop);
   1145   if (!desc || desc.configurable)
   1146     TestObserveConfigurable(obj, prop);
   1147   else if (desc.writable)
   1148     TestObserveNonConfigurable(obj, prop, desc);
   1149 }
   1150 
   1151 
   1152 // Observing array length (including truncation)
   1153 reset();
   1154 var arr = ['a', 'b', 'c', 'd'];
   1155 var arr2 = ['alpha', 'beta'];
   1156 var arr3 = ['hello'];
   1157 arr3[2] = 'goodbye';
   1158 arr3.length = 6;
   1159 Object.defineProperty(arr, '0', {configurable: false});
   1160 Object.defineProperty(arr, '2', {get: function(){}});
   1161 Object.defineProperty(arr2, '0', {get: function(){}, configurable: false});
   1162 Object.observe(arr, observer.callback);
   1163 Array.observe(arr, observer2.callback);
   1164 Object.observe(arr2, observer.callback);
   1165 Array.observe(arr2, observer2.callback);
   1166 Object.observe(arr3, observer.callback);
   1167 Array.observe(arr3, observer2.callback);
   1168 arr.length = 2;
   1169 arr.length = 0;
   1170 arr.length = 10;
   1171 Object.defineProperty(arr, 'length', {writable: false});
   1172 arr2.length = 0;
   1173 arr2.length = 1; // no change expected
   1174 Object.defineProperty(arr2, 'length', {value: 1, writable: false});
   1175 arr3.length = 0;
   1176 ++arr3.length;
   1177 arr3.length++;
   1178 arr3.length /= 2;
   1179 Object.defineProperty(arr3, 'length', {value: 5});
   1180 arr3[4] = 5;
   1181 Object.defineProperty(arr3, 'length', {value: 1, writable: false});
   1182 Object.deliverChangeRecords(observer.callback);
   1183 observer.assertCallbackRecords([
   1184   { object: arr, name: '3', type: 'delete', oldValue: 'd' },
   1185   { object: arr, name: '2', type: 'delete' },
   1186   { object: arr, name: 'length', type: 'update', oldValue: 4 },
   1187   { object: arr, name: '1', type: 'delete', oldValue: 'b' },
   1188   { object: arr, name: 'length', type: 'update', oldValue: 2 },
   1189   { object: arr, name: 'length', type: 'update', oldValue: 1 },
   1190   { object: arr, name: 'length', type: 'reconfigure' },
   1191   { object: arr2, name: '1', type: 'delete', oldValue: 'beta' },
   1192   { object: arr2, name: 'length', type: 'update', oldValue: 2 },
   1193   { object: arr2, name: 'length', type: 'reconfigure' },
   1194   { object: arr3, name: '2', type: 'delete', oldValue: 'goodbye' },
   1195   { object: arr3, name: '0', type: 'delete', oldValue: 'hello' },
   1196   { object: arr3, name: 'length', type: 'update', oldValue: 6 },
   1197   { object: arr3, name: 'length', type: 'update', oldValue: 0 },
   1198   { object: arr3, name: 'length', type: 'update', oldValue: 1 },
   1199   { object: arr3, name: 'length', type: 'update', oldValue: 2 },
   1200   { object: arr3, name: 'length', type: 'update', oldValue: 1 },
   1201   { object: arr3, name: '4', type: 'add' },
   1202   { object: arr3, name: '4', type: 'delete', oldValue: 5 },
   1203   // TODO(rafaelw): It breaks spec compliance to get two records here.
   1204   // When the TODO in v8natives.js::DefineArrayProperty is addressed
   1205   // which prevents DefineProperty from over-writing the magic length
   1206   // property, these will collapse into a single record.
   1207   { object: arr3, name: 'length', type: 'update', oldValue: 5 },
   1208   { object: arr3, name: 'length', type: 'reconfigure' }
   1209 ]);
   1210 Object.deliverChangeRecords(observer2.callback);
   1211 observer2.assertCallbackRecords([
   1212   { object: arr, type: 'splice', index: 2, removed: [, 'd'], addedCount: 0 },
   1213   { object: arr, type: 'splice', index: 1, removed: ['b'], addedCount: 0 },
   1214   { object: arr, type: 'splice', index: 1, removed: [], addedCount: 9 },
   1215   { object: arr2, type: 'splice', index: 1, removed: ['beta'], addedCount: 0 },
   1216   { object: arr3, type: 'splice', index: 0, removed: ['hello',, 'goodbye',,,,], addedCount: 0 },
   1217   { object: arr3, type: 'splice', index: 0, removed: [], addedCount: 1 },
   1218   { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 1 },
   1219   { object: arr3, type: 'splice', index: 1, removed: [,], addedCount: 0 },
   1220   { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 4 },
   1221   { object: arr3, name: '4', type: 'add' },
   1222   { object: arr3, type: 'splice', index: 1, removed: [,,,5], addedCount: 0 }
   1223 ]);
   1224 
   1225 
   1226 // Updating length on large (slow) array
   1227 reset();
   1228 var slow_arr = new Array(1000000000);
   1229 slow_arr[500000000] = 'hello';
   1230 Object.observe(slow_arr, observer.callback);
   1231 var spliceRecords;
   1232 function slowSpliceCallback(records) {
   1233   spliceRecords = records;
   1234 }
   1235 Array.observe(slow_arr, slowSpliceCallback);
   1236 slow_arr.length = 100;
   1237 Object.deliverChangeRecords(observer.callback);
   1238 observer.assertCallbackRecords([
   1239   { object: slow_arr, name: '500000000', type: 'delete', oldValue: 'hello' },
   1240   { object: slow_arr, name: 'length', type: 'update', oldValue: 1000000000 },
   1241 ]);
   1242 Object.deliverChangeRecords(slowSpliceCallback);
   1243 assertEquals(spliceRecords.length, 1);
   1244 // Have to custom assert this splice record because the removed array is huge.
   1245 var splice = spliceRecords[0];
   1246 assertSame(splice.object, slow_arr);
   1247 assertEquals(splice.type, 'splice');
   1248 assertEquals(splice.index, 100);
   1249 assertEquals(splice.addedCount, 0);
   1250 var array_keys = %GetArrayKeys(splice.removed, splice.removed.length);
   1251 assertEquals(array_keys.length, 1);
   1252 assertEquals(array_keys[0], 499999900);
   1253 assertEquals(splice.removed[499999900], 'hello');
   1254 assertEquals(splice.removed.length, 999999900);
   1255 
   1256 
   1257 // Assignments in loops (checking different IC states).
   1258 reset();
   1259 var obj = {};
   1260 Object.observe(obj, observer.callback);
   1261 for (var i = 0; i < 5; i++) {
   1262   obj["a" + i] = i;
   1263 }
   1264 Object.deliverChangeRecords(observer.callback);
   1265 observer.assertCallbackRecords([
   1266   { object: obj, name: "a0", type: "add" },
   1267   { object: obj, name: "a1", type: "add" },
   1268   { object: obj, name: "a2", type: "add" },
   1269   { object: obj, name: "a3", type: "add" },
   1270   { object: obj, name: "a4", type: "add" },
   1271 ]);
   1272 
   1273 reset();
   1274 var obj = {};
   1275 Object.observe(obj, observer.callback);
   1276 for (var i = 0; i < 5; i++) {
   1277   obj[i] = i;
   1278 }
   1279 Object.deliverChangeRecords(observer.callback);
   1280 observer.assertCallbackRecords([
   1281   { object: obj, name: "0", type: "add" },
   1282   { object: obj, name: "1", type: "add" },
   1283   { object: obj, name: "2", type: "add" },
   1284   { object: obj, name: "3", type: "add" },
   1285   { object: obj, name: "4", type: "add" },
   1286 ]);
   1287 
   1288 
   1289 // Adding elements past the end of an array should notify on length for
   1290 // Object.observe and emit "splices" for Array.observe.
   1291 reset();
   1292 var arr = [1, 2, 3];
   1293 Object.observe(arr, observer.callback);
   1294 Array.observe(arr, observer2.callback);
   1295 arr[3] = 10;
   1296 arr[100] = 20;
   1297 Object.defineProperty(arr, '200', {value: 7});
   1298 Object.defineProperty(arr, '400', {get: function(){}});
   1299 arr[50] = 30; // no length change expected
   1300 Object.deliverChangeRecords(observer.callback);
   1301 observer.assertCallbackRecords([
   1302   { object: arr, name: '3', type: 'add' },
   1303   { object: arr, name: 'length', type: 'update', oldValue: 3 },
   1304   { object: arr, name: '100', type: 'add' },
   1305   { object: arr, name: 'length', type: 'update', oldValue: 4 },
   1306   { object: arr, name: '200', type: 'add' },
   1307   { object: arr, name: 'length', type: 'update', oldValue: 101 },
   1308   { object: arr, name: '400', type: 'add' },
   1309   { object: arr, name: 'length', type: 'update', oldValue: 201 },
   1310   { object: arr, name: '50', type: 'add' },
   1311 ]);
   1312 Object.deliverChangeRecords(observer2.callback);
   1313 observer2.assertCallbackRecords([
   1314   { object: arr, type: 'splice', index: 3, removed: [], addedCount: 1 },
   1315   { object: arr, type: 'splice', index: 4, removed: [], addedCount: 97 },
   1316   { object: arr, type: 'splice', index: 101, removed: [], addedCount: 100 },
   1317   { object: arr, type: 'splice', index: 201, removed: [], addedCount: 200 },
   1318   { object: arr, type: 'add', name: '50' },
   1319 ]);
   1320 
   1321 
   1322 // Tests for array methods, first on arrays and then on plain objects
   1323 //
   1324 // === ARRAYS ===
   1325 //
   1326 // Push
   1327 reset();
   1328 var array = [1, 2];
   1329 Object.observe(array, observer.callback);
   1330 Array.observe(array, observer2.callback);
   1331 array.push(3, 4);
   1332 array.push(5);
   1333 Object.deliverChangeRecords(observer.callback);
   1334 observer.assertCallbackRecords([
   1335   { object: array, name: '2', type: 'add' },
   1336   { object: array, name: 'length', type: 'update', oldValue: 2 },
   1337   { object: array, name: '3', type: 'add' },
   1338   { object: array, name: 'length', type: 'update', oldValue: 3 },
   1339   { object: array, name: '4', type: 'add' },
   1340   { object: array, name: 'length', type: 'update', oldValue: 4 },
   1341 ]);
   1342 Object.deliverChangeRecords(observer2.callback);
   1343 observer2.assertCallbackRecords([
   1344   { object: array, type: 'splice', index: 2, removed: [], addedCount: 2 },
   1345   { object: array, type: 'splice', index: 4, removed: [], addedCount: 1 }
   1346 ]);
   1347 
   1348 // Pop
   1349 reset();
   1350 var array = [1, 2];
   1351 Object.observe(array, observer.callback);
   1352 array.pop();
   1353 array.pop();
   1354 Object.deliverChangeRecords(observer.callback);
   1355 observer.assertCallbackRecords([
   1356   { object: array, name: '1', type: 'delete', oldValue: 2 },
   1357   { object: array, name: 'length', type: 'update', oldValue: 2 },
   1358   { object: array, name: '0', type: 'delete', oldValue: 1 },
   1359   { object: array, name: 'length', type: 'update', oldValue: 1 },
   1360 ]);
   1361 
   1362 // Shift
   1363 reset();
   1364 var array = [1, 2];
   1365 Object.observe(array, observer.callback);
   1366 array.shift();
   1367 array.shift();
   1368 Object.deliverChangeRecords(observer.callback);
   1369 observer.assertCallbackRecords([
   1370   { object: array, name: '0', type: 'update', oldValue: 1 },
   1371   { object: array, name: '1', type: 'delete', oldValue: 2 },
   1372   { object: array, name: 'length', type: 'update', oldValue: 2 },
   1373   { object: array, name: '0', type: 'delete', oldValue: 2 },
   1374   { object: array, name: 'length', type: 'update', oldValue: 1 },
   1375 ]);
   1376 
   1377 // Unshift
   1378 reset();
   1379 var array = [1, 2];
   1380 Object.observe(array, observer.callback);
   1381 array.unshift(3, 4);
   1382 Object.deliverChangeRecords(observer.callback);
   1383 observer.assertCallbackRecords([
   1384   { object: array, name: '3', type: 'add' },
   1385   { object: array, name: 'length', type: 'update', oldValue: 2 },
   1386   { object: array, name: '2', type: 'add' },
   1387   { object: array, name: '0', type: 'update', oldValue: 1 },
   1388   { object: array, name: '1', type: 'update', oldValue: 2 },
   1389 ]);
   1390 
   1391 // Splice
   1392 reset();
   1393 var array = [1, 2, 3];
   1394 Object.observe(array, observer.callback);
   1395 array.splice(1, 1, 4, 5);
   1396 Object.deliverChangeRecords(observer.callback);
   1397 observer.assertCallbackRecords([
   1398   { object: array, name: '3', type: 'add' },
   1399   { object: array, name: 'length', type: 'update', oldValue: 3 },
   1400   { object: array, name: '1', type: 'update', oldValue: 2 },
   1401   { object: array, name: '2', type: 'update', oldValue: 3 },
   1402 ]);
   1403 
   1404 // Sort
   1405 reset();
   1406 var array = [3, 2, 1];
   1407 Object.observe(array, observer.callback);
   1408 array.sort();
   1409 assertEquals(1, array[0]);
   1410 assertEquals(2, array[1]);
   1411 assertEquals(3, array[2]);
   1412 Object.deliverChangeRecords(observer.callback);
   1413 observer.assertCallbackRecords([
   1414   { object: array, name: '1', type: 'update', oldValue: 2 },
   1415   { object: array, name: '0', type: 'update', oldValue: 3 },
   1416   { object: array, name: '2', type: 'update', oldValue: 1 },
   1417   { object: array, name: '1', type: 'update', oldValue: 3 },
   1418   { object: array, name: '0', type: 'update', oldValue: 2 },
   1419 ]);
   1420 
   1421 // Splice emitted after Array mutation methods
   1422 function MockArray(initial, observer) {
   1423   for (var i = 0; i < initial.length; i++)
   1424     this[i] = initial[i];
   1425 
   1426   this.length_ = initial.length;
   1427   this.observer = observer;
   1428 }
   1429 MockArray.prototype = {
   1430   set length(length) {
   1431     Object.getNotifier(this).notify({ type: 'lengthChange' });
   1432     this.length_ = length;
   1433     Object.observe(this, this.observer.callback, ['splice']);
   1434   },
   1435   get length() {
   1436     return this.length_;
   1437   }
   1438 }
   1439 
   1440 reset();
   1441 var array = new MockArray([], observer);
   1442 Object.observe(array, observer.callback, ['lengthChange']);
   1443 Array.prototype.push.call(array, 1);
   1444 Object.deliverChangeRecords(observer.callback);
   1445 observer.assertCallbackRecords([
   1446   { object: array, type: 'lengthChange' },
   1447   { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 },
   1448 ]);
   1449 
   1450 reset();
   1451 var array = new MockArray([1], observer);
   1452 Object.observe(array, observer.callback, ['lengthChange']);
   1453 Array.prototype.pop.call(array);
   1454 Object.deliverChangeRecords(observer.callback);
   1455 observer.assertCallbackRecords([
   1456   { object: array, type: 'lengthChange' },
   1457   { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
   1458 ]);
   1459 
   1460 reset();
   1461 var array = new MockArray([1], observer);
   1462 Object.observe(array, observer.callback, ['lengthChange']);
   1463 Array.prototype.shift.call(array);
   1464 Object.deliverChangeRecords(observer.callback);
   1465 observer.assertCallbackRecords([
   1466   { object: array, type: 'lengthChange' },
   1467   { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
   1468 ]);
   1469 
   1470 reset();
   1471 var array = new MockArray([], observer);
   1472 Object.observe(array, observer.callback, ['lengthChange']);
   1473 Array.prototype.unshift.call(array, 1);
   1474 Object.deliverChangeRecords(observer.callback);
   1475 observer.assertCallbackRecords([
   1476   { object: array, type: 'lengthChange' },
   1477   { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 },
   1478 ]);
   1479 
   1480 reset();
   1481 var array = new MockArray([0, 1, 2], observer);
   1482 Object.observe(array, observer.callback, ['lengthChange']);
   1483 Array.prototype.splice.call(array, 1, 1);
   1484 Object.deliverChangeRecords(observer.callback);
   1485 observer.assertCallbackRecords([
   1486   { object: array, type: 'lengthChange' },
   1487   { object: array, type: 'splice', index: 1, removed: [1], addedCount: 0 },
   1488 ]);
   1489 
   1490 //
   1491 // === PLAIN OBJECTS ===
   1492 //
   1493 // Push
   1494 reset()
   1495 var array = {0: 1, 1: 2, length: 2}
   1496 Object.observe(array, observer.callback);
   1497 Array.prototype.push.call(array, 3, 4);
   1498 Object.deliverChangeRecords(observer.callback);
   1499 observer.assertCallbackRecords([
   1500   { object: array, name: '2', type: 'add' },
   1501   { object: array, name: '3', type: 'add' },
   1502   { object: array, name: 'length', type: 'update', oldValue: 2 },
   1503 ]);
   1504 
   1505 // Pop
   1506 reset();
   1507 var array = [1, 2];
   1508 Object.observe(array, observer.callback);
   1509 Array.observe(array, observer2.callback);
   1510 array.pop();
   1511 array.pop();
   1512 array.pop();
   1513 Object.deliverChangeRecords(observer.callback);
   1514 observer.assertCallbackRecords([
   1515   { object: array, name: '1', type: 'delete', oldValue: 2 },
   1516   { object: array, name: 'length', type: 'update', oldValue: 2 },
   1517   { object: array, name: '0', type: 'delete', oldValue: 1 },
   1518   { object: array, name: 'length', type: 'update', oldValue: 1 },
   1519 ]);
   1520 Object.deliverChangeRecords(observer2.callback);
   1521 observer2.assertCallbackRecords([
   1522   { object: array, type: 'splice', index: 1, removed: [2], addedCount: 0 },
   1523   { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }
   1524 ]);
   1525 
   1526 // Shift
   1527 reset();
   1528 var array = [1, 2];
   1529 Object.observe(array, observer.callback);
   1530 Array.observe(array, observer2.callback);
   1531 array.shift();
   1532 array.shift();
   1533 array.shift();
   1534 Object.deliverChangeRecords(observer.callback);
   1535 observer.assertCallbackRecords([
   1536   { object: array, name: '0', type: 'update', oldValue: 1 },
   1537   { object: array, name: '1', type: 'delete', oldValue: 2 },
   1538   { object: array, name: 'length', type: 'update', oldValue: 2 },
   1539   { object: array, name: '0', type: 'delete', oldValue: 2 },
   1540   { object: array, name: 'length', type: 'update', oldValue: 1 },
   1541 ]);
   1542 Object.deliverChangeRecords(observer2.callback);
   1543 observer2.assertCallbackRecords([
   1544   { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
   1545   { object: array, type: 'splice', index: 0, removed: [2], addedCount: 0 }
   1546 ]);
   1547 
   1548 // Unshift
   1549 reset();
   1550 var array = [1, 2];
   1551 Object.observe(array, observer.callback);
   1552 Array.observe(array, observer2.callback);
   1553 array.unshift(3, 4);
   1554 array.unshift(5);
   1555 Object.deliverChangeRecords(observer.callback);
   1556 observer.assertCallbackRecords([
   1557   { object: array, name: '3', type: 'add' },
   1558   { object: array, name: 'length', type: 'update', oldValue: 2 },
   1559   { object: array, name: '2', type: 'add' },
   1560   { object: array, name: '0', type: 'update', oldValue: 1 },
   1561   { object: array, name: '1', type: 'update', oldValue: 2 },
   1562   { object: array, name: '4', type: 'add' },
   1563   { object: array, name: 'length', type: 'update', oldValue: 4 },
   1564   { object: array, name: '3', type: 'update', oldValue: 2 },
   1565   { object: array, name: '2', type: 'update', oldValue: 1 },
   1566   { object: array, name: '1', type: 'update', oldValue: 4 },
   1567   { object: array, name: '0', type: 'update', oldValue: 3 },
   1568 ]);
   1569 Object.deliverChangeRecords(observer2.callback);
   1570 observer2.assertCallbackRecords([
   1571   { object: array, type: 'splice', index: 0, removed: [], addedCount: 2 },
   1572   { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }
   1573 ]);
   1574 
   1575 // Splice
   1576 reset();
   1577 var array = [1, 2, 3];
   1578 Object.observe(array, observer.callback);
   1579 Array.observe(array, observer2.callback);
   1580 array.splice(1, 0, 4, 5); // 1 4 5 2 3
   1581 array.splice(0, 2); // 5 2 3
   1582 array.splice(1, 2, 6, 7); // 5 6 7
   1583 array.splice(2, 0);
   1584 Object.deliverChangeRecords(observer.callback);
   1585 observer.assertCallbackRecords([
   1586   { object: array, name: '4', type: 'add' },
   1587   { object: array, name: 'length', type: 'update', oldValue: 3 },
   1588   { object: array, name: '3', type: 'add' },
   1589   { object: array, name: '1', type: 'update', oldValue: 2 },
   1590   { object: array, name: '2', type: 'update', oldValue: 3 },
   1591 
   1592   { object: array, name: '0', type: 'update', oldValue: 1 },
   1593   { object: array, name: '1', type: 'update', oldValue: 4 },
   1594   { object: array, name: '2', type: 'update', oldValue: 5 },
   1595   { object: array, name: '4', type: 'delete', oldValue: 3 },
   1596   { object: array, name: '3', type: 'delete', oldValue: 2 },
   1597   { object: array, name: 'length', type: 'update', oldValue: 5 },
   1598 
   1599   { object: array, name: '1', type: 'update', oldValue: 2 },
   1600   { object: array, name: '2', type: 'update', oldValue: 3 },
   1601 ]);
   1602 Object.deliverChangeRecords(observer2.callback);
   1603 observer2.assertCallbackRecords([
   1604   { object: array, type: 'splice', index: 1, removed: [], addedCount: 2 },
   1605   { object: array, type: 'splice', index: 0, removed: [1, 4], addedCount: 0 },
   1606   { object: array, type: 'splice', index: 1, removed: [2, 3], addedCount: 2 },
   1607 ]);
   1608 
   1609 // Exercise StoreIC_ArrayLength
   1610 reset();
   1611 var dummy = {};
   1612 Object.observe(dummy, observer.callback);
   1613 Object.unobserve(dummy, observer.callback);
   1614 var array = [0];
   1615 Object.observe(array, observer.callback);
   1616 array.splice(0, 1);
   1617 Object.deliverChangeRecords(observer.callback);
   1618 observer.assertCallbackRecords([
   1619   { object: array, name: '0', type: 'delete', oldValue: 0 },
   1620   { object: array, name: 'length', type: 'update', oldValue: 1},
   1621 ]);
   1622 
   1623 
   1624 // __proto__
   1625 reset();
   1626 var obj = {};
   1627 Object.observe(obj, observer.callback);
   1628 var p = {foo: 'yes'};
   1629 var q = {bar: 'no'};
   1630 obj.__proto__ = p;
   1631 obj.__proto__ = p;  // ignored
   1632 obj.__proto__ = null;
   1633 obj.__proto__ = q;  // the __proto__ accessor is gone
   1634 // TODO(adamk): Add tests for objects with hidden prototypes
   1635 // once we support observing the global object.
   1636 Object.deliverChangeRecords(observer.callback);
   1637 observer.assertCallbackRecords([
   1638   { object: obj, name: '__proto__', type: 'setPrototype',
   1639     oldValue: Object.prototype },
   1640   { object: obj, name: '__proto__', type: 'setPrototype', oldValue: p },
   1641   { object: obj, name: '__proto__', type: 'add' },
   1642 ]);
   1643 
   1644 
   1645 // Function.prototype
   1646 reset();
   1647 var fun = function(){};
   1648 Object.observe(fun, observer.callback);
   1649 var myproto = {foo: 'bar'};
   1650 fun.prototype = myproto;
   1651 fun.prototype = 7;
   1652 fun.prototype = 7;  // ignored
   1653 Object.defineProperty(fun, 'prototype', {value: 8});
   1654 Object.deliverChangeRecords(observer.callback);
   1655 observer.assertRecordCount(3);
   1656 // Manually examine the first record in order to test
   1657 // lazy creation of oldValue
   1658 assertSame(fun, observer.records[0].object);
   1659 assertEquals('prototype', observer.records[0].name);
   1660 assertEquals('update', observer.records[0].type);
   1661 // The only existing reference to the oldValue object is in this
   1662 // record, so to test that lazy creation happened correctly
   1663 // we compare its constructor to our function (one of the invariants
   1664 // ensured when creating an object via AllocateFunctionPrototype).
   1665 assertSame(fun, observer.records[0].oldValue.constructor);
   1666 observer.records.splice(0, 1);
   1667 observer.assertCallbackRecords([
   1668   { object: fun, name: 'prototype', type: 'update', oldValue: myproto },
   1669   { object: fun, name: 'prototype', type: 'update', oldValue: 7 },
   1670 ]);
   1671 
   1672 // Function.prototype should not be observable except on the object itself
   1673 reset();
   1674 var fun = function(){};
   1675 var obj = { __proto__: fun };
   1676 Object.observe(obj, observer.callback);
   1677 obj.prototype = 7;
   1678 Object.deliverChangeRecords(observer.callback);
   1679 observer.assertNotCalled();
   1680 
   1681 
   1682 // Check that changes in observation status are detected in all IC states and
   1683 // in optimized code, especially in cases usually using fast elements.
   1684 var mutation = [
   1685   "a[i] = v",
   1686   "a[i] ? ++a[i] : a[i] = v",
   1687   "a[i] ? a[i]++ : a[i] = v",
   1688   "a[i] ? a[i] += 1 : a[i] = v",
   1689   "a[i] ? a[i] -= -1 : a[i] = v",
   1690 ];
   1691 
   1692 var props = [1, "1", "a"];
   1693 
   1694 function TestFastElements(prop, mutation, prepopulate, polymorphic, optimize) {
   1695   var setElement = eval(
   1696     "(function setElement(a, i, v) { " + mutation + "; " +
   1697     "/* " + [].join.call(arguments, " ") + " */" +
   1698     "})"
   1699   );
   1700   print("TestFastElements:", setElement);
   1701 
   1702   var arr = prepopulate ? [1, 2, 3, 4, 5] : [0];
   1703   if (prepopulate) arr[prop] = 2;  // for non-element case
   1704   setElement(arr, prop, 3);
   1705   setElement(arr, prop, 4);
   1706   if (polymorphic) setElement(["M", "i", "l", "n", "e", "r"], 0, "m");
   1707   if (optimize) %OptimizeFunctionOnNextCall(setElement);
   1708   setElement(arr, prop, 5);
   1709 
   1710   reset();
   1711   Object.observe(arr, observer.callback);
   1712   setElement(arr, prop, 989898);
   1713   Object.deliverChangeRecords(observer.callback);
   1714   observer.assertCallbackRecords([
   1715     { object: arr, name: "" + prop, type: 'update', oldValue: 5 }
   1716   ]);
   1717 }
   1718 
   1719 for (var b1 = 0; b1 < 2; ++b1)
   1720   for (var b2 = 0; b2 < 2; ++b2)
   1721     for (var b3 = 0; b3 < 2; ++b3)
   1722       for (var i in props)
   1723         for (var j in mutation)
   1724           TestFastElements(props[i], mutation[j], b1 != 0, b2 != 0, b3 != 0);
   1725 
   1726 
   1727 var mutation = [
   1728   "a.length = v",
   1729   "a.length += newSize - oldSize",
   1730   "a.length -= oldSize - newSize",
   1731 ];
   1732 
   1733 var mutationByIncr = [
   1734   "++a.length",
   1735   "a.length++",
   1736 ];
   1737 
   1738 function TestFastElementsLength(
   1739   mutation, polymorphic, optimize, oldSize, newSize) {
   1740   var setLength = eval(
   1741     "(function setLength(a, v) { " + mutation + "; " +
   1742     "/* " + [].join.call(arguments, " ") + " */"
   1743     + "})"
   1744   );
   1745   print("TestFastElementsLength:", setLength);
   1746 
   1747   function array(n) {
   1748     var arr = new Array(n);
   1749     for (var i = 0; i < n; ++i) arr[i] = i;
   1750     return arr;
   1751   }
   1752 
   1753   setLength(array(oldSize), newSize);
   1754   setLength(array(oldSize), newSize);
   1755   if (polymorphic) setLength(array(oldSize).map(isNaN), newSize);
   1756   if (optimize) %OptimizeFunctionOnNextCall(setLength);
   1757   setLength(array(oldSize), newSize);
   1758 
   1759   reset();
   1760   var arr = array(oldSize);
   1761   Object.observe(arr, observer.callback);
   1762   setLength(arr, newSize);
   1763   Object.deliverChangeRecords(observer.callback);
   1764   if (oldSize === newSize) {
   1765     observer.assertNotCalled();
   1766   } else {
   1767     var count = oldSize > newSize ? oldSize - newSize : 0;
   1768     observer.assertRecordCount(count + 1);
   1769     var lengthRecord = observer.records[count];
   1770     assertSame(arr, lengthRecord.object);
   1771     assertEquals('length', lengthRecord.name);
   1772     assertEquals('update', lengthRecord.type);
   1773     assertSame(oldSize, lengthRecord.oldValue);
   1774   }
   1775 }
   1776 
   1777 for (var b1 = 0; b1 < 2; ++b1)
   1778   for (var b2 = 0; b2 < 2; ++b2)
   1779     for (var n1 = 0; n1 < 3; ++n1)
   1780       for (var n2 = 0; n2 < 3; ++n2)
   1781         for (var i in mutation)
   1782           TestFastElementsLength(mutation[i], b1 != 0, b2 != 0, 20*n1, 20*n2);
   1783 
   1784 for (var b1 = 0; b1 < 2; ++b1)
   1785   for (var b2 = 0; b2 < 2; ++b2)
   1786     for (var n = 0; n < 3; ++n)
   1787       for (var i in mutationByIncr)
   1788         TestFastElementsLength(mutationByIncr[i], b1 != 0, b2 != 0, 7*n, 7*n+1);
   1789