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