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 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(this, observer.callback); }, 116 TypeError); 117 assertThrows(function() { Object.observe(obj, nonFunction); }, TypeError); 118 assertThrows(function() { Object.observe(obj, frozenFunction); }, TypeError); 119 assertEquals(obj, Object.observe(obj, observer.callback, [1])); 120 assertEquals(obj, Object.observe(obj, observer.callback, [true])); 121 assertEquals(obj, Object.observe(obj, observer.callback, ['foo', null])); 122 assertEquals(obj, Object.observe(obj, observer.callback, [undefined])); 123 assertEquals(obj, Object.observe(obj, observer.callback, 124 ['foo', 'bar', 'baz'])); 125 assertEquals(obj, Object.observe(obj, observer.callback, [])); 126 assertEquals(obj, Object.observe(obj, observer.callback, undefined)); 127 assertEquals(obj, Object.observe(obj, observer.callback)); 128 129 // Object.unobserve 130 assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError); 131 assertThrows(function() { Object.unobserve(this, observer.callback); }, 132 TypeError); 133 assertThrows(function() { Object.unobserve(obj, nonFunction); }, TypeError); 134 assertEquals(obj, Object.unobserve(obj, observer.callback)); 135 136 137 // Object.getNotifier 138 var notifier = Object.getNotifier(obj); 139 assertSame(notifier, Object.getNotifier(obj)); 140 assertEquals(null, Object.getNotifier(Object.freeze({}))); 141 assertThrows(function() { Object.getNotifier(this) }, TypeError); 142 assertFalse(notifier.hasOwnProperty('notify')); 143 assertEquals([], Object.keys(notifier)); 144 var notifyDesc = Object.getOwnPropertyDescriptor(notifier.__proto__, 'notify'); 145 assertTrue(notifyDesc.configurable); 146 assertTrue(notifyDesc.writable); 147 assertFalse(notifyDesc.enumerable); 148 assertThrows(function() { notifier.notify({}); }, TypeError); 149 assertThrows(function() { notifier.notify({ type: 4 }); }, TypeError); 150 151 assertThrows(function() { notifier.performChange(1, function(){}); }, TypeError); 152 assertThrows(function() { notifier.performChange(undefined, function(){}); }, TypeError); 153 assertThrows(function() { notifier.performChange('foo', undefined); }, TypeError); 154 assertThrows(function() { notifier.performChange('foo', 'bar'); }, TypeError); 155 var global = this; 156 notifier.performChange('foo', function() { 157 assertEquals(global, this); 158 }); 159 160 var notify = notifier.notify; 161 assertThrows(function() { notify.call(undefined, { type: 'a' }); }, TypeError); 162 assertThrows(function() { notify.call(null, { type: 'a' }); }, TypeError); 163 assertThrows(function() { notify.call(5, { type: 'a' }); }, TypeError); 164 assertThrows(function() { notify.call('hello', { type: 'a' }); }, TypeError); 165 assertThrows(function() { notify.call(false, { type: 'a' }); }, TypeError); 166 assertThrows(function() { notify.call({}, { type: 'a' }); }, TypeError); 167 assertFalse(recordCreated); 168 notifier.notify(changeRecordWithAccessor); 169 assertFalse(recordCreated); // not observed yet 170 171 172 // Object.deliverChangeRecords 173 assertThrows(function() { Object.deliverChangeRecords(nonFunction); }, TypeError); 174 175 Object.observe(obj, observer.callback); 176 177 178 // notify uses to [[CreateOwnProperty]] to create changeRecord; 179 reset(); 180 var protoExpandoAccessed = false; 181 Object.defineProperty(Object.prototype, 'protoExpando', 182 { 183 configurable: true, 184 set: function() { protoExpandoAccessed = true; } 185 } 186 ); 187 notifier.notify({ type: 'foo', protoExpando: 'val'}); 188 assertFalse(protoExpandoAccessed); 189 delete Object.prototype.protoExpando; 190 Object.deliverChangeRecords(observer.callback); 191 192 193 // Multiple records are delivered. 194 reset(); 195 notifier.notify({ 196 type: 'update', 197 name: 'foo', 198 expando: 1 199 }); 200 201 notifier.notify({ 202 object: notifier, // object property is ignored 203 type: 'delete', 204 name: 'bar', 205 expando2: 'str' 206 }); 207 Object.deliverChangeRecords(observer.callback); 208 observer.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 214 reset(); 215 Object.observe(obj, observer.callback, [true, 1, null, undefined]); 216 notifier = Object.getNotifier(obj); 217 notifier.notify({ type: 'true' }); 218 notifier.notify({ type: 'false' }); 219 notifier.notify({ type: '1' }); 220 notifier.notify({ type: '-1' }); 221 notifier.notify({ type: 'null' }); 222 notifier.notify({ type: 'nill' }); 223 notifier.notify({ type: 'undefined' }); 224 notifier.notify({ type: 'defined' }); 225 Object.deliverChangeRecords(observer.callback); 226 observer.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 234 reset(); 235 Object.deliverChangeRecords(observer.callback); 236 observer.assertNotCalled(); 237 238 239 // Multiple observation has no effect. 240 reset(); 241 Object.observe(obj, observer.callback); 242 Object.observe(obj, observer.callback); 243 Object.getNotifier(obj).notify({ 244 type: 'update', 245 }); 246 Object.deliverChangeRecords(observer.callback); 247 observer.assertCalled(); 248 249 250 // Observation can be stopped. 251 reset(); 252 Object.unobserve(obj, observer.callback); 253 Object.getNotifier(obj).notify({ 254 type: 'update', 255 }); 256 Object.deliverChangeRecords(observer.callback); 257 observer.assertNotCalled(); 258 259 260 // Multiple unobservation has no effect 261 reset(); 262 Object.unobserve(obj, observer.callback); 263 Object.unobserve(obj, observer.callback); 264 Object.getNotifier(obj).notify({ 265 type: 'update', 266 }); 267 Object.deliverChangeRecords(observer.callback); 268 observer.assertNotCalled(); 269 270 271 // Re-observation works and only includes changeRecords after of call. 272 reset(); 273 Object.getNotifier(obj).notify({ 274 type: 'update', 275 }); 276 Object.observe(obj, observer.callback); 277 Object.getNotifier(obj).notify({ 278 type: 'update', 279 }); 280 records = undefined; 281 Object.deliverChangeRecords(observer.callback); 282 observer.assertRecordCount(1); 283 284 // Get notifier prior to observing 285 reset(); 286 var obj = {}; 287 Object.getNotifier(obj); 288 Object.observe(obj, observer.callback); 289 obj.id = 1; 290 Object.deliverChangeRecords(observer.callback); 291 observer.assertCallbackRecords([ 292 { object: obj, type: 'add', name: 'id' }, 293 ]); 294 295 // The empty-string property is observable 296 reset(); 297 var obj = {}; 298 Object.observe(obj, observer.callback); 299 obj[''] = ''; 300 obj[''] = ' '; 301 delete obj['']; 302 Object.deliverChangeRecords(observer.callback); 303 observer.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 310 reset(); 311 var obj = { foo: 'bar'}; 312 Object.observe(obj, observer.callback); 313 obj.baz = 'bat'; 314 Object.preventExtensions(obj); 315 316 Object.deliverChangeRecords(observer.callback); 317 observer.assertCallbackRecords([ 318 { object: obj, type: 'add', name: 'baz' }, 319 { object: obj, type: 'preventExtensions' }, 320 ]); 321 322 reset(); 323 var obj = { foo: 'bar'}; 324 Object.preventExtensions(obj); 325 Object.observe(obj, observer.callback); 326 Object.preventExtensions(obj); 327 Object.deliverChangeRecords(observer.callback); 328 observer.assertNotCalled(); 329 330 // Object.freeze 331 reset(); 332 var obj = { a: 'a' }; 333 Object.defineProperty(obj, 'b', { 334 writable: false, 335 configurable: true, 336 value: 'b' 337 }); 338 Object.defineProperty(obj, 'c', { 339 writable: true, 340 configurable: false, 341 value: 'c' 342 }); 343 Object.defineProperty(obj, 'd', { 344 writable: false, 345 configurable: false, 346 value: 'd' 347 }); 348 Object.observe(obj, observer.callback); 349 Object.freeze(obj); 350 351 Object.deliverChangeRecords(observer.callback); 352 observer.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 359 reset(); 360 var obj = { foo: 'bar'}; 361 Object.freeze(obj); 362 Object.observe(obj, observer.callback); 363 Object.freeze(obj); 364 Object.deliverChangeRecords(observer.callback); 365 observer.assertNotCalled(); 366 367 // Object.seal 368 reset(); 369 var obj = { a: 'a' }; 370 Object.defineProperty(obj, 'b', { 371 writable: false, 372 configurable: true, 373 value: 'b' 374 }); 375 Object.defineProperty(obj, 'c', { 376 writable: true, 377 configurable: false, 378 value: 'c' 379 }); 380 Object.defineProperty(obj, 'd', { 381 writable: false, 382 configurable: false, 383 value: 'd' 384 }); 385 Object.observe(obj, observer.callback); 386 Object.seal(obj); 387 388 Object.deliverChangeRecords(observer.callback); 389 observer.assertCallbackRecords([ 390 { object: obj, type: 'preventExtensions' }, 391 { object: obj, type: 'reconfigure', name: 'a' }, 392 { object: obj, type: 'reconfigure', name: 'b' }, 393 ]); 394 395 reset(); 396 var obj = { foo: 'bar'}; 397 Object.seal(obj); 398 Object.observe(obj, observer.callback); 399 Object.seal(obj); 400 Object.deliverChangeRecords(observer.callback); 401 observer.assertNotCalled(); 402 403 // Observing a continuous stream of changes, while itermittantly unobserving. 404 reset(); 405 var obj = {}; 406 Object.observe(obj, observer.callback); 407 Object.getNotifier(obj).notify({ 408 type: 'update', 409 val: 1 410 }); 411 412 Object.unobserve(obj, observer.callback); 413 Object.getNotifier(obj).notify({ 414 type: 'update', 415 val: 2 416 }); 417 418 Object.observe(obj, observer.callback); 419 Object.getNotifier(obj).notify({ 420 type: 'update', 421 val: 3 422 }); 423 424 Object.unobserve(obj, observer.callback); 425 Object.getNotifier(obj).notify({ 426 type: 'update', 427 val: 4 428 }); 429 430 Object.observe(obj, observer.callback); 431 Object.getNotifier(obj).notify({ 432 type: 'update', 433 val: 5 434 }); 435 436 Object.unobserve(obj, observer.callback); 437 Object.deliverChangeRecords(observer.callback); 438 observer.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 445 reset(); 446 Object.observe(obj, observer.callback, ['somethingElse']); 447 Object.getNotifier(obj).notify({ 448 type: 'add' 449 }); 450 Object.getNotifier(obj).notify({ 451 type: 'update' 452 }); 453 Object.getNotifier(obj).notify({ 454 type: 'delete' 455 }); 456 Object.getNotifier(obj).notify({ 457 type: 'reconfigure' 458 }); 459 Object.getNotifier(obj).notify({ 460 type: 'setPrototype' 461 }); 462 Object.deliverChangeRecords(observer.callback); 463 observer.assertNotCalled(); 464 465 reset(); 466 Object.observe(obj, observer.callback, ['add', 'delete', 'setPrototype']); 467 Object.getNotifier(obj).notify({ 468 type: 'add' 469 }); 470 Object.getNotifier(obj).notify({ 471 type: 'update' 472 }); 473 Object.getNotifier(obj).notify({ 474 type: 'delete' 475 }); 476 Object.getNotifier(obj).notify({ 477 type: 'delete' 478 }); 479 Object.getNotifier(obj).notify({ 480 type: 'reconfigure' 481 }); 482 Object.getNotifier(obj).notify({ 483 type: 'setPrototype' 484 }); 485 Object.deliverChangeRecords(observer.callback); 486 observer.assertCallbackRecords([ 487 { object: obj, type: 'add' }, 488 { object: obj, type: 'delete' }, 489 { object: obj, type: 'delete' }, 490 { object: obj, type: 'setPrototype' } 491 ]); 492 493 reset(); 494 Object.observe(obj, observer.callback, ['update', 'foo']); 495 Object.getNotifier(obj).notify({ 496 type: 'add' 497 }); 498 Object.getNotifier(obj).notify({ 499 type: 'update' 500 }); 501 Object.getNotifier(obj).notify({ 502 type: 'delete' 503 }); 504 Object.getNotifier(obj).notify({ 505 type: 'foo' 506 }); 507 Object.getNotifier(obj).notify({ 508 type: 'bar' 509 }); 510 Object.getNotifier(obj).notify({ 511 type: 'foo' 512 }); 513 Object.deliverChangeRecords(observer.callback); 514 observer.assertCallbackRecords([ 515 { object: obj, type: 'update' }, 516 { object: obj, type: 'foo' }, 517 { object: obj, type: 'foo' } 518 ]); 519 520 reset(); 521 function Thingy(a, b, c) { 522 this.a = a; 523 this.b = b; 524 } 525 526 Thingy.MULTIPLY = 'multiply'; 527 Thingy.INCREMENT = 'increment'; 528 Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply'; 529 530 Thingy.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 575 Thingy.observe = function(thingy, callback) { 576 Object.observe(thingy, callback, [Thingy.INCREMENT, 577 Thingy.MULTIPLY, 578 Thingy.INCREMENT_AND_MULTIPLY, 579 'update']); 580 } 581 582 Thingy.unobserve = function(thingy, callback) { 583 Object.unobserve(thingy); 584 } 585 586 var thingy = new Thingy(2, 4); 587 588 Object.observe(thingy, observer.callback); 589 Thingy.observe(thingy, observer2.callback); 590 thingy.increment(3); // { a: 5, b: 7 } 591 thingy.b++; // { a: 5, b: 8 } 592 thingy.multiply(2); // { a: 10, b: 16 } 593 thingy.a++; // { a: 11, b: 16 } 594 thingy.incrementAndMultiply(2, 2); // { a: 26, b: 36 } 595 596 Object.deliverChangeRecords(observer.callback); 597 Object.deliverChangeRecords(observer2.callback); 598 observer.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 ]); 610 observer2.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 624 reset(); 625 626 function pushMultiple(arr) { 627 arr.push('a'); 628 arr.push('b'); 629 arr.push('c'); 630 } 631 632 for (var i = 0; i < 5; i++) { 633 var arr = []; 634 pushMultiple(arr); 635 } 636 637 for (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 656 reset(); 657 658 function popMultiple(arr) { 659 arr.pop(); 660 arr.pop(); 661 arr.pop(); 662 } 663 664 for (var i = 0; i < 5; i++) { 665 var arr = ['a', 'b', 'c']; 666 popMultiple(arr); 667 } 668 669 for (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 687 reset(); 688 function RecursiveThingy() {} 689 690 RecursiveThingy.MULTIPLY_FIRST_N = 'multiplyFirstN'; 691 692 RecursiveThingy.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 713 RecursiveThingy.observe = function(thingy, callback) { 714 Object.observe(thingy, callback, [RecursiveThingy.MULTIPLY_FIRST_N]); 715 } 716 717 RecursiveThingy.unobserve = function(thingy, callback) { 718 Object.unobserve(thingy); 719 } 720 721 var thingy = new RecursiveThingy; 722 thingy.push(1, 2, 3, 4); 723 724 Object.observe(thingy, observer.callback); 725 RecursiveThingy.observe(thingy, observer2.callback); 726 thingy.multiplyFirstN(2, 3); // [2, 4, 6, 4] 727 728 Object.deliverChangeRecords(observer.callback); 729 Object.deliverChangeRecords(observer2.callback); 730 observer.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 ]); 735 observer2.assertCallbackRecords([ 736 { object: thingy, type: RecursiveThingy.MULTIPLY_FIRST_N, multiplied: 2, n: 3 } 737 ]); 738 739 reset(); 740 function DeckSuit() { 741 this.push('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'A', 'Q', 'K'); 742 } 743 744 DeckSuit.SHUFFLE = 'shuffle'; 745 746 DeckSuit.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 771 DeckSuit.observe = function(thingy, callback) { 772 Object.observe(thingy, callback, [DeckSuit.SHUFFLE]); 773 } 774 775 DeckSuit.unobserve = function(thingy, callback) { 776 Object.unobserve(thingy); 777 } 778 779 var deck = new DeckSuit; 780 781 DeckSuit.observe(deck, observer2.callback); 782 deck.shuffle(); 783 784 Object.deliverChangeRecords(observer2.callback); 785 observer2.assertCallbackRecords([ 786 { object: deck, type: DeckSuit.SHUFFLE } 787 ]); 788 789 // Observing multiple objects; records appear in order. 790 reset(); 791 var obj2 = {}; 792 var obj3 = {} 793 Object.observe(obj, observer.callback); 794 Object.observe(obj3, observer.callback); 795 Object.observe(obj2, observer.callback); 796 Object.getNotifier(obj).notify({ 797 type: 'add', 798 }); 799 Object.getNotifier(obj2).notify({ 800 type: 'update', 801 }); 802 Object.getNotifier(obj3).notify({ 803 type: 'delete', 804 }); 805 Object.observe(obj3, observer.callback); 806 Object.deliverChangeRecords(observer.callback); 807 observer.assertCallbackRecords([ 808 { object: obj, type: 'add' }, 809 { object: obj2, type: 'update' }, 810 { object: obj3, type: 'delete' } 811 ]); 812 813 814 // Recursive observation. 815 var obj = {a: 1}; 816 var callbackCount = 0; 817 function recursiveObserver(r) { 818 assertEquals(1, r.length); 819 ++callbackCount; 820 if (r[0].oldValue < 100) ++obj[r[0].name]; 821 } 822 Object.observe(obj, recursiveObserver); 823 ++obj.a; 824 Object.deliverChangeRecords(recursiveObserver); 825 assertEquals(100, callbackCount); 826 827 var obj1 = {a: 1}; 828 var obj2 = {a: 1}; 829 var recordCount = 0; 830 function recursiveObserver2(r) { 831 recordCount += r.length; 832 if (r[0].oldValue < 100) { 833 ++obj1.a; 834 ++obj2.a; 835 } 836 } 837 Object.observe(obj1, recursiveObserver2); 838 Object.observe(obj2, recursiveObserver2); 839 ++obj1.a; 840 Object.deliverChangeRecords(recursiveObserver2); 841 assertEquals(199, recordCount); 842 843 844 // Observing named properties. 845 reset(); 846 var obj = {a: 1} 847 Object.observe(obj, observer.callback); 848 obj.a = 2; 849 obj["a"] = 3; 850 delete obj.a; 851 obj.a = 4; 852 obj.a = 4; // ignored 853 obj.a = 5; 854 Object.defineProperty(obj, "a", {value: 6}); 855 Object.defineProperty(obj, "a", {writable: false}); 856 obj.a = 7; // ignored 857 Object.defineProperty(obj, "a", {value: 8}); 858 Object.defineProperty(obj, "a", {value: 7, writable: true}); 859 Object.defineProperty(obj, "a", {get: function() {}}); 860 Object.defineProperty(obj, "a", {get: frozenFunction}); 861 Object.defineProperty(obj, "a", {get: frozenFunction}); // ignored 862 Object.defineProperty(obj, "a", {get: frozenFunction, set: frozenFunction}); 863 Object.defineProperty(obj, "a", {set: frozenFunction}); // ignored 864 Object.defineProperty(obj, "a", {get: undefined, set: frozenFunction}); 865 delete obj.a; 866 delete obj.a; 867 Object.defineProperty(obj, "a", {get: function() {}, configurable: true}); 868 Object.defineProperty(obj, "a", {value: 9, writable: true}); 869 obj.a = 10; 870 ++obj.a; 871 obj.a++; 872 obj.a *= 3; 873 delete obj.a; 874 Object.defineProperty(obj, "a", {value: 11, configurable: true}); 875 Object.deliverChangeRecords(observer.callback); 876 observer.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. 903 reset(); 904 var obj = {'1': 1} 905 Object.observe(obj, observer.callback); 906 obj[1] = 2; 907 obj[1] = 3; 908 delete obj[1]; 909 obj[1] = 4; 910 obj[1] = 4; // ignored 911 obj[1] = 5; 912 Object.defineProperty(obj, "1", {value: 6}); 913 Object.defineProperty(obj, "1", {writable: false}); 914 obj[1] = 7; // ignored 915 Object.defineProperty(obj, "1", {value: 8}); 916 Object.defineProperty(obj, "1", {value: 7, writable: true}); 917 Object.defineProperty(obj, "1", {get: function() {}}); 918 Object.defineProperty(obj, "1", {get: frozenFunction}); 919 Object.defineProperty(obj, "1", {get: frozenFunction}); // ignored 920 Object.defineProperty(obj, "1", {get: frozenFunction, set: frozenFunction}); 921 Object.defineProperty(obj, "1", {set: frozenFunction}); // ignored 922 Object.defineProperty(obj, "1", {get: undefined, set: frozenFunction}); 923 delete obj[1]; 924 delete obj[1]; 925 Object.defineProperty(obj, "1", {get: function() {}, configurable: true}); 926 Object.defineProperty(obj, "1", {value: 9, writable: true}); 927 obj[1] = 10; 928 ++obj[1]; 929 obj[1]++; 930 obj[1] *= 3; 931 delete obj[1]; 932 Object.defineProperty(obj, "1", {value: 11, configurable: true}); 933 Object.deliverChangeRecords(observer.callback); 934 observer.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). 961 print("*****") 962 reset(); 963 var obj = {} 964 var symbol = Symbol("secret"); 965 Object.observe(obj, observer.callback); 966 obj[symbol] = 3; 967 delete obj[symbol]; 968 Object.defineProperty(obj, symbol, {get: function() {}, configurable: true}); 969 Object.defineProperty(obj, symbol, {value: 6}); 970 Object.defineProperty(obj, symbol, {writable: false}); 971 delete obj[symbol]; 972 Object.defineProperty(obj, symbol, {value: 7}); 973 ++obj[symbol]; 974 obj[symbol]++; 975 obj[symbol] *= 3; 976 delete obj[symbol]; 977 obj.__defineSetter__(symbol, function() {}); 978 obj.__defineGetter__(symbol, function() {}); 979 Object.deliverChangeRecords(observer.callback); 980 observer.assertNotCalled(); 981 982 983 // Test all kinds of objects generically. 984 function 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 1055 function 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 /* 1083 function 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 1124 var 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 ]; 1139 var properties = ["a", "1", 1, "length", "setPrototype", "name", "caller"]; 1140 1141 // Cases that yield non-standard results. 1142 function 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 1150 for (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) 1164 reset(); 1165 var arr = ['a', 'b', 'c', 'd']; 1166 var arr2 = ['alpha', 'beta']; 1167 var arr3 = ['hello']; 1168 arr3[2] = 'goodbye'; 1169 arr3.length = 6; 1170 Object.defineProperty(arr, '0', {configurable: false}); 1171 Object.defineProperty(arr, '2', {get: function(){}}); 1172 Object.defineProperty(arr2, '0', {get: function(){}, configurable: false}); 1173 Object.observe(arr, observer.callback); 1174 Array.observe(arr, observer2.callback); 1175 Object.observe(arr2, observer.callback); 1176 Array.observe(arr2, observer2.callback); 1177 Object.observe(arr3, observer.callback); 1178 Array.observe(arr3, observer2.callback); 1179 arr.length = 2; 1180 arr.length = 0; 1181 arr.length = 10; 1182 Object.defineProperty(arr, 'length', {writable: false}); 1183 arr2.length = 0; 1184 arr2.length = 1; // no change expected 1185 Object.defineProperty(arr2, 'length', {value: 1, writable: false}); 1186 arr3.length = 0; 1187 ++arr3.length; 1188 arr3.length++; 1189 arr3.length /= 2; 1190 Object.defineProperty(arr3, 'length', {value: 5}); 1191 arr3[4] = 5; 1192 Object.defineProperty(arr3, 'length', {value: 1, writable: false}); 1193 Object.deliverChangeRecords(observer.callback); 1194 observer.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 ]); 1221 Object.deliverChangeRecords(observer2.callback); 1222 observer2.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 1238 reset(); 1239 var slow_arr = %NormalizeElements([]); 1240 slow_arr[500000000] = 'hello'; 1241 slow_arr.length = 1000000000; 1242 Object.observe(slow_arr, observer.callback); 1243 var spliceRecords; 1244 function slowSpliceCallback(records) { 1245 spliceRecords = records; 1246 } 1247 Array.observe(slow_arr, slowSpliceCallback); 1248 slow_arr.length = 100; 1249 Object.deliverChangeRecords(observer.callback); 1250 observer.assertCallbackRecords([ 1251 { object: slow_arr, name: '500000000', type: 'delete', oldValue: 'hello' }, 1252 { object: slow_arr, name: 'length', type: 'update', oldValue: 1000000000 }, 1253 ]); 1254 Object.deliverChangeRecords(slowSpliceCallback); 1255 assertEquals(spliceRecords.length, 1); 1256 // Have to custom assert this splice record because the removed array is huge. 1257 var splice = spliceRecords[0]; 1258 assertSame(splice.object, slow_arr); 1259 assertEquals(splice.type, 'splice'); 1260 assertEquals(splice.index, 100); 1261 assertEquals(splice.addedCount, 0); 1262 var array_keys = %GetArrayKeys(splice.removed, splice.removed.length); 1263 assertEquals(array_keys.length, 1); 1264 assertEquals(array_keys[0], 499999900); 1265 assertEquals(splice.removed[499999900], 'hello'); 1266 assertEquals(splice.removed.length, 999999900); 1267 1268 1269 // Assignments in loops (checking different IC states). 1270 reset(); 1271 var obj = {}; 1272 Object.observe(obj, observer.callback); 1273 for (var i = 0; i < 5; i++) { 1274 obj["a" + i] = i; 1275 } 1276 Object.deliverChangeRecords(observer.callback); 1277 observer.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 1285 reset(); 1286 var obj = {}; 1287 Object.observe(obj, observer.callback); 1288 for (var i = 0; i < 5; i++) { 1289 obj[i] = i; 1290 } 1291 Object.deliverChangeRecords(observer.callback); 1292 observer.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. 1303 reset(); 1304 var arr = [1, 2, 3]; 1305 Object.observe(arr, observer.callback); 1306 Array.observe(arr, observer2.callback); 1307 arr[3] = 10; 1308 arr[100] = 20; 1309 Object.defineProperty(arr, '200', {value: 7}); 1310 Object.defineProperty(arr, '400', {get: function(){}}); 1311 arr[50] = 30; // no length change expected 1312 Object.deliverChangeRecords(observer.callback); 1313 observer.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 ]); 1324 Object.deliverChangeRecords(observer2.callback); 1325 observer2.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 1339 reset(); 1340 var array = [1, 2]; 1341 Object.observe(array, observer.callback); 1342 Array.observe(array, observer2.callback); 1343 array.push(3, 4); 1344 array.push(5); 1345 Object.deliverChangeRecords(observer.callback); 1346 observer.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 ]); 1354 Object.deliverChangeRecords(observer2.callback); 1355 observer2.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 1361 reset(); 1362 var array = [1, 2]; 1363 Object.observe(array, observer.callback); 1364 array.pop(); 1365 array.pop(); 1366 Object.deliverChangeRecords(observer.callback); 1367 observer.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 1375 reset(); 1376 var array = [1, 2]; 1377 Object.observe(array, observer.callback); 1378 array.shift(); 1379 array.shift(); 1380 Object.deliverChangeRecords(observer.callback); 1381 observer.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 1390 reset(); 1391 var array = [1, 2]; 1392 Object.observe(array, observer.callback); 1393 array.unshift(3, 4); 1394 Object.deliverChangeRecords(observer.callback); 1395 observer.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 1404 reset(); 1405 var array = [1, 2, 3]; 1406 Object.observe(array, observer.callback); 1407 array.splice(1, 1, 4, 5); 1408 Object.deliverChangeRecords(observer.callback); 1409 observer.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 1417 reset(); 1418 var array = [3, 2, 1]; 1419 Object.observe(array, observer.callback); 1420 array.sort(); 1421 assertEquals(1, array[0]); 1422 assertEquals(2, array[1]); 1423 assertEquals(3, array[2]); 1424 Object.deliverChangeRecords(observer.callback); 1425 observer.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 1434 function 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 } 1441 MockArray.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 1452 reset(); 1453 var array = new MockArray([], observer); 1454 Object.observe(array, observer.callback, ['lengthChange']); 1455 Array.prototype.push.call(array, 1); 1456 Object.deliverChangeRecords(observer.callback); 1457 observer.assertCallbackRecords([ 1458 { object: array, type: 'lengthChange' }, 1459 { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }, 1460 ]); 1461 1462 reset(); 1463 var array = new MockArray([1], observer); 1464 Object.observe(array, observer.callback, ['lengthChange']); 1465 Array.prototype.pop.call(array); 1466 Object.deliverChangeRecords(observer.callback); 1467 observer.assertCallbackRecords([ 1468 { object: array, type: 'lengthChange' }, 1469 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }, 1470 ]); 1471 1472 reset(); 1473 var array = new MockArray([1], observer); 1474 Object.observe(array, observer.callback, ['lengthChange']); 1475 Array.prototype.shift.call(array); 1476 Object.deliverChangeRecords(observer.callback); 1477 observer.assertCallbackRecords([ 1478 { object: array, type: 'lengthChange' }, 1479 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }, 1480 ]); 1481 1482 reset(); 1483 var array = new MockArray([], observer); 1484 Object.observe(array, observer.callback, ['lengthChange']); 1485 Array.prototype.unshift.call(array, 1); 1486 Object.deliverChangeRecords(observer.callback); 1487 observer.assertCallbackRecords([ 1488 { object: array, type: 'lengthChange' }, 1489 { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }, 1490 ]); 1491 1492 reset(); 1493 var array = new MockArray([0, 1, 2], observer); 1494 Object.observe(array, observer.callback, ['lengthChange']); 1495 Array.prototype.splice.call(array, 1, 1); 1496 Object.deliverChangeRecords(observer.callback); 1497 observer.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 1506 reset() 1507 var array = {0: 1, 1: 2, length: 2} 1508 Object.observe(array, observer.callback); 1509 Array.prototype.push.call(array, 3, 4); 1510 Object.deliverChangeRecords(observer.callback); 1511 observer.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 1518 reset(); 1519 var array = [1, 2]; 1520 Object.observe(array, observer.callback); 1521 Array.observe(array, observer2.callback); 1522 array.pop(); 1523 array.pop(); 1524 array.pop(); 1525 Object.deliverChangeRecords(observer.callback); 1526 observer.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 ]); 1532 Object.deliverChangeRecords(observer2.callback); 1533 observer2.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 1539 reset(); 1540 var array = [1, 2]; 1541 Object.observe(array, observer.callback); 1542 Array.observe(array, observer2.callback); 1543 array.shift(); 1544 array.shift(); 1545 array.shift(); 1546 Object.deliverChangeRecords(observer.callback); 1547 observer.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 ]); 1554 Object.deliverChangeRecords(observer2.callback); 1555 observer2.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 1561 reset(); 1562 var array = [1, 2]; 1563 Object.observe(array, observer.callback); 1564 Array.observe(array, observer2.callback); 1565 array.unshift(3, 4); 1566 array.unshift(5); 1567 Object.deliverChangeRecords(observer.callback); 1568 observer.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 ]); 1581 Object.deliverChangeRecords(observer2.callback); 1582 observer2.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 1588 reset(); 1589 var array = [1, 2, 3]; 1590 Object.observe(array, observer.callback); 1591 Array.observe(array, observer2.callback); 1592 array.splice(1, 0, 4, 5); // 1 4 5 2 3 1593 array.splice(0, 2); // 5 2 3 1594 array.splice(1, 2, 6, 7); // 5 6 7 1595 array.splice(2, 0); 1596 Object.deliverChangeRecords(observer.callback); 1597 observer.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 ]); 1614 Object.deliverChangeRecords(observer2.callback); 1615 observer2.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 1622 reset(); 1623 var dummy = {}; 1624 Object.observe(dummy, observer.callback); 1625 Object.unobserve(dummy, observer.callback); 1626 var array = [0]; 1627 Object.observe(array, observer.callback); 1628 array.splice(0, 1); 1629 Object.deliverChangeRecords(observer.callback); 1630 observer.assertCallbackRecords([ 1631 { object: array, name: '0', type: 'delete', oldValue: 0 }, 1632 { object: array, name: 'length', type: 'update', oldValue: 1}, 1633 ]); 1634 1635 1636 // __proto__ 1637 reset(); 1638 var obj = {}; 1639 Object.observe(obj, observer.callback); 1640 var p = {foo: 'yes'}; 1641 var q = {bar: 'no'}; 1642 obj.__proto__ = p; 1643 obj.__proto__ = p; // ignored 1644 obj.__proto__ = null; 1645 obj.__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. 1648 Object.deliverChangeRecords(observer.callback); 1649 observer.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 1658 reset(); 1659 var fun = function(){}; 1660 Object.observe(fun, observer.callback); 1661 var myproto = {foo: 'bar'}; 1662 fun.prototype = myproto; 1663 fun.prototype = 7; 1664 fun.prototype = 7; // ignored 1665 Object.defineProperty(fun, 'prototype', {value: 8}); 1666 Object.deliverChangeRecords(observer.callback); 1667 observer.assertRecordCount(3); 1668 // Manually examine the first record in order to test 1669 // lazy creation of oldValue 1670 assertSame(fun, observer.records[0].object); 1671 assertEquals('prototype', observer.records[0].name); 1672 assertEquals('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). 1677 assertSame(fun, observer.records[0].oldValue.constructor); 1678 observer.records.splice(0, 1); 1679 observer.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 1685 reset(); 1686 var fun = function(){}; 1687 var obj = { __proto__: fun }; 1688 Object.observe(obj, observer.callback); 1689 obj.prototype = 7; 1690 Object.deliverChangeRecords(observer.callback); 1691 observer.assertRecordCount(1); 1692 observer.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. 1698 var 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 1706 var props = [1, "1", "a"]; 1707 1708 function 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 1733 for (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 1741 var mutation = [ 1742 "a.length = v", 1743 "a.length += newSize - oldSize", 1744 "a.length -= oldSize - newSize", 1745 ]; 1746 1747 var mutationByIncr = [ 1748 "++a.length", 1749 "a.length++", 1750 ]; 1751 1752 function 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 1791 for (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 1798 for (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