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); }, TypeError);
    114 assertThrows(function() { Object.observe(obj, nonFunction); }, TypeError);
    115 assertThrows(function() { Object.observe(obj, frozenFunction); }, TypeError);
    116 assertThrows(function() { Object.observe(obj, function() {}, 1); }, TypeError);
    117 assertThrows(function() { Object.observe(obj, function() {}, [undefined]); }, TypeError);
    118 assertThrows(function() { Object.observe(obj, function() {}, [1]); }, TypeError);
    119 assertThrows(function() { Object.observe(obj, function() {}, ['foo', null]); }, TypeError);
    120 assertEquals(obj, Object.observe(obj, observer.callback, ['foo', 'bar', 'baz']));
    121 assertEquals(obj, Object.observe(obj, observer.callback, []));
    122 assertEquals(obj, Object.observe(obj, observer.callback, undefined));
    123 assertEquals(obj, Object.observe(obj, observer.callback));
    124 
    125 // Object.unobserve
    126 assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError);
    127 assertThrows(function() { Object.unobserve(obj, nonFunction); }, TypeError);
    128 assertEquals(obj, Object.unobserve(obj, observer.callback));
    129 
    130 
    131 // Object.getNotifier
    132 var notifier = Object.getNotifier(obj);
    133 assertSame(notifier, Object.getNotifier(obj));
    134 assertEquals(null, Object.getNotifier(Object.freeze({})));
    135 assertFalse(notifier.hasOwnProperty('notify'));
    136 assertEquals([], Object.keys(notifier));
    137 var notifyDesc = Object.getOwnPropertyDescriptor(notifier.__proto__, 'notify');
    138 assertTrue(notifyDesc.configurable);
    139 assertTrue(notifyDesc.writable);
    140 assertFalse(notifyDesc.enumerable);
    141 assertThrows(function() { notifier.notify({}); }, TypeError);
    142 assertThrows(function() { notifier.notify({ type: 4 }); }, TypeError);
    143 
    144 assertThrows(function() { notifier.performChange(1, function(){}); }, TypeError);
    145 assertThrows(function() { notifier.performChange(undefined, function(){}); }, TypeError);
    146 assertThrows(function() { notifier.performChange('foo', undefined); }, TypeError);
    147 assertThrows(function() { notifier.performChange('foo', 'bar'); }, TypeError);
    148 var testSelf = {};
    149 notifier.performChange('foo', function() {
    150   assertTrue(testSelf === this);
    151 }, testSelf);
    152 var self = this;
    153 notifier.performChange('foo', function() {
    154   assertTrue(self === this);
    155 });
    156 
    157 var notify = notifier.notify;
    158 assertThrows(function() { notify.call(undefined, { type: 'a' }); }, TypeError);
    159 assertThrows(function() { notify.call(null, { type: 'a' }); }, TypeError);
    160 assertThrows(function() { notify.call(5, { type: 'a' }); }, TypeError);
    161 assertThrows(function() { notify.call('hello', { type: 'a' }); }, TypeError);
    162 assertThrows(function() { notify.call(false, { type: 'a' }); }, TypeError);
    163 assertThrows(function() { notify.call({}, { type: 'a' }); }, TypeError);
    164 assertFalse(recordCreated);
    165 notifier.notify(changeRecordWithAccessor);
    166 assertFalse(recordCreated);  // not observed yet
    167 
    168 
    169 // Object.deliverChangeRecords
    170 assertThrows(function() { Object.deliverChangeRecords(nonFunction); }, TypeError);
    171 
    172 Object.observe(obj, observer.callback);
    173 
    174 
    175 // notify uses to [[CreateOwnProperty]] to create changeRecord;
    176 reset();
    177 var protoExpandoAccessed = false;
    178 Object.defineProperty(Object.prototype, 'protoExpando',
    179   {
    180     configurable: true,
    181     set: function() { protoExpandoAccessed = true; }
    182   }
    183 );
    184 notifier.notify({ type: 'foo', protoExpando: 'val'});
    185 assertFalse(protoExpandoAccessed);
    186 delete Object.prototype.protoExpando;
    187 Object.deliverChangeRecords(observer.callback);
    188 
    189 
    190 // Multiple records are delivered.
    191 reset();
    192 notifier.notify({
    193   type: 'updated',
    194   name: 'foo',
    195   expando: 1
    196 });
    197 
    198 notifier.notify({
    199   object: notifier,  // object property is ignored
    200   type: 'deleted',
    201   name: 'bar',
    202   expando2: 'str'
    203 });
    204 Object.deliverChangeRecords(observer.callback);
    205 observer.assertCallbackRecords([
    206   { object: obj, name: 'foo', type: 'updated', expando: 1 },
    207   { object: obj, name: 'bar', type: 'deleted', expando2: 'str' }
    208 ]);
    209 
    210 
    211 // No delivery takes place if no records are pending
    212 reset();
    213 Object.deliverChangeRecords(observer.callback);
    214 observer.assertNotCalled();
    215 
    216 
    217 // Multiple observation has no effect.
    218 reset();
    219 Object.observe(obj, observer.callback);
    220 Object.observe(obj, observer.callback);
    221 Object.getNotifier(obj).notify({
    222   type: 'updated',
    223 });
    224 Object.deliverChangeRecords(observer.callback);
    225 observer.assertCalled();
    226 
    227 
    228 // Observation can be stopped.
    229 reset();
    230 Object.unobserve(obj, observer.callback);
    231 Object.getNotifier(obj).notify({
    232   type: 'updated',
    233 });
    234 Object.deliverChangeRecords(observer.callback);
    235 observer.assertNotCalled();
    236 
    237 
    238 // Multiple unobservation has no effect
    239 reset();
    240 Object.unobserve(obj, observer.callback);
    241 Object.unobserve(obj, observer.callback);
    242 Object.getNotifier(obj).notify({
    243   type: 'updated',
    244 });
    245 Object.deliverChangeRecords(observer.callback);
    246 observer.assertNotCalled();
    247 
    248 
    249 // Re-observation works and only includes changeRecords after of call.
    250 reset();
    251 Object.getNotifier(obj).notify({
    252   type: 'updated',
    253 });
    254 Object.observe(obj, observer.callback);
    255 Object.getNotifier(obj).notify({
    256   type: 'updated',
    257 });
    258 records = undefined;
    259 Object.deliverChangeRecords(observer.callback);
    260 observer.assertRecordCount(1);
    261 
    262 // Get notifier prior to observing
    263 reset();
    264 var obj = {};
    265 Object.getNotifier(obj);
    266 Object.observe(obj, observer.callback);
    267 obj.id = 1;
    268 Object.deliverChangeRecords(observer.callback);
    269 observer.assertCallbackRecords([
    270   { object: obj, type: 'new', name: 'id' },
    271 ]);
    272 
    273 // Observing a continuous stream of changes, while itermittantly unobserving.
    274 reset();
    275 Object.observe(obj, observer.callback);
    276 Object.getNotifier(obj).notify({
    277   type: 'updated',
    278   val: 1
    279 });
    280 
    281 Object.unobserve(obj, observer.callback);
    282 Object.getNotifier(obj).notify({
    283   type: 'updated',
    284   val: 2
    285 });
    286 
    287 Object.observe(obj, observer.callback);
    288 Object.getNotifier(obj).notify({
    289   type: 'updated',
    290   val: 3
    291 });
    292 
    293 Object.unobserve(obj, observer.callback);
    294 Object.getNotifier(obj).notify({
    295   type: 'updated',
    296   val: 4
    297 });
    298 
    299 Object.observe(obj, observer.callback);
    300 Object.getNotifier(obj).notify({
    301   type: 'updated',
    302   val: 5
    303 });
    304 
    305 Object.unobserve(obj, observer.callback);
    306 Object.deliverChangeRecords(observer.callback);
    307 observer.assertCallbackRecords([
    308   { object: obj, type: 'updated', val: 1 },
    309   { object: obj, type: 'updated', val: 3 },
    310   { object: obj, type: 'updated', val: 5 }
    311 ]);
    312 
    313 // Accept
    314 reset();
    315 Object.observe(obj, observer.callback, []);
    316 Object.getNotifier(obj).notify({
    317   type: 'new'
    318 });
    319 Object.getNotifier(obj).notify({
    320   type: 'updated'
    321 });
    322 Object.getNotifier(obj).notify({
    323   type: 'deleted'
    324 });
    325 Object.getNotifier(obj).notify({
    326   type: 'reconfigured'
    327 });
    328 Object.getNotifier(obj).notify({
    329   type: 'prototype'
    330 });
    331 Object.deliverChangeRecords(observer.callback);
    332 observer.assertNotCalled();
    333 
    334 reset();
    335 Object.observe(obj, observer.callback, ['new', 'deleted', 'prototype']);
    336 Object.getNotifier(obj).notify({
    337   type: 'new'
    338 });
    339 Object.getNotifier(obj).notify({
    340   type: 'updated'
    341 });
    342 Object.getNotifier(obj).notify({
    343   type: 'deleted'
    344 });
    345 Object.getNotifier(obj).notify({
    346   type: 'deleted'
    347 });
    348 Object.getNotifier(obj).notify({
    349   type: 'reconfigured'
    350 });
    351 Object.getNotifier(obj).notify({
    352   type: 'prototype'
    353 });
    354 Object.deliverChangeRecords(observer.callback);
    355 observer.assertCallbackRecords([
    356   { object: obj, type: 'new' },
    357   { object: obj, type: 'deleted' },
    358   { object: obj, type: 'deleted' },
    359   { object: obj, type: 'prototype' }
    360 ]);
    361 
    362 reset();
    363 Object.observe(obj, observer.callback, ['updated', 'foo']);
    364 Object.getNotifier(obj).notify({
    365   type: 'new'
    366 });
    367 Object.getNotifier(obj).notify({
    368   type: 'updated'
    369 });
    370 Object.getNotifier(obj).notify({
    371   type: 'deleted'
    372 });
    373 Object.getNotifier(obj).notify({
    374   type: 'foo'
    375 });
    376 Object.getNotifier(obj).notify({
    377   type: 'bar'
    378 });
    379 Object.getNotifier(obj).notify({
    380   type: 'foo'
    381 });
    382 Object.deliverChangeRecords(observer.callback);
    383 observer.assertCallbackRecords([
    384   { object: obj, type: 'updated' },
    385   { object: obj, type: 'foo' },
    386   { object: obj, type: 'foo' }
    387 ]);
    388 
    389 reset();
    390 function Thingy(a, b, c) {
    391   this.a = a;
    392   this.b = b;
    393 }
    394 
    395 Thingy.MULTIPLY = 'multiply';
    396 Thingy.INCREMENT = 'increment';
    397 Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply';
    398 
    399 Thingy.prototype = {
    400   increment: function(amount) {
    401     var notifier = Object.getNotifier(this);
    402 
    403     notifier.performChange(Thingy.INCREMENT, function() {
    404       this.a += amount;
    405       this.b += amount;
    406     }, this);
    407 
    408     notifier.notify({
    409       object: this,
    410       type: Thingy.INCREMENT,
    411       incremented: amount
    412     });
    413   },
    414 
    415   multiply: function(amount) {
    416     var notifier = Object.getNotifier(this);
    417 
    418     notifier.performChange(Thingy.MULTIPLY, function() {
    419       this.a *= amount;
    420       this.b *= amount;
    421     }, this);
    422 
    423     notifier.notify({
    424       object: this,
    425       type: Thingy.MULTIPLY,
    426       multiplied: amount
    427     });
    428   },
    429 
    430   incrementAndMultiply: function(incAmount, multAmount) {
    431     var notifier = Object.getNotifier(this);
    432 
    433     notifier.performChange(Thingy.INCREMENT_AND_MULTIPLY, function() {
    434       this.increment(incAmount);
    435       this.multiply(multAmount);
    436     }, this);
    437 
    438     notifier.notify({
    439       object: this,
    440       type: Thingy.INCREMENT_AND_MULTIPLY,
    441       incremented: incAmount,
    442       multiplied: multAmount
    443     });
    444   }
    445 }
    446 
    447 Thingy.observe = function(thingy, callback) {
    448   Object.observe(thingy, callback, [Thingy.INCREMENT,
    449                                     Thingy.MULTIPLY,
    450                                     Thingy.INCREMENT_AND_MULTIPLY,
    451                                     'updated']);
    452 }
    453 
    454 Thingy.unobserve = function(thingy, callback) {
    455   Object.unobserve(thingy);
    456 }
    457 
    458 var thingy = new Thingy(2, 4);
    459 
    460 Object.observe(thingy, observer.callback);
    461 Thingy.observe(thingy, observer2.callback);
    462 thingy.increment(3);               // { a: 5, b: 7 }
    463 thingy.b++;                        // { a: 5, b: 8 }
    464 thingy.multiply(2);                // { a: 10, b: 16 }
    465 thingy.a++;                        // { a: 11, b: 16 }
    466 thingy.incrementAndMultiply(2, 2); // { a: 26, b: 36 }
    467 
    468 Object.deliverChangeRecords(observer.callback);
    469 Object.deliverChangeRecords(observer2.callback);
    470 observer.assertCallbackRecords([
    471   { object: thingy, type: 'updated', name: 'a', oldValue: 2 },
    472   { object: thingy, type: 'updated', name: 'b', oldValue: 4 },
    473   { object: thingy, type: 'updated', name: 'b', oldValue: 7 },
    474   { object: thingy, type: 'updated', name: 'a', oldValue: 5 },
    475   { object: thingy, type: 'updated', name: 'b', oldValue: 8 },
    476   { object: thingy, type: 'updated', name: 'a', oldValue: 10 },
    477   { object: thingy, type: 'updated', name: 'a', oldValue: 11 },
    478   { object: thingy, type: 'updated', name: 'b', oldValue: 16 },
    479   { object: thingy, type: 'updated', name: 'a', oldValue: 13 },
    480   { object: thingy, type: 'updated', name: 'b', oldValue: 18 },
    481 ]);
    482 observer2.assertCallbackRecords([
    483   { object: thingy, type: Thingy.INCREMENT, incremented: 3 },
    484   { object: thingy, type: 'updated', name: 'b', oldValue: 7 },
    485   { object: thingy, type: Thingy.MULTIPLY, multiplied: 2 },
    486   { object: thingy, type: 'updated', name: 'a', oldValue: 10 },
    487   {
    488     object: thingy,
    489     type: Thingy.INCREMENT_AND_MULTIPLY,
    490     incremented: 2,
    491     multiplied: 2
    492   }
    493 ]);
    494 
    495 
    496 reset();
    497 function RecursiveThingy() {}
    498 
    499 RecursiveThingy.MULTIPLY_FIRST_N = 'multiplyFirstN';
    500 
    501 RecursiveThingy.prototype = {
    502   __proto__: Array.prototype,
    503 
    504   multiplyFirstN: function(amount, n) {
    505     if (!n)
    506       return;
    507     var notifier = Object.getNotifier(this);
    508     notifier.performChange(RecursiveThingy.MULTIPLY_FIRST_N, function() {
    509       this[n-1] = this[n-1]*amount;
    510       this.multiplyFirstN(amount, n-1);
    511     }, this);
    512 
    513     notifier.notify({
    514       object: this,
    515       type: RecursiveThingy.MULTIPLY_FIRST_N,
    516       multiplied: amount,
    517       n: n
    518     });
    519   },
    520 }
    521 
    522 RecursiveThingy.observe = function(thingy, callback) {
    523   Object.observe(thingy, callback, [RecursiveThingy.MULTIPLY_FIRST_N]);
    524 }
    525 
    526 RecursiveThingy.unobserve = function(thingy, callback) {
    527   Object.unobserve(thingy);
    528 }
    529 
    530 var thingy = new RecursiveThingy;
    531 thingy.push(1, 2, 3, 4);
    532 
    533 Object.observe(thingy, observer.callback);
    534 RecursiveThingy.observe(thingy, observer2.callback);
    535 thingy.multiplyFirstN(2, 3);                // [2, 4, 6, 4]
    536 
    537 Object.deliverChangeRecords(observer.callback);
    538 Object.deliverChangeRecords(observer2.callback);
    539 observer.assertCallbackRecords([
    540   { object: thingy, type: 'updated', name: '2', oldValue: 3 },
    541   { object: thingy, type: 'updated', name: '1', oldValue: 2 },
    542   { object: thingy, type: 'updated', name: '0', oldValue: 1 }
    543 ]);
    544 observer2.assertCallbackRecords([
    545   { object: thingy, type: RecursiveThingy.MULTIPLY_FIRST_N, multiplied: 2, n: 3 }
    546 ]);
    547 
    548 reset();
    549 function DeckSuit() {
    550   this.push('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'A', 'Q', 'K');
    551 }
    552 
    553 DeckSuit.SHUFFLE = 'shuffle';
    554 
    555 DeckSuit.prototype = {
    556   __proto__: Array.prototype,
    557 
    558   shuffle: function() {
    559     var notifier = Object.getNotifier(this);
    560     notifier.performChange(DeckSuit.SHUFFLE, function() {
    561       this.reverse();
    562       this.sort(function() { return Math.random()* 2 - 1; });
    563       var cut = this.splice(0, 6);
    564       Array.prototype.push.apply(this, cut);
    565       this.reverse();
    566       this.sort(function() { return Math.random()* 2 - 1; });
    567       var cut = this.splice(0, 6);
    568       Array.prototype.push.apply(this, cut);
    569       this.reverse();
    570       this.sort(function() { return Math.random()* 2 - 1; });
    571     }, this);
    572 
    573     notifier.notify({
    574       object: this,
    575       type: DeckSuit.SHUFFLE
    576     });
    577   },
    578 }
    579 
    580 DeckSuit.observe = function(thingy, callback) {
    581   Object.observe(thingy, callback, [DeckSuit.SHUFFLE]);
    582 }
    583 
    584 DeckSuit.unobserve = function(thingy, callback) {
    585   Object.unobserve(thingy);
    586 }
    587 
    588 var deck = new DeckSuit;
    589 
    590 DeckSuit.observe(deck, observer2.callback);
    591 deck.shuffle();
    592 
    593 Object.deliverChangeRecords(observer2.callback);
    594 observer2.assertCallbackRecords([
    595   { object: deck, type: DeckSuit.SHUFFLE }
    596 ]);
    597 
    598 // Observing multiple objects; records appear in order.
    599 reset();
    600 var obj2 = {};
    601 var obj3 = {}
    602 Object.observe(obj, observer.callback);
    603 Object.observe(obj3, observer.callback);
    604 Object.observe(obj2, observer.callback);
    605 Object.getNotifier(obj).notify({
    606   type: 'new',
    607 });
    608 Object.getNotifier(obj2).notify({
    609   type: 'updated',
    610 });
    611 Object.getNotifier(obj3).notify({
    612   type: 'deleted',
    613 });
    614 Object.observe(obj3, observer.callback);
    615 Object.deliverChangeRecords(observer.callback);
    616 observer.assertCallbackRecords([
    617   { object: obj, type: 'new' },
    618   { object: obj2, type: 'updated' },
    619   { object: obj3, type: 'deleted' }
    620 ]);
    621 
    622 
    623 // Recursive observation.
    624 var obj = {a: 1};
    625 var callbackCount = 0;
    626 function recursiveObserver(r) {
    627   assertEquals(1, r.length);
    628   ++callbackCount;
    629   if (r[0].oldValue < 100) ++obj[r[0].name];
    630 }
    631 Object.observe(obj, recursiveObserver);
    632 ++obj.a;
    633 Object.deliverChangeRecords(recursiveObserver);
    634 assertEquals(100, callbackCount);
    635 
    636 var obj1 = {a: 1};
    637 var obj2 = {a: 1};
    638 var recordCount = 0;
    639 function recursiveObserver2(r) {
    640   recordCount += r.length;
    641   if (r[0].oldValue < 100) {
    642     ++obj1.a;
    643     ++obj2.a;
    644   }
    645 }
    646 Object.observe(obj1, recursiveObserver2);
    647 Object.observe(obj2, recursiveObserver2);
    648 ++obj1.a;
    649 Object.deliverChangeRecords(recursiveObserver2);
    650 assertEquals(199, recordCount);
    651 
    652 
    653 // Observing named properties.
    654 reset();
    655 var obj = {a: 1}
    656 Object.observe(obj, observer.callback);
    657 obj.a = 2;
    658 obj["a"] = 3;
    659 delete obj.a;
    660 obj.a = 4;
    661 obj.a = 4;  // ignored
    662 obj.a = 5;
    663 Object.defineProperty(obj, "a", {value: 6});
    664 Object.defineProperty(obj, "a", {writable: false});
    665 obj.a = 7;  // ignored
    666 Object.defineProperty(obj, "a", {value: 8});
    667 Object.defineProperty(obj, "a", {value: 7, writable: true});
    668 Object.defineProperty(obj, "a", {get: function() {}});
    669 Object.defineProperty(obj, "a", {get: frozenFunction});
    670 Object.defineProperty(obj, "a", {get: frozenFunction});  // ignored
    671 Object.defineProperty(obj, "a", {get: frozenFunction, set: frozenFunction});
    672 Object.defineProperty(obj, "a", {set: frozenFunction});  // ignored
    673 Object.defineProperty(obj, "a", {get: undefined, set: frozenFunction});
    674 delete obj.a;
    675 delete obj.a;
    676 Object.defineProperty(obj, "a", {get: function() {}, configurable: true});
    677 Object.defineProperty(obj, "a", {value: 9, writable: true});
    678 obj.a = 10;
    679 ++obj.a;
    680 obj.a++;
    681 obj.a *= 3;
    682 delete obj.a;
    683 Object.defineProperty(obj, "a", {value: 11, configurable: true});
    684 Object.deliverChangeRecords(observer.callback);
    685 observer.assertCallbackRecords([
    686   { object: obj, name: "a", type: "updated", oldValue: 1 },
    687   { object: obj, name: "a", type: "updated", oldValue: 2 },
    688   { object: obj, name: "a", type: "deleted", oldValue: 3 },
    689   { object: obj, name: "a", type: "new" },
    690   { object: obj, name: "a", type: "updated", oldValue: 4 },
    691   { object: obj, name: "a", type: "updated", oldValue: 5 },
    692   { object: obj, name: "a", type: "reconfigured" },
    693   { object: obj, name: "a", type: "updated", oldValue: 6 },
    694   { object: obj, name: "a", type: "reconfigured", oldValue: 8 },
    695   { object: obj, name: "a", type: "reconfigured", oldValue: 7 },
    696   { object: obj, name: "a", type: "reconfigured" },
    697   { object: obj, name: "a", type: "reconfigured" },
    698   { object: obj, name: "a", type: "reconfigured" },
    699   { object: obj, name: "a", type: "deleted" },
    700   { object: obj, name: "a", type: "new" },
    701   { object: obj, name: "a", type: "reconfigured" },
    702   { object: obj, name: "a", type: "updated", oldValue: 9 },
    703   { object: obj, name: "a", type: "updated", oldValue: 10 },
    704   { object: obj, name: "a", type: "updated", oldValue: 11 },
    705   { object: obj, name: "a", type: "updated", oldValue: 12 },
    706   { object: obj, name: "a", type: "deleted", oldValue: 36 },
    707   { object: obj, name: "a", type: "new" },
    708 ]);
    709 
    710 
    711 // Observing indexed properties.
    712 reset();
    713 var obj = {'1': 1}
    714 Object.observe(obj, observer.callback);
    715 obj[1] = 2;
    716 obj[1] = 3;
    717 delete obj[1];
    718 obj[1] = 4;
    719 obj[1] = 4;  // ignored
    720 obj[1] = 5;
    721 Object.defineProperty(obj, "1", {value: 6});
    722 Object.defineProperty(obj, "1", {writable: false});
    723 obj[1] = 7;  // ignored
    724 Object.defineProperty(obj, "1", {value: 8});
    725 Object.defineProperty(obj, "1", {value: 7, writable: true});
    726 Object.defineProperty(obj, "1", {get: function() {}});
    727 Object.defineProperty(obj, "1", {get: frozenFunction});
    728 Object.defineProperty(obj, "1", {get: frozenFunction});  // ignored
    729 Object.defineProperty(obj, "1", {get: frozenFunction, set: frozenFunction});
    730 Object.defineProperty(obj, "1", {set: frozenFunction});  // ignored
    731 Object.defineProperty(obj, "1", {get: undefined, set: frozenFunction});
    732 delete obj[1];
    733 delete obj[1];
    734 Object.defineProperty(obj, "1", {get: function() {}, configurable: true});
    735 Object.defineProperty(obj, "1", {value: 9, writable: true});
    736 obj[1] = 10;
    737 ++obj[1];
    738 obj[1]++;
    739 obj[1] *= 3;
    740 delete obj[1];
    741 Object.defineProperty(obj, "1", {value: 11, configurable: true});
    742 Object.deliverChangeRecords(observer.callback);
    743 observer.assertCallbackRecords([
    744   { object: obj, name: "1", type: "updated", oldValue: 1 },
    745   { object: obj, name: "1", type: "updated", oldValue: 2 },
    746   { object: obj, name: "1", type: "deleted", oldValue: 3 },
    747   { object: obj, name: "1", type: "new" },
    748   { object: obj, name: "1", type: "updated", oldValue: 4 },
    749   { object: obj, name: "1", type: "updated", oldValue: 5 },
    750   { object: obj, name: "1", type: "reconfigured" },
    751   { object: obj, name: "1", type: "updated", oldValue: 6 },
    752   { object: obj, name: "1", type: "reconfigured", oldValue: 8 },
    753   { object: obj, name: "1", type: "reconfigured", oldValue: 7 },
    754   { object: obj, name: "1", type: "reconfigured" },
    755   { object: obj, name: "1", type: "reconfigured" },
    756   { object: obj, name: "1", type: "reconfigured" },
    757   { object: obj, name: "1", type: "deleted" },
    758   { object: obj, name: "1", type: "new" },
    759   { object: obj, name: "1", type: "reconfigured" },
    760   { object: obj, name: "1", type: "updated", oldValue: 9 },
    761   { object: obj, name: "1", type: "updated", oldValue: 10 },
    762   { object: obj, name: "1", type: "updated", oldValue: 11 },
    763   { object: obj, name: "1", type: "updated", oldValue: 12 },
    764   { object: obj, name: "1", type: "deleted", oldValue: 36 },
    765   { object: obj, name: "1", type: "new" },
    766 ]);
    767 
    768 
    769 // Observing symbol properties (not).
    770 print("*****")
    771 reset();
    772 var obj = {}
    773 var symbol = Symbol("secret");
    774 Object.observe(obj, observer.callback);
    775 obj[symbol] = 3;
    776 delete obj[symbol];
    777 Object.defineProperty(obj, symbol, {get: function() {}, configurable: true});
    778 Object.defineProperty(obj, symbol, {value: 6});
    779 Object.defineProperty(obj, symbol, {writable: false});
    780 delete obj[symbol];
    781 Object.defineProperty(obj, symbol, {value: 7});
    782 ++obj[symbol];
    783 obj[symbol]++;
    784 obj[symbol] *= 3;
    785 delete obj[symbol];
    786 obj.__defineSetter__(symbol, function() {});
    787 obj.__defineGetter__(symbol, function() {});
    788 Object.deliverChangeRecords(observer.callback);
    789 observer.assertNotCalled();
    790 
    791 
    792 // Test all kinds of objects generically.
    793 function TestObserveConfigurable(obj, prop) {
    794   reset();
    795   Object.observe(obj, observer.callback);
    796   Object.unobserve(obj, observer.callback);
    797   obj[prop] = 1;
    798   Object.observe(obj, observer.callback);
    799   obj[prop] = 2;
    800   obj[prop] = 3;
    801   delete obj[prop];
    802   obj[prop] = 4;
    803   obj[prop] = 4;  // ignored
    804   obj[prop] = 5;
    805   Object.defineProperty(obj, prop, {value: 6});
    806   Object.defineProperty(obj, prop, {writable: false});
    807   obj[prop] = 7;  // ignored
    808   Object.defineProperty(obj, prop, {value: 8});
    809   Object.defineProperty(obj, prop, {value: 7, writable: true});
    810   Object.defineProperty(obj, prop, {get: function() {}});
    811   Object.defineProperty(obj, prop, {get: frozenFunction});
    812   Object.defineProperty(obj, prop, {get: frozenFunction});  // ignored
    813   Object.defineProperty(obj, prop, {get: frozenFunction, set: frozenFunction});
    814   Object.defineProperty(obj, prop, {set: frozenFunction});  // ignored
    815   Object.defineProperty(obj, prop, {get: undefined, set: frozenFunction});
    816   obj.__defineSetter__(prop, frozenFunction);  // ignored
    817   obj.__defineSetter__(prop, function() {});
    818   obj.__defineGetter__(prop, function() {});
    819   delete obj[prop];
    820   delete obj[prop];  // ignored
    821   obj.__defineGetter__(prop, function() {});
    822   delete obj[prop];
    823   Object.defineProperty(obj, prop, {get: function() {}, configurable: true});
    824   Object.defineProperty(obj, prop, {value: 9, writable: true});
    825   obj[prop] = 10;
    826   ++obj[prop];
    827   obj[prop]++;
    828   obj[prop] *= 3;
    829   delete obj[prop];
    830   Object.defineProperty(obj, prop, {value: 11, configurable: true});
    831   Object.deliverChangeRecords(observer.callback);
    832   observer.assertCallbackRecords([
    833     { object: obj, name: prop, type: "updated", oldValue: 1 },
    834     { object: obj, name: prop, type: "updated", oldValue: 2 },
    835     { object: obj, name: prop, type: "deleted", oldValue: 3 },
    836     { object: obj, name: prop, type: "new" },
    837     { object: obj, name: prop, type: "updated", oldValue: 4 },
    838     { object: obj, name: prop, type: "updated", oldValue: 5 },
    839     { object: obj, name: prop, type: "reconfigured" },
    840     { object: obj, name: prop, type: "updated", oldValue: 6 },
    841     { object: obj, name: prop, type: "reconfigured", oldValue: 8 },
    842     { object: obj, name: prop, type: "reconfigured", oldValue: 7 },
    843     { object: obj, name: prop, type: "reconfigured" },
    844     { object: obj, name: prop, type: "reconfigured" },
    845     { object: obj, name: prop, type: "reconfigured" },
    846     { object: obj, name: prop, type: "reconfigured" },
    847     { object: obj, name: prop, type: "reconfigured" },
    848     { object: obj, name: prop, type: "deleted" },
    849     { object: obj, name: prop, type: "new" },
    850     { object: obj, name: prop, type: "deleted" },
    851     { object: obj, name: prop, type: "new" },
    852     { object: obj, name: prop, type: "reconfigured" },
    853     { object: obj, name: prop, type: "updated", oldValue: 9 },
    854     { object: obj, name: prop, type: "updated", oldValue: 10 },
    855     { object: obj, name: prop, type: "updated", oldValue: 11 },
    856     { object: obj, name: prop, type: "updated", oldValue: 12 },
    857     { object: obj, name: prop, type: "deleted", oldValue: 36 },
    858     { object: obj, name: prop, type: "new" },
    859   ]);
    860   Object.unobserve(obj, observer.callback);
    861   delete obj[prop];
    862 }
    863 
    864 function TestObserveNonConfigurable(obj, prop, desc) {
    865   reset();
    866   Object.observe(obj, observer.callback);
    867   Object.unobserve(obj, observer.callback);
    868   obj[prop] = 1;
    869   Object.observe(obj, observer.callback);
    870   obj[prop] = 4;
    871   obj[prop] = 4;  // ignored
    872   obj[prop] = 5;
    873   Object.defineProperty(obj, prop, {value: 6});
    874   Object.defineProperty(obj, prop, {value: 6});  // ignored
    875   Object.defineProperty(obj, prop, {value: 7});
    876   Object.defineProperty(obj, prop, {enumerable: desc.enumerable});  // ignored
    877   Object.defineProperty(obj, prop, {writable: false});
    878   obj[prop] = 7;  // ignored
    879   Object.deliverChangeRecords(observer.callback);
    880   observer.assertCallbackRecords([
    881     { object: obj, name: prop, type: "updated", oldValue: 1 },
    882     { object: obj, name: prop, type: "updated", oldValue: 4 },
    883     { object: obj, name: prop, type: "updated", oldValue: 5 },
    884     { object: obj, name: prop, type: "updated", oldValue: 6 },
    885     { object: obj, name: prop, type: "reconfigured" },
    886   ]);
    887   Object.unobserve(obj, observer.callback);
    888 }
    889 
    890 function createProxy(create, x) {
    891   var handler = {
    892     getPropertyDescriptor: function(k) {
    893       for (var o = this.target; o; o = Object.getPrototypeOf(o)) {
    894         var desc = Object.getOwnPropertyDescriptor(o, k);
    895         if (desc) return desc;
    896       }
    897       return undefined;
    898     },
    899     getOwnPropertyDescriptor: function(k) {
    900       return Object.getOwnPropertyDescriptor(this.target, k);
    901     },
    902     defineProperty: function(k, desc) {
    903       var x = Object.defineProperty(this.target, k, desc);
    904       Object.deliverChangeRecords(this.callback);
    905       return x;
    906     },
    907     delete: function(k) {
    908       var x = delete this.target[k];
    909       Object.deliverChangeRecords(this.callback);
    910       return x;
    911     },
    912     getPropertyNames: function() {
    913       return Object.getOwnPropertyNames(this.target);
    914     },
    915     target: {isProxy: true},
    916     callback: function(changeRecords) {
    917       print("callback", stringifyNoThrow(handler.proxy), stringifyNoThrow(got));
    918       for (var i in changeRecords) {
    919         var got = changeRecords[i];
    920         var change = {object: handler.proxy, name: got.name, type: got.type};
    921         if ("oldValue" in got) change.oldValue = got.oldValue;
    922         Object.getNotifier(handler.proxy).notify(change);
    923       }
    924     },
    925   };
    926   Object.observe(handler.target, handler.callback);
    927   return handler.proxy = create(handler, x);
    928 }
    929 
    930 var objects = [
    931   {},
    932   [],
    933   this,  // global object
    934   function(){},
    935   (function(){ return arguments })(),
    936   (function(){ "use strict"; return arguments })(),
    937   Object(1), Object(true), Object("bla"),
    938   new Date(),
    939   Object, Function, Date, RegExp,
    940   new Set, new Map, new WeakMap,
    941   new ArrayBuffer(10), new Int32Array(5),
    942   createProxy(Proxy.create, null),
    943   createProxy(Proxy.createFunction, function(){}),
    944 ];
    945 var properties = ["a", "1", 1, "length", "prototype", "name", "caller"];
    946 
    947 // Cases that yield non-standard results.
    948 function blacklisted(obj, prop) {
    949   return (obj instanceof Int32Array && prop == 1) ||
    950          (obj instanceof Int32Array && prop === "length") ||
    951          (obj instanceof ArrayBuffer && prop == 1)
    952 }
    953 
    954 for (var i in objects) for (var j in properties) {
    955   var obj = objects[i];
    956   var prop = properties[j];
    957   if (blacklisted(obj, prop)) continue;
    958   var desc = Object.getOwnPropertyDescriptor(obj, prop);
    959   print("***", typeof obj, stringifyNoThrow(obj), prop);
    960   if (!desc || desc.configurable)
    961     TestObserveConfigurable(obj, prop);
    962   else if (desc.writable)
    963     TestObserveNonConfigurable(obj, prop, desc);
    964 }
    965 
    966 
    967 // Observing array length (including truncation)
    968 reset();
    969 var arr = ['a', 'b', 'c', 'd'];
    970 var arr2 = ['alpha', 'beta'];
    971 var arr3 = ['hello'];
    972 arr3[2] = 'goodbye';
    973 arr3.length = 6;
    974 Object.defineProperty(arr, '0', {configurable: false});
    975 Object.defineProperty(arr, '2', {get: function(){}});
    976 Object.defineProperty(arr2, '0', {get: function(){}, configurable: false});
    977 Object.observe(arr, observer.callback);
    978 Array.observe(arr, observer2.callback);
    979 Object.observe(arr2, observer.callback);
    980 Array.observe(arr2, observer2.callback);
    981 Object.observe(arr3, observer.callback);
    982 Array.observe(arr3, observer2.callback);
    983 arr.length = 2;
    984 arr.length = 0;
    985 arr.length = 10;
    986 Object.defineProperty(arr, 'length', {writable: false});
    987 arr2.length = 0;
    988 arr2.length = 1; // no change expected
    989 Object.defineProperty(arr2, 'length', {value: 1, writable: false});
    990 arr3.length = 0;
    991 ++arr3.length;
    992 arr3.length++;
    993 arr3.length /= 2;
    994 Object.defineProperty(arr3, 'length', {value: 5});
    995 arr3[4] = 5;
    996 Object.defineProperty(arr3, 'length', {value: 1, writable: false});
    997 Object.deliverChangeRecords(observer.callback);
    998 observer.assertCallbackRecords([
    999   { object: arr, name: '3', type: 'deleted', oldValue: 'd' },
   1000   { object: arr, name: '2', type: 'deleted' },
   1001   { object: arr, name: 'length', type: 'updated', oldValue: 4 },
   1002   { object: arr, name: '1', type: 'deleted', oldValue: 'b' },
   1003   { object: arr, name: 'length', type: 'updated', oldValue: 2 },
   1004   { object: arr, name: 'length', type: 'updated', oldValue: 1 },
   1005   { object: arr, name: 'length', type: 'reconfigured' },
   1006   { object: arr2, name: '1', type: 'deleted', oldValue: 'beta' },
   1007   { object: arr2, name: 'length', type: 'updated', oldValue: 2 },
   1008   { object: arr2, name: 'length', type: 'reconfigured' },
   1009   { object: arr3, name: '2', type: 'deleted', oldValue: 'goodbye' },
   1010   { object: arr3, name: '0', type: 'deleted', oldValue: 'hello' },
   1011   { object: arr3, name: 'length', type: 'updated', oldValue: 6 },
   1012   { object: arr3, name: 'length', type: 'updated', oldValue: 0 },
   1013   { object: arr3, name: 'length', type: 'updated', oldValue: 1 },
   1014   { object: arr3, name: 'length', type: 'updated', oldValue: 2 },
   1015   { object: arr3, name: 'length', type: 'updated', oldValue: 1 },
   1016   { object: arr3, name: '4', type: 'new' },
   1017   { object: arr3, name: '4', type: 'deleted', oldValue: 5 },
   1018   // TODO(rafaelw): It breaks spec compliance to get two records here.
   1019   // When the TODO in v8natives.js::DefineArrayProperty is addressed
   1020   // which prevents DefineProperty from over-writing the magic length
   1021   // property, these will collapse into a single record.
   1022   { object: arr3, name: 'length', type: 'updated', oldValue: 5 },
   1023   { object: arr3, name: 'length', type: 'reconfigured' }
   1024 ]);
   1025 Object.deliverChangeRecords(observer2.callback);
   1026 observer2.assertCallbackRecords([
   1027   { object: arr, type: 'splice', index: 2, removed: [, 'd'], addedCount: 0 },
   1028   { object: arr, type: 'splice', index: 1, removed: ['b'], addedCount: 0 },
   1029   { object: arr, type: 'splice', index: 1, removed: [], addedCount: 9 },
   1030   { object: arr2, type: 'splice', index: 1, removed: ['beta'], addedCount: 0 },
   1031   { object: arr3, type: 'splice', index: 0, removed: ['hello',, 'goodbye',,,,], addedCount: 0 },
   1032   { object: arr3, type: 'splice', index: 0, removed: [], addedCount: 1 },
   1033   { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 1 },
   1034   { object: arr3, type: 'splice', index: 1, removed: [,], addedCount: 0 },
   1035   { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 4 },
   1036   { object: arr3, name: '4', type: 'new' },
   1037   { object: arr3, type: 'splice', index: 1, removed: [,,,5], addedCount: 0 }
   1038 ]);
   1039 
   1040 
   1041 // Updating length on large (slow) array
   1042 reset();
   1043 var slow_arr = new Array(1000000000);
   1044 slow_arr[500000000] = 'hello';
   1045 Object.observe(slow_arr, observer.callback);
   1046 var spliceRecords;
   1047 function slowSpliceCallback(records) {
   1048   spliceRecords = records;
   1049 }
   1050 Array.observe(slow_arr, slowSpliceCallback);
   1051 slow_arr.length = 100;
   1052 Object.deliverChangeRecords(observer.callback);
   1053 observer.assertCallbackRecords([
   1054   { object: slow_arr, name: '500000000', type: 'deleted', oldValue: 'hello' },
   1055   { object: slow_arr, name: 'length', type: 'updated', oldValue: 1000000000 },
   1056 ]);
   1057 Object.deliverChangeRecords(slowSpliceCallback);
   1058 assertEquals(spliceRecords.length, 1);
   1059 // Have to custom assert this splice record because the removed array is huge.
   1060 var splice = spliceRecords[0];
   1061 assertSame(splice.object, slow_arr);
   1062 assertEquals(splice.type, 'splice');
   1063 assertEquals(splice.index, 100);
   1064 assertEquals(splice.addedCount, 0);
   1065 var array_keys = %GetArrayKeys(splice.removed, splice.removed.length);
   1066 assertEquals(array_keys.length, 1);
   1067 assertEquals(array_keys[0], 499999900);
   1068 assertEquals(splice.removed[499999900], 'hello');
   1069 assertEquals(splice.removed.length, 999999900);
   1070 
   1071 
   1072 // Assignments in loops (checking different IC states).
   1073 reset();
   1074 var obj = {};
   1075 Object.observe(obj, observer.callback);
   1076 for (var i = 0; i < 5; i++) {
   1077   obj["a" + i] = i;
   1078 }
   1079 Object.deliverChangeRecords(observer.callback);
   1080 observer.assertCallbackRecords([
   1081   { object: obj, name: "a0", type: "new" },
   1082   { object: obj, name: "a1", type: "new" },
   1083   { object: obj, name: "a2", type: "new" },
   1084   { object: obj, name: "a3", type: "new" },
   1085   { object: obj, name: "a4", type: "new" },
   1086 ]);
   1087 
   1088 reset();
   1089 var obj = {};
   1090 Object.observe(obj, observer.callback);
   1091 for (var i = 0; i < 5; i++) {
   1092   obj[i] = i;
   1093 }
   1094 Object.deliverChangeRecords(observer.callback);
   1095 observer.assertCallbackRecords([
   1096   { object: obj, name: "0", type: "new" },
   1097   { object: obj, name: "1", type: "new" },
   1098   { object: obj, name: "2", type: "new" },
   1099   { object: obj, name: "3", type: "new" },
   1100   { object: obj, name: "4", type: "new" },
   1101 ]);
   1102 
   1103 
   1104 // Adding elements past the end of an array should notify on length for
   1105 // Object.observe and emit "splices" for Array.observe.
   1106 reset();
   1107 var arr = [1, 2, 3];
   1108 Object.observe(arr, observer.callback);
   1109 Array.observe(arr, observer2.callback);
   1110 arr[3] = 10;
   1111 arr[100] = 20;
   1112 Object.defineProperty(arr, '200', {value: 7});
   1113 Object.defineProperty(arr, '400', {get: function(){}});
   1114 arr[50] = 30; // no length change expected
   1115 Object.deliverChangeRecords(observer.callback);
   1116 observer.assertCallbackRecords([
   1117   { object: arr, name: '3', type: 'new' },
   1118   { object: arr, name: 'length', type: 'updated', oldValue: 3 },
   1119   { object: arr, name: '100', type: 'new' },
   1120   { object: arr, name: 'length', type: 'updated', oldValue: 4 },
   1121   { object: arr, name: '200', type: 'new' },
   1122   { object: arr, name: 'length', type: 'updated', oldValue: 101 },
   1123   { object: arr, name: '400', type: 'new' },
   1124   { object: arr, name: 'length', type: 'updated', oldValue: 201 },
   1125   { object: arr, name: '50', type: 'new' },
   1126 ]);
   1127 Object.deliverChangeRecords(observer2.callback);
   1128 observer2.assertCallbackRecords([
   1129   { object: arr, type: 'splice', index: 3, removed: [], addedCount: 1 },
   1130   { object: arr, type: 'splice', index: 4, removed: [], addedCount: 97 },
   1131   { object: arr, type: 'splice', index: 101, removed: [], addedCount: 100 },
   1132   { object: arr, type: 'splice', index: 201, removed: [], addedCount: 200 },
   1133   { object: arr, type: 'new', name: '50' },
   1134 ]);
   1135 
   1136 
   1137 // Tests for array methods, first on arrays and then on plain objects
   1138 //
   1139 // === ARRAYS ===
   1140 //
   1141 // Push
   1142 reset();
   1143 var array = [1, 2];
   1144 Object.observe(array, observer.callback);
   1145 Array.observe(array, observer2.callback);
   1146 array.push(3, 4);
   1147 array.push(5);
   1148 Object.deliverChangeRecords(observer.callback);
   1149 observer.assertCallbackRecords([
   1150   { object: array, name: '2', type: 'new' },
   1151   { object: array, name: 'length', type: 'updated', oldValue: 2 },
   1152   { object: array, name: '3', type: 'new' },
   1153   { object: array, name: 'length', type: 'updated', oldValue: 3 },
   1154   { object: array, name: '4', type: 'new' },
   1155   { object: array, name: 'length', type: 'updated', oldValue: 4 },
   1156 ]);
   1157 Object.deliverChangeRecords(observer2.callback);
   1158 observer2.assertCallbackRecords([
   1159   { object: array, type: 'splice', index: 2, removed: [], addedCount: 2 },
   1160   { object: array, type: 'splice', index: 4, removed: [], addedCount: 1 }
   1161 ]);
   1162 
   1163 // Pop
   1164 reset();
   1165 var array = [1, 2];
   1166 Object.observe(array, observer.callback);
   1167 array.pop();
   1168 array.pop();
   1169 Object.deliverChangeRecords(observer.callback);
   1170 observer.assertCallbackRecords([
   1171   { object: array, name: '1', type: 'deleted', oldValue: 2 },
   1172   { object: array, name: 'length', type: 'updated', oldValue: 2 },
   1173   { object: array, name: '0', type: 'deleted', oldValue: 1 },
   1174   { object: array, name: 'length', type: 'updated', oldValue: 1 },
   1175 ]);
   1176 
   1177 // Shift
   1178 reset();
   1179 var array = [1, 2];
   1180 Object.observe(array, observer.callback);
   1181 array.shift();
   1182 array.shift();
   1183 Object.deliverChangeRecords(observer.callback);
   1184 observer.assertCallbackRecords([
   1185   { object: array, name: '0', type: 'updated', oldValue: 1 },
   1186   { object: array, name: '1', type: 'deleted', oldValue: 2 },
   1187   { object: array, name: 'length', type: 'updated', oldValue: 2 },
   1188   { object: array, name: '0', type: 'deleted', oldValue: 2 },
   1189   { object: array, name: 'length', type: 'updated', oldValue: 1 },
   1190 ]);
   1191 
   1192 // Unshift
   1193 reset();
   1194 var array = [1, 2];
   1195 Object.observe(array, observer.callback);
   1196 array.unshift(3, 4);
   1197 Object.deliverChangeRecords(observer.callback);
   1198 observer.assertCallbackRecords([
   1199   { object: array, name: '3', type: 'new' },
   1200   { object: array, name: 'length', type: 'updated', oldValue: 2 },
   1201   { object: array, name: '2', type: 'new' },
   1202   { object: array, name: '0', type: 'updated', oldValue: 1 },
   1203   { object: array, name: '1', type: 'updated', oldValue: 2 },
   1204 ]);
   1205 
   1206 // Splice
   1207 reset();
   1208 var array = [1, 2, 3];
   1209 Object.observe(array, observer.callback);
   1210 array.splice(1, 1, 4, 5);
   1211 Object.deliverChangeRecords(observer.callback);
   1212 observer.assertCallbackRecords([
   1213   { object: array, name: '3', type: 'new' },
   1214   { object: array, name: 'length', type: 'updated', oldValue: 3 },
   1215   { object: array, name: '1', type: 'updated', oldValue: 2 },
   1216   { object: array, name: '2', type: 'updated', oldValue: 3 },
   1217 ]);
   1218 
   1219 // Sort
   1220 reset();
   1221 var array = [3, 2, 1];
   1222 Object.observe(array, observer.callback);
   1223 array.sort();
   1224 assertEquals(1, array[0]);
   1225 assertEquals(2, array[1]);
   1226 assertEquals(3, array[2]);
   1227 Object.deliverChangeRecords(observer.callback);
   1228 observer.assertCallbackRecords([
   1229   { object: array, name: '1', type: 'updated', oldValue: 2 },
   1230   { object: array, name: '0', type: 'updated', oldValue: 3 },
   1231   { object: array, name: '2', type: 'updated', oldValue: 1 },
   1232   { object: array, name: '1', type: 'updated', oldValue: 3 },
   1233   { object: array, name: '0', type: 'updated', oldValue: 2 },
   1234 ]);
   1235 
   1236 //
   1237 // === PLAIN OBJECTS ===
   1238 //
   1239 // Push
   1240 reset()
   1241 var array = {0: 1, 1: 2, length: 2}
   1242 Object.observe(array, observer.callback);
   1243 Array.prototype.push.call(array, 3, 4);
   1244 Object.deliverChangeRecords(observer.callback);
   1245 observer.assertCallbackRecords([
   1246   { object: array, name: '2', type: 'new' },
   1247   { object: array, name: '3', type: 'new' },
   1248   { object: array, name: 'length', type: 'updated', oldValue: 2 },
   1249 ]);
   1250 
   1251 // Pop
   1252 reset();
   1253 var array = [1, 2];
   1254 Object.observe(array, observer.callback);
   1255 Array.observe(array, observer2.callback);
   1256 array.pop();
   1257 array.pop();
   1258 array.pop();
   1259 Object.deliverChangeRecords(observer.callback);
   1260 observer.assertCallbackRecords([
   1261   { object: array, name: '1', type: 'deleted', oldValue: 2 },
   1262   { object: array, name: 'length', type: 'updated', oldValue: 2 },
   1263   { object: array, name: '0', type: 'deleted', oldValue: 1 },
   1264   { object: array, name: 'length', type: 'updated', oldValue: 1 },
   1265 ]);
   1266 Object.deliverChangeRecords(observer2.callback);
   1267 observer2.assertCallbackRecords([
   1268   { object: array, type: 'splice', index: 1, removed: [2], addedCount: 0 },
   1269   { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }
   1270 ]);
   1271 
   1272 // Shift
   1273 reset();
   1274 var array = [1, 2];
   1275 Object.observe(array, observer.callback);
   1276 Array.observe(array, observer2.callback);
   1277 array.shift();
   1278 array.shift();
   1279 array.shift();
   1280 Object.deliverChangeRecords(observer.callback);
   1281 observer.assertCallbackRecords([
   1282   { object: array, name: '0', type: 'updated', oldValue: 1 },
   1283   { object: array, name: '1', type: 'deleted', oldValue: 2 },
   1284   { object: array, name: 'length', type: 'updated', oldValue: 2 },
   1285   { object: array, name: '0', type: 'deleted', oldValue: 2 },
   1286   { object: array, name: 'length', type: 'updated', oldValue: 1 },
   1287 ]);
   1288 Object.deliverChangeRecords(observer2.callback);
   1289 observer2.assertCallbackRecords([
   1290   { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
   1291   { object: array, type: 'splice', index: 0, removed: [2], addedCount: 0 }
   1292 ]);
   1293 
   1294 // Unshift
   1295 reset();
   1296 var array = [1, 2];
   1297 Object.observe(array, observer.callback);
   1298 Array.observe(array, observer2.callback);
   1299 array.unshift(3, 4);
   1300 array.unshift(5);
   1301 Object.deliverChangeRecords(observer.callback);
   1302 observer.assertCallbackRecords([
   1303   { object: array, name: '3', type: 'new' },
   1304   { object: array, name: 'length', type: 'updated', oldValue: 2 },
   1305   { object: array, name: '2', type: 'new' },
   1306   { object: array, name: '0', type: 'updated', oldValue: 1 },
   1307   { object: array, name: '1', type: 'updated', oldValue: 2 },
   1308   { object: array, name: '4', type: 'new' },
   1309   { object: array, name: 'length', type: 'updated', oldValue: 4 },
   1310   { object: array, name: '3', type: 'updated', oldValue: 2 },
   1311   { object: array, name: '2', type: 'updated', oldValue: 1 },
   1312   { object: array, name: '1', type: 'updated', oldValue: 4 },
   1313   { object: array, name: '0', type: 'updated', oldValue: 3 },
   1314 ]);
   1315 Object.deliverChangeRecords(observer2.callback);
   1316 observer2.assertCallbackRecords([
   1317   { object: array, type: 'splice', index: 0, removed: [], addedCount: 2 },
   1318   { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }
   1319 ]);
   1320 
   1321 // Splice
   1322 reset();
   1323 var array = [1, 2, 3];
   1324 Object.observe(array, observer.callback);
   1325 Array.observe(array, observer2.callback);
   1326 array.splice(1, 0, 4, 5); // 1 4 5 2 3
   1327 array.splice(0, 2); // 5 2 3
   1328 array.splice(1, 2, 6, 7); // 5 6 7
   1329 array.splice(2, 0);
   1330 Object.deliverChangeRecords(observer.callback);
   1331 observer.assertCallbackRecords([
   1332   { object: array, name: '4', type: 'new' },
   1333   { object: array, name: 'length', type: 'updated', oldValue: 3 },
   1334   { object: array, name: '3', type: 'new' },
   1335   { object: array, name: '1', type: 'updated', oldValue: 2 },
   1336   { object: array, name: '2', type: 'updated', oldValue: 3 },
   1337 
   1338   { object: array, name: '0', type: 'updated', oldValue: 1 },
   1339   { object: array, name: '1', type: 'updated', oldValue: 4 },
   1340   { object: array, name: '2', type: 'updated', oldValue: 5 },
   1341   { object: array, name: '4', type: 'deleted', oldValue: 3 },
   1342   { object: array, name: '3', type: 'deleted', oldValue: 2 },
   1343   { object: array, name: 'length', type: 'updated', oldValue: 5 },
   1344 
   1345   { object: array, name: '1', type: 'updated', oldValue: 2 },
   1346   { object: array, name: '2', type: 'updated', oldValue: 3 },
   1347 ]);
   1348 Object.deliverChangeRecords(observer2.callback);
   1349 observer2.assertCallbackRecords([
   1350   { object: array, type: 'splice', index: 1, removed: [], addedCount: 2 },
   1351   { object: array, type: 'splice', index: 0, removed: [1, 4], addedCount: 0 },
   1352   { object: array, type: 'splice', index: 1, removed: [2, 3], addedCount: 2 },
   1353 ]);
   1354 
   1355 // Exercise StoreIC_ArrayLength
   1356 reset();
   1357 var dummy = {};
   1358 Object.observe(dummy, observer.callback);
   1359 Object.unobserve(dummy, observer.callback);
   1360 var array = [0];
   1361 Object.observe(array, observer.callback);
   1362 array.splice(0, 1);
   1363 Object.deliverChangeRecords(observer.callback);
   1364 observer.assertCallbackRecords([
   1365   { object: array, name: '0', type: 'deleted', oldValue: 0 },
   1366   { object: array, name: 'length', type: 'updated', oldValue: 1},
   1367 ]);
   1368 
   1369 
   1370 // __proto__
   1371 reset();
   1372 var obj = {};
   1373 Object.observe(obj, observer.callback);
   1374 var p = {foo: 'yes'};
   1375 var q = {bar: 'no'};
   1376 obj.__proto__ = p;
   1377 obj.__proto__ = p;  // ignored
   1378 obj.__proto__ = null;
   1379 obj.__proto__ = q;  // the __proto__ accessor is gone
   1380 // TODO(adamk): Add tests for objects with hidden prototypes
   1381 // once we support observing the global object.
   1382 Object.deliverChangeRecords(observer.callback);
   1383 observer.assertCallbackRecords([
   1384   { object: obj, name: '__proto__', type: 'prototype',
   1385     oldValue: Object.prototype },
   1386   { object: obj, name: '__proto__', type: 'prototype', oldValue: p },
   1387   { object: obj, name: '__proto__', type: 'new' },
   1388 ]);
   1389 
   1390 
   1391 // Function.prototype
   1392 reset();
   1393 var fun = function(){};
   1394 Object.observe(fun, observer.callback);
   1395 var myproto = {foo: 'bar'};
   1396 fun.prototype = myproto;
   1397 fun.prototype = 7;
   1398 fun.prototype = 7;  // ignored
   1399 Object.defineProperty(fun, 'prototype', {value: 8});
   1400 Object.deliverChangeRecords(observer.callback);
   1401 observer.assertRecordCount(3);
   1402 // Manually examine the first record in order to test
   1403 // lazy creation of oldValue
   1404 assertSame(fun, observer.records[0].object);
   1405 assertEquals('prototype', observer.records[0].name);
   1406 assertEquals('updated', observer.records[0].type);
   1407 // The only existing reference to the oldValue object is in this
   1408 // record, so to test that lazy creation happened correctly
   1409 // we compare its constructor to our function (one of the invariants
   1410 // ensured when creating an object via AllocateFunctionPrototype).
   1411 assertSame(fun, observer.records[0].oldValue.constructor);
   1412 observer.records.splice(0, 1);
   1413 observer.assertCallbackRecords([
   1414   { object: fun, name: 'prototype', type: 'updated', oldValue: myproto },
   1415   { object: fun, name: 'prototype', type: 'updated', oldValue: 7 },
   1416 ]);
   1417 
   1418 // Function.prototype should not be observable except on the object itself
   1419 reset();
   1420 var fun = function(){};
   1421 var obj = { __proto__: fun };
   1422 Object.observe(obj, observer.callback);
   1423 obj.prototype = 7;
   1424 Object.deliverChangeRecords(observer.callback);
   1425 observer.assertNotCalled();
   1426 
   1427 
   1428 // Check that changes in observation status are detected in all IC states and
   1429 // in optimized code, especially in cases usually using fast elements.
   1430 var mutation = [
   1431   "a[i] = v",
   1432   "a[i] ? ++a[i] : a[i] = v",
   1433   "a[i] ? a[i]++ : a[i] = v",
   1434   "a[i] ? a[i] += 1 : a[i] = v",
   1435   "a[i] ? a[i] -= -1 : a[i] = v",
   1436 ];
   1437 
   1438 var props = [1, "1", "a"];
   1439 
   1440 function TestFastElements(prop, mutation, prepopulate, polymorphic, optimize) {
   1441   var setElement = eval(
   1442     "(function setElement(a, i, v) { " + mutation + "; " +
   1443     "/* " + [].join.call(arguments, " ") + " */" +
   1444     "})"
   1445   );
   1446   print("TestFastElements:", setElement);
   1447 
   1448   var arr = prepopulate ? [1, 2, 3, 4, 5] : [0];
   1449   if (prepopulate) arr[prop] = 2;  // for non-element case
   1450   setElement(arr, prop, 3);
   1451   setElement(arr, prop, 4);
   1452   if (polymorphic) setElement(["M", "i", "l", "n", "e", "r"], 0, "m");
   1453   if (optimize) %OptimizeFunctionOnNextCall(setElement);
   1454   setElement(arr, prop, 5);
   1455 
   1456   reset();
   1457   Object.observe(arr, observer.callback);
   1458   setElement(arr, prop, 989898);
   1459   Object.deliverChangeRecords(observer.callback);
   1460   observer.assertCallbackRecords([
   1461     { object: arr, name: "" + prop, type: 'updated', oldValue: 5 }
   1462   ]);
   1463 }
   1464 
   1465 for (var b1 = 0; b1 < 2; ++b1)
   1466   for (var b2 = 0; b2 < 2; ++b2)
   1467     for (var b3 = 0; b3 < 2; ++b3)
   1468       for (var i in props)
   1469         for (var j in mutation)
   1470           TestFastElements(props[i], mutation[j], b1 != 0, b2 != 0, b3 != 0);
   1471 
   1472 
   1473 var mutation = [
   1474   "a.length = v",
   1475   "a.length += newSize - oldSize",
   1476   "a.length -= oldSize - newSize",
   1477 ];
   1478 
   1479 var mutationByIncr = [
   1480   "++a.length",
   1481   "a.length++",
   1482 ];
   1483 
   1484 function TestFastElementsLength(
   1485   mutation, polymorphic, optimize, oldSize, newSize) {
   1486   var setLength = eval(
   1487     "(function setLength(a, v) { " + mutation + "; " +
   1488     "/* " + [].join.call(arguments, " ") + " */"
   1489     + "})"
   1490   );
   1491   print("TestFastElementsLength:", setLength);
   1492 
   1493   function array(n) {
   1494     var arr = new Array(n);
   1495     for (var i = 0; i < n; ++i) arr[i] = i;
   1496     return arr;
   1497   }
   1498 
   1499   setLength(array(oldSize), newSize);
   1500   setLength(array(oldSize), newSize);
   1501   if (polymorphic) setLength(array(oldSize).map(isNaN), newSize);
   1502   if (optimize) %OptimizeFunctionOnNextCall(setLength);
   1503   setLength(array(oldSize), newSize);
   1504 
   1505   reset();
   1506   var arr = array(oldSize);
   1507   Object.observe(arr, observer.callback);
   1508   setLength(arr, newSize);
   1509   Object.deliverChangeRecords(observer.callback);
   1510   if (oldSize === newSize) {
   1511     observer.assertNotCalled();
   1512   } else {
   1513     var count = oldSize > newSize ? oldSize - newSize : 0;
   1514     observer.assertRecordCount(count + 1);
   1515     var lengthRecord = observer.records[count];
   1516     assertSame(arr, lengthRecord.object);
   1517     assertEquals('length', lengthRecord.name);
   1518     assertEquals('updated', lengthRecord.type);
   1519     assertSame(oldSize, lengthRecord.oldValue);
   1520   }
   1521 }
   1522 
   1523 for (var b1 = 0; b1 < 2; ++b1)
   1524   for (var b2 = 0; b2 < 2; ++b2)
   1525     for (var n1 = 0; n1 < 3; ++n1)
   1526       for (var n2 = 0; n2 < 3; ++n2)
   1527         for (var i in mutation)
   1528           TestFastElementsLength(mutation[i], b1 != 0, b2 != 0, 20*n1, 20*n2);
   1529 
   1530 for (var b1 = 0; b1 < 2; ++b1)
   1531   for (var b2 = 0; b2 < 2; ++b2)
   1532     for (var n = 0; n < 3; ++n)
   1533       for (var i in mutationByIncr)
   1534         TestFastElementsLength(mutationByIncr[i], b1 != 0, b2 != 0, 7*n, 7*n+1);
   1535