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