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