Home | History | Annotate | Download | only in es7
      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 }
   1147 
   1148 for (var i in objects) for (var j in properties) {
   1149   var obj = objects[i];
   1150   var prop = properties[j];
   1151   if (blacklisted(obj, prop)) continue;
   1152   var desc = Object.getOwnPropertyDescriptor(obj, prop);
   1153   print("***", typeof obj, stringifyNoThrow(obj), prop);
   1154   if (!desc || desc.configurable)
   1155     TestObserveConfigurable(obj, prop);
   1156   else if (desc.writable)
   1157     TestObserveNonConfigurable(obj, prop, desc);
   1158 }
   1159 
   1160 
   1161 // Observing array length (including truncation)
   1162 reset();
   1163 var arr = ['a', 'b', 'c', 'd'];
   1164 var arr2 = ['alpha', 'beta'];
   1165 var arr3 = ['hello'];
   1166 arr3[2] = 'goodbye';
   1167 arr3.length = 6;
   1168 Object.defineProperty(arr, '0', {configurable: false});
   1169 Object.defineProperty(arr, '2', {get: function(){}});
   1170 Object.defineProperty(arr2, '0', {get: function(){}, configurable: false});
   1171 Object.observe(arr, observer.callback);
   1172 Array.observe(arr, observer2.callback);
   1173 Object.observe(arr2, observer.callback);
   1174 Array.observe(arr2, observer2.callback);
   1175 Object.observe(arr3, observer.callback);
   1176 Array.observe(arr3, observer2.callback);
   1177 arr.length = 2;
   1178 arr.length = 0;
   1179 arr.length = 10;
   1180 Object.defineProperty(arr, 'length', {writable: false});
   1181 arr2.length = 0;
   1182 arr2.length = 1; // no change expected
   1183 Object.defineProperty(arr2, 'length', {value: 1, writable: false});
   1184 arr3.length = 0;
   1185 ++arr3.length;
   1186 arr3.length++;
   1187 arr3.length /= 2;
   1188 Object.defineProperty(arr3, 'length', {value: 5});
   1189 arr3[4] = 5;
   1190 Object.defineProperty(arr3, 'length', {value: 1, writable: false});
   1191 Object.deliverChangeRecords(observer.callback);
   1192 observer.assertCallbackRecords([
   1193   { object: arr, name: '3', type: 'delete', oldValue: 'd' },
   1194   { object: arr, name: '2', type: 'delete' },
   1195   { object: arr, name: 'length', type: 'update', oldValue: 4 },
   1196   { object: arr, name: '1', type: 'delete', oldValue: 'b' },
   1197   { object: arr, name: 'length', type: 'update', oldValue: 2 },
   1198   { object: arr, name: 'length', type: 'update', oldValue: 1 },
   1199   { object: arr, name: 'length', type: 'reconfigure' },
   1200   { object: arr2, name: '1', type: 'delete', oldValue: 'beta' },
   1201   { object: arr2, name: 'length', type: 'update', oldValue: 2 },
   1202   { object: arr2, name: 'length', type: 'reconfigure' },
   1203   { object: arr3, name: '2', type: 'delete', oldValue: 'goodbye' },
   1204   { object: arr3, name: '0', type: 'delete', oldValue: 'hello' },
   1205   { object: arr3, name: 'length', type: 'update', oldValue: 6 },
   1206   { object: arr3, name: 'length', type: 'update', oldValue: 0 },
   1207   { object: arr3, name: 'length', type: 'update', oldValue: 1 },
   1208   { object: arr3, name: 'length', type: 'update', oldValue: 2 },
   1209   { object: arr3, name: 'length', type: 'update', oldValue: 1 },
   1210   { object: arr3, name: '4', type: 'add' },
   1211   { object: arr3, name: '4', type: 'delete', oldValue: 5 },
   1212   // TODO(rafaelw): It breaks spec compliance to get two records here.
   1213   // When the TODO in v8natives.js::DefineArrayProperty is addressed
   1214   // which prevents DefineProperty from over-writing the magic length
   1215   // property, these will collapse into a single record.
   1216   { object: arr3, name: 'length', type: 'update', oldValue: 5 },
   1217   { object: arr3, name: 'length', type: 'reconfigure' }
   1218 ]);
   1219 Object.deliverChangeRecords(observer2.callback);
   1220 observer2.assertCallbackRecords([
   1221   { object: arr, type: 'splice', index: 2, removed: [, 'd'], addedCount: 0 },
   1222   { object: arr, type: 'splice', index: 1, removed: ['b'], addedCount: 0 },
   1223   { object: arr, type: 'splice', index: 1, removed: [], addedCount: 9 },
   1224   { object: arr2, type: 'splice', index: 1, removed: ['beta'], addedCount: 0 },
   1225   { object: arr3, type: 'splice', index: 0, removed: ['hello',, 'goodbye',,,,], addedCount: 0 },
   1226   { object: arr3, type: 'splice', index: 0, removed: [], addedCount: 1 },
   1227   { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 1 },
   1228   { object: arr3, type: 'splice', index: 1, removed: [,], addedCount: 0 },
   1229   { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 4 },
   1230   { object: arr3, name: '4', type: 'add' },
   1231   { object: arr3, type: 'splice', index: 1, removed: [,,,5], addedCount: 0 }
   1232 ]);
   1233 
   1234 
   1235 // Updating length on large (slow) array
   1236 reset();
   1237 var slow_arr = %NormalizeElements([]);
   1238 slow_arr[500000000] = 'hello';
   1239 slow_arr.length = 1000000000;
   1240 Object.observe(slow_arr, observer.callback);
   1241 var spliceRecords;
   1242 function slowSpliceCallback(records) {
   1243   spliceRecords = records;
   1244 }
   1245 Array.observe(slow_arr, slowSpliceCallback);
   1246 slow_arr.length = 100;
   1247 Object.deliverChangeRecords(observer.callback);
   1248 observer.assertCallbackRecords([
   1249   { object: slow_arr, name: '500000000', type: 'delete', oldValue: 'hello' },
   1250   { object: slow_arr, name: 'length', type: 'update', oldValue: 1000000000 },
   1251 ]);
   1252 Object.deliverChangeRecords(slowSpliceCallback);
   1253 assertEquals(spliceRecords.length, 1);
   1254 // Have to custom assert this splice record because the removed array is huge.
   1255 var splice = spliceRecords[0];
   1256 assertSame(splice.object, slow_arr);
   1257 assertEquals(splice.type, 'splice');
   1258 assertEquals(splice.index, 100);
   1259 assertEquals(splice.addedCount, 0);
   1260 var array_keys = %GetArrayKeys(splice.removed, splice.removed.length);
   1261 assertEquals(array_keys.length, 1);
   1262 assertEquals(array_keys[0], 499999900);
   1263 assertEquals(splice.removed[499999900], 'hello');
   1264 assertEquals(splice.removed.length, 999999900);
   1265 
   1266 
   1267 // Assignments in loops (checking different IC states).
   1268 reset();
   1269 var obj = {};
   1270 Object.observe(obj, observer.callback);
   1271 for (var i = 0; i < 5; i++) {
   1272   obj["a" + i] = i;
   1273 }
   1274 Object.deliverChangeRecords(observer.callback);
   1275 observer.assertCallbackRecords([
   1276   { object: obj, name: "a0", type: "add" },
   1277   { object: obj, name: "a1", type: "add" },
   1278   { object: obj, name: "a2", type: "add" },
   1279   { object: obj, name: "a3", type: "add" },
   1280   { object: obj, name: "a4", type: "add" },
   1281 ]);
   1282 
   1283 reset();
   1284 var obj = {};
   1285 Object.observe(obj, observer.callback);
   1286 for (var i = 0; i < 5; i++) {
   1287   obj[i] = i;
   1288 }
   1289 Object.deliverChangeRecords(observer.callback);
   1290 observer.assertCallbackRecords([
   1291   { object: obj, name: "0", type: "add" },
   1292   { object: obj, name: "1", type: "add" },
   1293   { object: obj, name: "2", type: "add" },
   1294   { object: obj, name: "3", type: "add" },
   1295   { object: obj, name: "4", type: "add" },
   1296 ]);
   1297 
   1298 
   1299 // Adding elements past the end of an array should notify on length for
   1300 // Object.observe and emit "splices" for Array.observe.
   1301 reset();
   1302 var arr = [1, 2, 3];
   1303 Object.observe(arr, observer.callback);
   1304 Array.observe(arr, observer2.callback);
   1305 arr[3] = 10;
   1306 arr[100] = 20;
   1307 Object.defineProperty(arr, '200', {value: 7});
   1308 Object.defineProperty(arr, '400', {get: function(){}});
   1309 arr[50] = 30; // no length change expected
   1310 Object.deliverChangeRecords(observer.callback);
   1311 observer.assertCallbackRecords([
   1312   { object: arr, name: '3', type: 'add' },
   1313   { object: arr, name: 'length', type: 'update', oldValue: 3 },
   1314   { object: arr, name: '100', type: 'add' },
   1315   { object: arr, name: 'length', type: 'update', oldValue: 4 },
   1316   { object: arr, name: '200', type: 'add' },
   1317   { object: arr, name: 'length', type: 'update', oldValue: 101 },
   1318   { object: arr, name: '400', type: 'add' },
   1319   { object: arr, name: 'length', type: 'update', oldValue: 201 },
   1320   { object: arr, name: '50', type: 'add' },
   1321 ]);
   1322 Object.deliverChangeRecords(observer2.callback);
   1323 observer2.assertCallbackRecords([
   1324   { object: arr, type: 'splice', index: 3, removed: [], addedCount: 1 },
   1325   { object: arr, type: 'splice', index: 4, removed: [], addedCount: 97 },
   1326   { object: arr, type: 'splice', index: 101, removed: [], addedCount: 100 },
   1327   { object: arr, type: 'splice', index: 201, removed: [], addedCount: 200 },
   1328   { object: arr, type: 'add', name: '50' },
   1329 ]);
   1330 
   1331 
   1332 // Tests for array methods, first on arrays and then on plain objects
   1333 //
   1334 // === ARRAYS ===
   1335 //
   1336 // Push
   1337 reset();
   1338 var array = [1, 2];
   1339 Object.observe(array, observer.callback);
   1340 Array.observe(array, observer2.callback);
   1341 array.push(3, 4);
   1342 array.push(5);
   1343 Object.deliverChangeRecords(observer.callback);
   1344 observer.assertCallbackRecords([
   1345   { object: array, name: '2', type: 'add' },
   1346   { object: array, name: 'length', type: 'update', oldValue: 2 },
   1347   { object: array, name: '3', type: 'add' },
   1348   { object: array, name: 'length', type: 'update', oldValue: 3 },
   1349   { object: array, name: '4', type: 'add' },
   1350   { object: array, name: 'length', type: 'update', oldValue: 4 },
   1351 ]);
   1352 Object.deliverChangeRecords(observer2.callback);
   1353 observer2.assertCallbackRecords([
   1354   { object: array, type: 'splice', index: 2, removed: [], addedCount: 2 },
   1355   { object: array, type: 'splice', index: 4, removed: [], addedCount: 1 }
   1356 ]);
   1357 
   1358 // Pop
   1359 reset();
   1360 var array = [1, 2];
   1361 Object.observe(array, observer.callback);
   1362 array.pop();
   1363 array.pop();
   1364 Object.deliverChangeRecords(observer.callback);
   1365 observer.assertCallbackRecords([
   1366   { object: array, name: '1', type: 'delete', oldValue: 2 },
   1367   { object: array, name: 'length', type: 'update', oldValue: 2 },
   1368   { object: array, name: '0', type: 'delete', oldValue: 1 },
   1369   { object: array, name: 'length', type: 'update', oldValue: 1 },
   1370 ]);
   1371 
   1372 // Shift
   1373 reset();
   1374 var array = [1, 2];
   1375 Object.observe(array, observer.callback);
   1376 array.shift();
   1377 array.shift();
   1378 Object.deliverChangeRecords(observer.callback);
   1379 observer.assertCallbackRecords([
   1380   { object: array, name: '0', type: 'update', oldValue: 1 },
   1381   { object: array, name: '1', type: 'delete', oldValue: 2 },
   1382   { object: array, name: 'length', type: 'update', oldValue: 2 },
   1383   { object: array, name: '0', type: 'delete', oldValue: 2 },
   1384   { object: array, name: 'length', type: 'update', oldValue: 1 },
   1385 ]);
   1386 
   1387 // Unshift
   1388 reset();
   1389 var array = [1, 2];
   1390 Object.observe(array, observer.callback);
   1391 array.unshift(3, 4);
   1392 Object.deliverChangeRecords(observer.callback);
   1393 observer.assertCallbackRecords([
   1394   { object: array, name: '3', type: 'add' },
   1395   { object: array, name: 'length', type: 'update', oldValue: 2 },
   1396   { object: array, name: '2', type: 'add' },
   1397   { object: array, name: '0', type: 'update', oldValue: 1 },
   1398   { object: array, name: '1', type: 'update', oldValue: 2 },
   1399 ]);
   1400 
   1401 // Splice
   1402 reset();
   1403 var array = [1, 2, 3];
   1404 Object.observe(array, observer.callback);
   1405 array.splice(1, 1, 4, 5);
   1406 Object.deliverChangeRecords(observer.callback);
   1407 observer.assertCallbackRecords([
   1408   { object: array, name: '3', type: 'add' },
   1409   { object: array, name: 'length', type: 'update', oldValue: 3 },
   1410   { object: array, name: '1', type: 'update', oldValue: 2 },
   1411   { object: array, name: '2', type: 'update', oldValue: 3 },
   1412 ]);
   1413 
   1414 // Sort
   1415 reset();
   1416 var array = [3, 2, 1];
   1417 Object.observe(array, observer.callback);
   1418 array.sort();
   1419 assertEquals(1, array[0]);
   1420 assertEquals(2, array[1]);
   1421 assertEquals(3, array[2]);
   1422 Object.deliverChangeRecords(observer.callback);
   1423 observer.assertCallbackRecords([
   1424   { object: array, name: '1', type: 'update', oldValue: 2 },
   1425   { object: array, name: '0', type: 'update', oldValue: 3 },
   1426   { object: array, name: '2', type: 'update', oldValue: 1 },
   1427   { object: array, name: '1', type: 'update', oldValue: 3 },
   1428   { object: array, name: '0', type: 'update', oldValue: 2 },
   1429 ]);
   1430 
   1431 // Splice emitted after Array mutation methods
   1432 function MockArray(initial, observer) {
   1433   for (var i = 0; i < initial.length; i++)
   1434     this[i] = initial[i];
   1435 
   1436   this.length_ = initial.length;
   1437   this.observer = observer;
   1438 }
   1439 MockArray.prototype = {
   1440   set length(length) {
   1441     Object.getNotifier(this).notify({ type: 'lengthChange' });
   1442     this.length_ = length;
   1443     Object.observe(this, this.observer.callback, ['splice']);
   1444   },
   1445   get length() {
   1446     return this.length_;
   1447   }
   1448 }
   1449 
   1450 reset();
   1451 var array = new MockArray([], observer);
   1452 Object.observe(array, observer.callback, ['lengthChange']);
   1453 Array.prototype.push.call(array, 1);
   1454 Object.deliverChangeRecords(observer.callback);
   1455 observer.assertCallbackRecords([
   1456   { object: array, type: 'lengthChange' },
   1457   { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 },
   1458 ]);
   1459 
   1460 reset();
   1461 var array = new MockArray([1], observer);
   1462 Object.observe(array, observer.callback, ['lengthChange']);
   1463 Array.prototype.pop.call(array);
   1464 Object.deliverChangeRecords(observer.callback);
   1465 observer.assertCallbackRecords([
   1466   { object: array, type: 'lengthChange' },
   1467   { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
   1468 ]);
   1469 
   1470 reset();
   1471 var array = new MockArray([1], observer);
   1472 Object.observe(array, observer.callback, ['lengthChange']);
   1473 Array.prototype.shift.call(array);
   1474 Object.deliverChangeRecords(observer.callback);
   1475 observer.assertCallbackRecords([
   1476   { object: array, type: 'lengthChange' },
   1477   { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
   1478 ]);
   1479 
   1480 reset();
   1481 var array = new MockArray([], observer);
   1482 Object.observe(array, observer.callback, ['lengthChange']);
   1483 Array.prototype.unshift.call(array, 1);
   1484 Object.deliverChangeRecords(observer.callback);
   1485 observer.assertCallbackRecords([
   1486   { object: array, type: 'lengthChange' },
   1487   { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 },
   1488 ]);
   1489 
   1490 reset();
   1491 var array = new MockArray([0, 1, 2], observer);
   1492 Object.observe(array, observer.callback, ['lengthChange']);
   1493 Array.prototype.splice.call(array, 1, 1);
   1494 Object.deliverChangeRecords(observer.callback);
   1495 observer.assertCallbackRecords([
   1496   { object: array, type: 'lengthChange' },
   1497   { object: array, type: 'splice', index: 1, removed: [1], addedCount: 0 },
   1498 ]);
   1499 
   1500 //
   1501 // === PLAIN OBJECTS ===
   1502 //
   1503 // Push
   1504 reset()
   1505 var array = {0: 1, 1: 2, length: 2}
   1506 Object.observe(array, observer.callback);
   1507 Array.prototype.push.call(array, 3, 4);
   1508 Object.deliverChangeRecords(observer.callback);
   1509 observer.assertCallbackRecords([
   1510   { object: array, name: '2', type: 'add' },
   1511   { object: array, name: '3', type: 'add' },
   1512   { object: array, name: 'length', type: 'update', oldValue: 2 },
   1513 ]);
   1514 
   1515 // Pop
   1516 reset();
   1517 var array = [1, 2];
   1518 Object.observe(array, observer.callback);
   1519 Array.observe(array, observer2.callback);
   1520 array.pop();
   1521 array.pop();
   1522 array.pop();
   1523 Object.deliverChangeRecords(observer.callback);
   1524 observer.assertCallbackRecords([
   1525   { object: array, name: '1', type: 'delete', oldValue: 2 },
   1526   { object: array, name: 'length', type: 'update', oldValue: 2 },
   1527   { object: array, name: '0', type: 'delete', oldValue: 1 },
   1528   { object: array, name: 'length', type: 'update', oldValue: 1 },
   1529 ]);
   1530 Object.deliverChangeRecords(observer2.callback);
   1531 observer2.assertCallbackRecords([
   1532   { object: array, type: 'splice', index: 1, removed: [2], addedCount: 0 },
   1533   { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }
   1534 ]);
   1535 
   1536 // Shift
   1537 reset();
   1538 var array = [1, 2];
   1539 Object.observe(array, observer.callback);
   1540 Array.observe(array, observer2.callback);
   1541 array.shift();
   1542 array.shift();
   1543 array.shift();
   1544 Object.deliverChangeRecords(observer.callback);
   1545 observer.assertCallbackRecords([
   1546   { object: array, name: '0', type: 'update', oldValue: 1 },
   1547   { object: array, name: '1', type: 'delete', oldValue: 2 },
   1548   { object: array, name: 'length', type: 'update', oldValue: 2 },
   1549   { object: array, name: '0', type: 'delete', oldValue: 2 },
   1550   { object: array, name: 'length', type: 'update', oldValue: 1 },
   1551 ]);
   1552 Object.deliverChangeRecords(observer2.callback);
   1553 observer2.assertCallbackRecords([
   1554   { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
   1555   { object: array, type: 'splice', index: 0, removed: [2], addedCount: 0 }
   1556 ]);
   1557 
   1558 // Unshift
   1559 reset();
   1560 var array = [1, 2];
   1561 Object.observe(array, observer.callback);
   1562 Array.observe(array, observer2.callback);
   1563 array.unshift(3, 4);
   1564 array.unshift(5);
   1565 Object.deliverChangeRecords(observer.callback);
   1566 observer.assertCallbackRecords([
   1567   { object: array, name: '3', type: 'add' },
   1568   { object: array, name: 'length', type: 'update', oldValue: 2 },
   1569   { object: array, name: '2', type: 'add' },
   1570   { object: array, name: '0', type: 'update', oldValue: 1 },
   1571   { object: array, name: '1', type: 'update', oldValue: 2 },
   1572   { object: array, name: '4', type: 'add' },
   1573   { object: array, name: 'length', type: 'update', oldValue: 4 },
   1574   { object: array, name: '3', type: 'update', oldValue: 2 },
   1575   { object: array, name: '2', type: 'update', oldValue: 1 },
   1576   { object: array, name: '1', type: 'update', oldValue: 4 },
   1577   { object: array, name: '0', type: 'update', oldValue: 3 },
   1578 ]);
   1579 Object.deliverChangeRecords(observer2.callback);
   1580 observer2.assertCallbackRecords([
   1581   { object: array, type: 'splice', index: 0, removed: [], addedCount: 2 },
   1582   { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }
   1583 ]);
   1584 
   1585 // Splice
   1586 reset();
   1587 var array = [1, 2, 3];
   1588 Object.observe(array, observer.callback);
   1589 Array.observe(array, observer2.callback);
   1590 array.splice(1, 0, 4, 5); // 1 4 5 2 3
   1591 array.splice(0, 2); // 5 2 3
   1592 array.splice(1, 2, 6, 7); // 5 6 7
   1593 array.splice(2, 0);
   1594 Object.deliverChangeRecords(observer.callback);
   1595 observer.assertCallbackRecords([
   1596   { object: array, name: '4', type: 'add' },
   1597   { object: array, name: 'length', type: 'update', oldValue: 3 },
   1598   { object: array, name: '3', type: 'add' },
   1599   { object: array, name: '1', type: 'update', oldValue: 2 },
   1600   { object: array, name: '2', type: 'update', oldValue: 3 },
   1601 
   1602   { object: array, name: '0', type: 'update', oldValue: 1 },
   1603   { object: array, name: '1', type: 'update', oldValue: 4 },
   1604   { object: array, name: '2', type: 'update', oldValue: 5 },
   1605   { object: array, name: '4', type: 'delete', oldValue: 3 },
   1606   { object: array, name: '3', type: 'delete', oldValue: 2 },
   1607   { object: array, name: 'length', type: 'update', oldValue: 5 },
   1608 
   1609   { object: array, name: '1', type: 'update', oldValue: 2 },
   1610   { object: array, name: '2', type: 'update', oldValue: 3 },
   1611 ]);
   1612 Object.deliverChangeRecords(observer2.callback);
   1613 observer2.assertCallbackRecords([
   1614   { object: array, type: 'splice', index: 1, removed: [], addedCount: 2 },
   1615   { object: array, type: 'splice', index: 0, removed: [1, 4], addedCount: 0 },
   1616   { object: array, type: 'splice', index: 1, removed: [2, 3], addedCount: 2 },
   1617 ]);
   1618 
   1619 // Exercise StoreIC_ArrayLength
   1620 reset();
   1621 var dummy = {};
   1622 Object.observe(dummy, observer.callback);
   1623 Object.unobserve(dummy, observer.callback);
   1624 var array = [0];
   1625 Object.observe(array, observer.callback);
   1626 array.splice(0, 1);
   1627 Object.deliverChangeRecords(observer.callback);
   1628 observer.assertCallbackRecords([
   1629   { object: array, name: '0', type: 'delete', oldValue: 0 },
   1630   { object: array, name: 'length', type: 'update', oldValue: 1},
   1631 ]);
   1632 
   1633 
   1634 // __proto__
   1635 reset();
   1636 var obj = {};
   1637 Object.observe(obj, observer.callback);
   1638 var p = {foo: 'yes'};
   1639 var q = {bar: 'no'};
   1640 obj.__proto__ = p;
   1641 obj.__proto__ = p;  // ignored
   1642 obj.__proto__ = null;
   1643 obj.__proto__ = q;  // the __proto__ accessor is gone
   1644 // TODO(adamk): Add tests for objects with hidden prototypes
   1645 // once we support observing the global object.
   1646 Object.deliverChangeRecords(observer.callback);
   1647 observer.assertCallbackRecords([
   1648   { object: obj, name: '__proto__', type: 'setPrototype',
   1649     oldValue: Object.prototype },
   1650   { object: obj, name: '__proto__', type: 'setPrototype', oldValue: p },
   1651   { object: obj, name: '__proto__', type: 'add' },
   1652 ]);
   1653 
   1654 
   1655 // Function.prototype
   1656 reset();
   1657 var fun = function(){};
   1658 Object.observe(fun, observer.callback);
   1659 var myproto = {foo: 'bar'};
   1660 fun.prototype = myproto;
   1661 fun.prototype = 7;
   1662 fun.prototype = 7;  // ignored
   1663 Object.defineProperty(fun, 'prototype', {value: 8});
   1664 Object.deliverChangeRecords(observer.callback);
   1665 observer.assertRecordCount(3);
   1666 // Manually examine the first record in order to test
   1667 // lazy creation of oldValue
   1668 assertSame(fun, observer.records[0].object);
   1669 assertEquals('prototype', observer.records[0].name);
   1670 assertEquals('update', observer.records[0].type);
   1671 // The only existing reference to the oldValue object is in this
   1672 // record, so to test that lazy creation happened correctly
   1673 // we compare its constructor to our function (one of the invariants
   1674 // ensured when creating an object via AllocateFunctionPrototype).
   1675 assertSame(fun, observer.records[0].oldValue.constructor);
   1676 observer.records.splice(0, 1);
   1677 observer.assertCallbackRecords([
   1678   { object: fun, name: 'prototype', type: 'update', oldValue: myproto },
   1679   { object: fun, name: 'prototype', type: 'update', oldValue: 7 },
   1680 ]);
   1681 
   1682 // Function.prototype should not be observable except on the object itself
   1683 reset();
   1684 var fun = function(){};
   1685 var obj = { __proto__: fun };
   1686 Object.observe(obj, observer.callback);
   1687 obj.prototype = 7;
   1688 Object.deliverChangeRecords(observer.callback);
   1689 observer.assertRecordCount(1);
   1690 observer.assertCallbackRecords([
   1691   { object: obj, name: 'prototype', type: 'add' },
   1692 ]);
   1693 
   1694 // Check that changes in observation status are detected in all IC states and
   1695 // in optimized code, especially in cases usually using fast elements.
   1696 var mutation = [
   1697   "a[i] = v",
   1698   "a[i] ? ++a[i] : a[i] = v",
   1699   "a[i] ? a[i]++ : a[i] = v",
   1700   "a[i] ? a[i] += 1 : a[i] = v",
   1701   "a[i] ? a[i] -= -1 : a[i] = v",
   1702 ];
   1703 
   1704 var props = [1, "1", "a"];
   1705 
   1706 function TestFastElements(prop, mutation, prepopulate, polymorphic, optimize) {
   1707   var setElement = eval(
   1708     "(function setElement(a, i, v) { " + mutation + "; " +
   1709     "/* " + [].join.call(arguments, " ") + " */" +
   1710     "})"
   1711   );
   1712   print("TestFastElements:", setElement);
   1713 
   1714   var arr = prepopulate ? [1, 2, 3, 4, 5] : [0];
   1715   if (prepopulate) arr[prop] = 2;  // for non-element case
   1716   setElement(arr, prop, 3);
   1717   setElement(arr, prop, 4);
   1718   if (polymorphic) setElement(["M", "i", "l", "n", "e", "r"], 0, "m");
   1719   if (optimize) %OptimizeFunctionOnNextCall(setElement);
   1720   setElement(arr, prop, 5);
   1721 
   1722   reset();
   1723   Object.observe(arr, observer.callback);
   1724   setElement(arr, prop, 989898);
   1725   Object.deliverChangeRecords(observer.callback);
   1726   observer.assertCallbackRecords([
   1727     { object: arr, name: "" + prop, type: 'update', oldValue: 5 }
   1728   ]);
   1729 }
   1730 
   1731 for (var b1 = 0; b1 < 2; ++b1)
   1732   for (var b2 = 0; b2 < 2; ++b2)
   1733     for (var b3 = 0; b3 < 2; ++b3)
   1734       for (var i in props)
   1735         for (var j in mutation)
   1736           TestFastElements(props[i], mutation[j], b1 != 0, b2 != 0, b3 != 0);
   1737 
   1738 
   1739 var mutation = [
   1740   "a.length = v",
   1741   "a.length += newSize - oldSize",
   1742   "a.length -= oldSize - newSize",
   1743 ];
   1744 
   1745 var mutationByIncr = [
   1746   "++a.length",
   1747   "a.length++",
   1748 ];
   1749 
   1750 function TestFastElementsLength(
   1751   mutation, polymorphic, optimize, oldSize, newSize) {
   1752   var setLength = eval(
   1753     "(function setLength(a, v) { " + mutation + "; " +
   1754     "/* " + [].join.call(arguments, " ") + " */"
   1755     + "})"
   1756   );
   1757   print("TestFastElementsLength:", setLength);
   1758 
   1759   function array(n) {
   1760     var arr = new Array(n);
   1761     for (var i = 0; i < n; ++i) arr[i] = i;
   1762     return arr;
   1763   }
   1764 
   1765   setLength(array(oldSize), newSize);
   1766   setLength(array(oldSize), newSize);
   1767   if (polymorphic) setLength(array(oldSize).map(isNaN), newSize);
   1768   if (optimize) %OptimizeFunctionOnNextCall(setLength);
   1769   setLength(array(oldSize), newSize);
   1770 
   1771   reset();
   1772   var arr = array(oldSize);
   1773   Object.observe(arr, observer.callback);
   1774   setLength(arr, newSize);
   1775   Object.deliverChangeRecords(observer.callback);
   1776   if (oldSize === newSize) {
   1777     observer.assertNotCalled();
   1778   } else {
   1779     var count = oldSize > newSize ? oldSize - newSize : 0;
   1780     observer.assertRecordCount(count + 1);
   1781     var lengthRecord = observer.records[count];
   1782     assertSame(arr, lengthRecord.object);
   1783     assertEquals('length', lengthRecord.name);
   1784     assertEquals('update', lengthRecord.type);
   1785     assertSame(oldSize, lengthRecord.oldValue);
   1786   }
   1787 }
   1788 
   1789 for (var b1 = 0; b1 < 2; ++b1)
   1790   for (var b2 = 0; b2 < 2; ++b2)
   1791     for (var n1 = 0; n1 < 3; ++n1)
   1792       for (var n2 = 0; n2 < 3; ++n2)
   1793         for (var i in mutation)
   1794           TestFastElementsLength(mutation[i], b1 != 0, b2 != 0, 20*n1, 20*n2);
   1795 
   1796 for (var b1 = 0; b1 < 2; ++b1)
   1797   for (var b2 = 0; b2 < 2; ++b2)
   1798     for (var n = 0; n < 3; ++n)
   1799       for (var i in mutationByIncr)
   1800         TestFastElementsLength(mutationByIncr[i], b1 != 0, b2 != 0, 7*n, 7*n+1);
   1801