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