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