1 // Copyright 2012 the V8 project authors. All rights reserved. 2 // Redistribution and use in source and binary forms, with or without 3 // modification, are permitted provided that the following conditions are 4 // met: 5 // 6 // * Redistributions of source code must retain the above copyright 7 // notice, this list of conditions and the following disclaimer. 8 // * Redistributions in binary form must reproduce the above 9 // copyright notice, this list of conditions and the following 10 // disclaimer in the documentation and/or other materials provided 11 // with the distribution. 12 // * Neither the name of Google Inc. nor the names of its 13 // contributors may be used to endorse or promote products derived 14 // from this software without specific prior written permission. 15 // 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28 // Flags: --harmony-proxies 29 // Flags: --allow-natives-syntax 30 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 = %NormalizeElements([]); 1238 slow_arr[500000000] = 'hello'; 1239 slow_arr.length = 1000000000; 1240 Object.observe(slow_arr, observer.callback); 1241 var spliceRecords; 1242 function slowSpliceCallback(records) { 1243 spliceRecords = records; 1244 } 1245 Array.observe(slow_arr, slowSpliceCallback); 1246 slow_arr.length = 100; 1247 Object.deliverChangeRecords(observer.callback); 1248 observer.assertCallbackRecords([ 1249 { object: slow_arr, name: '500000000', type: 'delete', oldValue: 'hello' }, 1250 { object: slow_arr, name: 'length', type: 'update', oldValue: 1000000000 }, 1251 ]); 1252 Object.deliverChangeRecords(slowSpliceCallback); 1253 assertEquals(spliceRecords.length, 1); 1254 // Have to custom assert this splice record because the removed array is huge. 1255 var splice = spliceRecords[0]; 1256 assertSame(splice.object, slow_arr); 1257 assertEquals(splice.type, 'splice'); 1258 assertEquals(splice.index, 100); 1259 assertEquals(splice.addedCount, 0); 1260 var array_keys = %GetArrayKeys(splice.removed, splice.removed.length); 1261 assertEquals(array_keys.length, 1); 1262 assertEquals(array_keys[0], 499999900); 1263 assertEquals(splice.removed[499999900], 'hello'); 1264 assertEquals(splice.removed.length, 999999900); 1265 1266 1267 // Assignments in loops (checking different IC states). 1268 reset(); 1269 var obj = {}; 1270 Object.observe(obj, observer.callback); 1271 for (var i = 0; i < 5; i++) { 1272 obj["a" + i] = i; 1273 } 1274 Object.deliverChangeRecords(observer.callback); 1275 observer.assertCallbackRecords([ 1276 { object: obj, name: "a0", type: "add" }, 1277 { object: obj, name: "a1", type: "add" }, 1278 { object: obj, name: "a2", type: "add" }, 1279 { object: obj, name: "a3", type: "add" }, 1280 { object: obj, name: "a4", type: "add" }, 1281 ]); 1282 1283 reset(); 1284 var obj = {}; 1285 Object.observe(obj, observer.callback); 1286 for (var i = 0; i < 5; i++) { 1287 obj[i] = i; 1288 } 1289 Object.deliverChangeRecords(observer.callback); 1290 observer.assertCallbackRecords([ 1291 { object: obj, name: "0", type: "add" }, 1292 { object: obj, name: "1", type: "add" }, 1293 { object: obj, name: "2", type: "add" }, 1294 { object: obj, name: "3", type: "add" }, 1295 { object: obj, name: "4", type: "add" }, 1296 ]); 1297 1298 1299 // Adding elements past the end of an array should notify on length for 1300 // Object.observe and emit "splices" for Array.observe. 1301 reset(); 1302 var arr = [1, 2, 3]; 1303 Object.observe(arr, observer.callback); 1304 Array.observe(arr, observer2.callback); 1305 arr[3] = 10; 1306 arr[100] = 20; 1307 Object.defineProperty(arr, '200', {value: 7}); 1308 Object.defineProperty(arr, '400', {get: function(){}}); 1309 arr[50] = 30; // no length change expected 1310 Object.deliverChangeRecords(observer.callback); 1311 observer.assertCallbackRecords([ 1312 { object: arr, name: '3', type: 'add' }, 1313 { object: arr, name: 'length', type: 'update', oldValue: 3 }, 1314 { object: arr, name: '100', type: 'add' }, 1315 { object: arr, name: 'length', type: 'update', oldValue: 4 }, 1316 { object: arr, name: '200', type: 'add' }, 1317 { object: arr, name: 'length', type: 'update', oldValue: 101 }, 1318 { object: arr, name: '400', type: 'add' }, 1319 { object: arr, name: 'length', type: 'update', oldValue: 201 }, 1320 { object: arr, name: '50', type: 'add' }, 1321 ]); 1322 Object.deliverChangeRecords(observer2.callback); 1323 observer2.assertCallbackRecords([ 1324 { object: arr, type: 'splice', index: 3, removed: [], addedCount: 1 }, 1325 { object: arr, type: 'splice', index: 4, removed: [], addedCount: 97 }, 1326 { object: arr, type: 'splice', index: 101, removed: [], addedCount: 100 }, 1327 { object: arr, type: 'splice', index: 201, removed: [], addedCount: 200 }, 1328 { object: arr, type: 'add', name: '50' }, 1329 ]); 1330 1331 1332 // Tests for array methods, first on arrays and then on plain objects 1333 // 1334 // === ARRAYS === 1335 // 1336 // Push 1337 reset(); 1338 var array = [1, 2]; 1339 Object.observe(array, observer.callback); 1340 Array.observe(array, observer2.callback); 1341 array.push(3, 4); 1342 array.push(5); 1343 Object.deliverChangeRecords(observer.callback); 1344 observer.assertCallbackRecords([ 1345 { object: array, name: '2', type: 'add' }, 1346 { object: array, name: 'length', type: 'update', oldValue: 2 }, 1347 { object: array, name: '3', type: 'add' }, 1348 { object: array, name: 'length', type: 'update', oldValue: 3 }, 1349 { object: array, name: '4', type: 'add' }, 1350 { object: array, name: 'length', type: 'update', oldValue: 4 }, 1351 ]); 1352 Object.deliverChangeRecords(observer2.callback); 1353 observer2.assertCallbackRecords([ 1354 { object: array, type: 'splice', index: 2, removed: [], addedCount: 2 }, 1355 { object: array, type: 'splice', index: 4, removed: [], addedCount: 1 } 1356 ]); 1357 1358 // Pop 1359 reset(); 1360 var array = [1, 2]; 1361 Object.observe(array, observer.callback); 1362 array.pop(); 1363 array.pop(); 1364 Object.deliverChangeRecords(observer.callback); 1365 observer.assertCallbackRecords([ 1366 { object: array, name: '1', type: 'delete', oldValue: 2 }, 1367 { object: array, name: 'length', type: 'update', oldValue: 2 }, 1368 { object: array, name: '0', type: 'delete', oldValue: 1 }, 1369 { object: array, name: 'length', type: 'update', oldValue: 1 }, 1370 ]); 1371 1372 // Shift 1373 reset(); 1374 var array = [1, 2]; 1375 Object.observe(array, observer.callback); 1376 array.shift(); 1377 array.shift(); 1378 Object.deliverChangeRecords(observer.callback); 1379 observer.assertCallbackRecords([ 1380 { object: array, name: '0', type: 'update', oldValue: 1 }, 1381 { object: array, name: '1', type: 'delete', oldValue: 2 }, 1382 { object: array, name: 'length', type: 'update', oldValue: 2 }, 1383 { object: array, name: '0', type: 'delete', oldValue: 2 }, 1384 { object: array, name: 'length', type: 'update', oldValue: 1 }, 1385 ]); 1386 1387 // Unshift 1388 reset(); 1389 var array = [1, 2]; 1390 Object.observe(array, observer.callback); 1391 array.unshift(3, 4); 1392 Object.deliverChangeRecords(observer.callback); 1393 observer.assertCallbackRecords([ 1394 { object: array, name: '3', type: 'add' }, 1395 { object: array, name: 'length', type: 'update', oldValue: 2 }, 1396 { object: array, name: '2', type: 'add' }, 1397 { object: array, name: '0', type: 'update', oldValue: 1 }, 1398 { object: array, name: '1', type: 'update', oldValue: 2 }, 1399 ]); 1400 1401 // Splice 1402 reset(); 1403 var array = [1, 2, 3]; 1404 Object.observe(array, observer.callback); 1405 array.splice(1, 1, 4, 5); 1406 Object.deliverChangeRecords(observer.callback); 1407 observer.assertCallbackRecords([ 1408 { object: array, name: '3', type: 'add' }, 1409 { object: array, name: 'length', type: 'update', oldValue: 3 }, 1410 { object: array, name: '1', type: 'update', oldValue: 2 }, 1411 { object: array, name: '2', type: 'update', oldValue: 3 }, 1412 ]); 1413 1414 // Sort 1415 reset(); 1416 var array = [3, 2, 1]; 1417 Object.observe(array, observer.callback); 1418 array.sort(); 1419 assertEquals(1, array[0]); 1420 assertEquals(2, array[1]); 1421 assertEquals(3, array[2]); 1422 Object.deliverChangeRecords(observer.callback); 1423 observer.assertCallbackRecords([ 1424 { object: array, name: '1', type: 'update', oldValue: 2 }, 1425 { object: array, name: '0', type: 'update', oldValue: 3 }, 1426 { object: array, name: '2', type: 'update', oldValue: 1 }, 1427 { object: array, name: '1', type: 'update', oldValue: 3 }, 1428 { object: array, name: '0', type: 'update', oldValue: 2 }, 1429 ]); 1430 1431 // Splice emitted after Array mutation methods 1432 function MockArray(initial, observer) { 1433 for (var i = 0; i < initial.length; i++) 1434 this[i] = initial[i]; 1435 1436 this.length_ = initial.length; 1437 this.observer = observer; 1438 } 1439 MockArray.prototype = { 1440 set length(length) { 1441 Object.getNotifier(this).notify({ type: 'lengthChange' }); 1442 this.length_ = length; 1443 Object.observe(this, this.observer.callback, ['splice']); 1444 }, 1445 get length() { 1446 return this.length_; 1447 } 1448 } 1449 1450 reset(); 1451 var array = new MockArray([], observer); 1452 Object.observe(array, observer.callback, ['lengthChange']); 1453 Array.prototype.push.call(array, 1); 1454 Object.deliverChangeRecords(observer.callback); 1455 observer.assertCallbackRecords([ 1456 { object: array, type: 'lengthChange' }, 1457 { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }, 1458 ]); 1459 1460 reset(); 1461 var array = new MockArray([1], observer); 1462 Object.observe(array, observer.callback, ['lengthChange']); 1463 Array.prototype.pop.call(array); 1464 Object.deliverChangeRecords(observer.callback); 1465 observer.assertCallbackRecords([ 1466 { object: array, type: 'lengthChange' }, 1467 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }, 1468 ]); 1469 1470 reset(); 1471 var array = new MockArray([1], observer); 1472 Object.observe(array, observer.callback, ['lengthChange']); 1473 Array.prototype.shift.call(array); 1474 Object.deliverChangeRecords(observer.callback); 1475 observer.assertCallbackRecords([ 1476 { object: array, type: 'lengthChange' }, 1477 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }, 1478 ]); 1479 1480 reset(); 1481 var array = new MockArray([], observer); 1482 Object.observe(array, observer.callback, ['lengthChange']); 1483 Array.prototype.unshift.call(array, 1); 1484 Object.deliverChangeRecords(observer.callback); 1485 observer.assertCallbackRecords([ 1486 { object: array, type: 'lengthChange' }, 1487 { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }, 1488 ]); 1489 1490 reset(); 1491 var array = new MockArray([0, 1, 2], observer); 1492 Object.observe(array, observer.callback, ['lengthChange']); 1493 Array.prototype.splice.call(array, 1, 1); 1494 Object.deliverChangeRecords(observer.callback); 1495 observer.assertCallbackRecords([ 1496 { object: array, type: 'lengthChange' }, 1497 { object: array, type: 'splice', index: 1, removed: [1], addedCount: 0 }, 1498 ]); 1499 1500 // 1501 // === PLAIN OBJECTS === 1502 // 1503 // Push 1504 reset() 1505 var array = {0: 1, 1: 2, length: 2} 1506 Object.observe(array, observer.callback); 1507 Array.prototype.push.call(array, 3, 4); 1508 Object.deliverChangeRecords(observer.callback); 1509 observer.assertCallbackRecords([ 1510 { object: array, name: '2', type: 'add' }, 1511 { object: array, name: '3', type: 'add' }, 1512 { object: array, name: 'length', type: 'update', oldValue: 2 }, 1513 ]); 1514 1515 // Pop 1516 reset(); 1517 var array = [1, 2]; 1518 Object.observe(array, observer.callback); 1519 Array.observe(array, observer2.callback); 1520 array.pop(); 1521 array.pop(); 1522 array.pop(); 1523 Object.deliverChangeRecords(observer.callback); 1524 observer.assertCallbackRecords([ 1525 { object: array, name: '1', type: 'delete', oldValue: 2 }, 1526 { object: array, name: 'length', type: 'update', oldValue: 2 }, 1527 { object: array, name: '0', type: 'delete', oldValue: 1 }, 1528 { object: array, name: 'length', type: 'update', oldValue: 1 }, 1529 ]); 1530 Object.deliverChangeRecords(observer2.callback); 1531 observer2.assertCallbackRecords([ 1532 { object: array, type: 'splice', index: 1, removed: [2], addedCount: 0 }, 1533 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 } 1534 ]); 1535 1536 // Shift 1537 reset(); 1538 var array = [1, 2]; 1539 Object.observe(array, observer.callback); 1540 Array.observe(array, observer2.callback); 1541 array.shift(); 1542 array.shift(); 1543 array.shift(); 1544 Object.deliverChangeRecords(observer.callback); 1545 observer.assertCallbackRecords([ 1546 { object: array, name: '0', type: 'update', oldValue: 1 }, 1547 { object: array, name: '1', type: 'delete', oldValue: 2 }, 1548 { object: array, name: 'length', type: 'update', oldValue: 2 }, 1549 { object: array, name: '0', type: 'delete', oldValue: 2 }, 1550 { object: array, name: 'length', type: 'update', oldValue: 1 }, 1551 ]); 1552 Object.deliverChangeRecords(observer2.callback); 1553 observer2.assertCallbackRecords([ 1554 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }, 1555 { object: array, type: 'splice', index: 0, removed: [2], addedCount: 0 } 1556 ]); 1557 1558 // Unshift 1559 reset(); 1560 var array = [1, 2]; 1561 Object.observe(array, observer.callback); 1562 Array.observe(array, observer2.callback); 1563 array.unshift(3, 4); 1564 array.unshift(5); 1565 Object.deliverChangeRecords(observer.callback); 1566 observer.assertCallbackRecords([ 1567 { object: array, name: '3', type: 'add' }, 1568 { object: array, name: 'length', type: 'update', oldValue: 2 }, 1569 { object: array, name: '2', type: 'add' }, 1570 { object: array, name: '0', type: 'update', oldValue: 1 }, 1571 { object: array, name: '1', type: 'update', oldValue: 2 }, 1572 { object: array, name: '4', type: 'add' }, 1573 { object: array, name: 'length', type: 'update', oldValue: 4 }, 1574 { object: array, name: '3', type: 'update', oldValue: 2 }, 1575 { object: array, name: '2', type: 'update', oldValue: 1 }, 1576 { object: array, name: '1', type: 'update', oldValue: 4 }, 1577 { object: array, name: '0', type: 'update', oldValue: 3 }, 1578 ]); 1579 Object.deliverChangeRecords(observer2.callback); 1580 observer2.assertCallbackRecords([ 1581 { object: array, type: 'splice', index: 0, removed: [], addedCount: 2 }, 1582 { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 } 1583 ]); 1584 1585 // Splice 1586 reset(); 1587 var array = [1, 2, 3]; 1588 Object.observe(array, observer.callback); 1589 Array.observe(array, observer2.callback); 1590 array.splice(1, 0, 4, 5); // 1 4 5 2 3 1591 array.splice(0, 2); // 5 2 3 1592 array.splice(1, 2, 6, 7); // 5 6 7 1593 array.splice(2, 0); 1594 Object.deliverChangeRecords(observer.callback); 1595 observer.assertCallbackRecords([ 1596 { object: array, name: '4', type: 'add' }, 1597 { object: array, name: 'length', type: 'update', oldValue: 3 }, 1598 { object: array, name: '3', type: 'add' }, 1599 { object: array, name: '1', type: 'update', oldValue: 2 }, 1600 { object: array, name: '2', type: 'update', oldValue: 3 }, 1601 1602 { object: array, name: '0', type: 'update', oldValue: 1 }, 1603 { object: array, name: '1', type: 'update', oldValue: 4 }, 1604 { object: array, name: '2', type: 'update', oldValue: 5 }, 1605 { object: array, name: '4', type: 'delete', oldValue: 3 }, 1606 { object: array, name: '3', type: 'delete', oldValue: 2 }, 1607 { object: array, name: 'length', type: 'update', oldValue: 5 }, 1608 1609 { object: array, name: '1', type: 'update', oldValue: 2 }, 1610 { object: array, name: '2', type: 'update', oldValue: 3 }, 1611 ]); 1612 Object.deliverChangeRecords(observer2.callback); 1613 observer2.assertCallbackRecords([ 1614 { object: array, type: 'splice', index: 1, removed: [], addedCount: 2 }, 1615 { object: array, type: 'splice', index: 0, removed: [1, 4], addedCount: 0 }, 1616 { object: array, type: 'splice', index: 1, removed: [2, 3], addedCount: 2 }, 1617 ]); 1618 1619 // Exercise StoreIC_ArrayLength 1620 reset(); 1621 var dummy = {}; 1622 Object.observe(dummy, observer.callback); 1623 Object.unobserve(dummy, observer.callback); 1624 var array = [0]; 1625 Object.observe(array, observer.callback); 1626 array.splice(0, 1); 1627 Object.deliverChangeRecords(observer.callback); 1628 observer.assertCallbackRecords([ 1629 { object: array, name: '0', type: 'delete', oldValue: 0 }, 1630 { object: array, name: 'length', type: 'update', oldValue: 1}, 1631 ]); 1632 1633 1634 // __proto__ 1635 reset(); 1636 var obj = {}; 1637 Object.observe(obj, observer.callback); 1638 var p = {foo: 'yes'}; 1639 var q = {bar: 'no'}; 1640 obj.__proto__ = p; 1641 obj.__proto__ = p; // ignored 1642 obj.__proto__ = null; 1643 obj.__proto__ = q; // the __proto__ accessor is gone 1644 // TODO(adamk): Add tests for objects with hidden prototypes 1645 // once we support observing the global object. 1646 Object.deliverChangeRecords(observer.callback); 1647 observer.assertCallbackRecords([ 1648 { object: obj, name: '__proto__', type: 'setPrototype', 1649 oldValue: Object.prototype }, 1650 { object: obj, name: '__proto__', type: 'setPrototype', oldValue: p }, 1651 { object: obj, name: '__proto__', type: 'add' }, 1652 ]); 1653 1654 1655 // Function.prototype 1656 reset(); 1657 var fun = function(){}; 1658 Object.observe(fun, observer.callback); 1659 var myproto = {foo: 'bar'}; 1660 fun.prototype = myproto; 1661 fun.prototype = 7; 1662 fun.prototype = 7; // ignored 1663 Object.defineProperty(fun, 'prototype', {value: 8}); 1664 Object.deliverChangeRecords(observer.callback); 1665 observer.assertRecordCount(3); 1666 // Manually examine the first record in order to test 1667 // lazy creation of oldValue 1668 assertSame(fun, observer.records[0].object); 1669 assertEquals('prototype', observer.records[0].name); 1670 assertEquals('update', observer.records[0].type); 1671 // The only existing reference to the oldValue object is in this 1672 // record, so to test that lazy creation happened correctly 1673 // we compare its constructor to our function (one of the invariants 1674 // ensured when creating an object via AllocateFunctionPrototype). 1675 assertSame(fun, observer.records[0].oldValue.constructor); 1676 observer.records.splice(0, 1); 1677 observer.assertCallbackRecords([ 1678 { object: fun, name: 'prototype', type: 'update', oldValue: myproto }, 1679 { object: fun, name: 'prototype', type: 'update', oldValue: 7 }, 1680 ]); 1681 1682 // Function.prototype should not be observable except on the object itself 1683 reset(); 1684 var fun = function(){}; 1685 var obj = { __proto__: fun }; 1686 Object.observe(obj, observer.callback); 1687 obj.prototype = 7; 1688 Object.deliverChangeRecords(observer.callback); 1689 observer.assertRecordCount(1); 1690 observer.assertCallbackRecords([ 1691 { object: obj, name: 'prototype', type: 'add' }, 1692 ]); 1693 1694 // Check that changes in observation status are detected in all IC states and 1695 // in optimized code, especially in cases usually using fast elements. 1696 var mutation = [ 1697 "a[i] = v", 1698 "a[i] ? ++a[i] : a[i] = v", 1699 "a[i] ? a[i]++ : a[i] = v", 1700 "a[i] ? a[i] += 1 : a[i] = v", 1701 "a[i] ? a[i] -= -1 : a[i] = v", 1702 ]; 1703 1704 var props = [1, "1", "a"]; 1705 1706 function TestFastElements(prop, mutation, prepopulate, polymorphic, optimize) { 1707 var setElement = eval( 1708 "(function setElement(a, i, v) { " + mutation + "; " + 1709 "/* " + [].join.call(arguments, " ") + " */" + 1710 "})" 1711 ); 1712 print("TestFastElements:", setElement); 1713 1714 var arr = prepopulate ? [1, 2, 3, 4, 5] : [0]; 1715 if (prepopulate) arr[prop] = 2; // for non-element case 1716 setElement(arr, prop, 3); 1717 setElement(arr, prop, 4); 1718 if (polymorphic) setElement(["M", "i", "l", "n", "e", "r"], 0, "m"); 1719 if (optimize) %OptimizeFunctionOnNextCall(setElement); 1720 setElement(arr, prop, 5); 1721 1722 reset(); 1723 Object.observe(arr, observer.callback); 1724 setElement(arr, prop, 989898); 1725 Object.deliverChangeRecords(observer.callback); 1726 observer.assertCallbackRecords([ 1727 { object: arr, name: "" + prop, type: 'update', oldValue: 5 } 1728 ]); 1729 } 1730 1731 for (var b1 = 0; b1 < 2; ++b1) 1732 for (var b2 = 0; b2 < 2; ++b2) 1733 for (var b3 = 0; b3 < 2; ++b3) 1734 for (var i in props) 1735 for (var j in mutation) 1736 TestFastElements(props[i], mutation[j], b1 != 0, b2 != 0, b3 != 0); 1737 1738 1739 var mutation = [ 1740 "a.length = v", 1741 "a.length += newSize - oldSize", 1742 "a.length -= oldSize - newSize", 1743 ]; 1744 1745 var mutationByIncr = [ 1746 "++a.length", 1747 "a.length++", 1748 ]; 1749 1750 function TestFastElementsLength( 1751 mutation, polymorphic, optimize, oldSize, newSize) { 1752 var setLength = eval( 1753 "(function setLength(a, v) { " + mutation + "; " + 1754 "/* " + [].join.call(arguments, " ") + " */" 1755 + "})" 1756 ); 1757 print("TestFastElementsLength:", setLength); 1758 1759 function array(n) { 1760 var arr = new Array(n); 1761 for (var i = 0; i < n; ++i) arr[i] = i; 1762 return arr; 1763 } 1764 1765 setLength(array(oldSize), newSize); 1766 setLength(array(oldSize), newSize); 1767 if (polymorphic) setLength(array(oldSize).map(isNaN), newSize); 1768 if (optimize) %OptimizeFunctionOnNextCall(setLength); 1769 setLength(array(oldSize), newSize); 1770 1771 reset(); 1772 var arr = array(oldSize); 1773 Object.observe(arr, observer.callback); 1774 setLength(arr, newSize); 1775 Object.deliverChangeRecords(observer.callback); 1776 if (oldSize === newSize) { 1777 observer.assertNotCalled(); 1778 } else { 1779 var count = oldSize > newSize ? oldSize - newSize : 0; 1780 observer.assertRecordCount(count + 1); 1781 var lengthRecord = observer.records[count]; 1782 assertSame(arr, lengthRecord.object); 1783 assertEquals('length', lengthRecord.name); 1784 assertEquals('update', lengthRecord.type); 1785 assertSame(oldSize, lengthRecord.oldValue); 1786 } 1787 } 1788 1789 for (var b1 = 0; b1 < 2; ++b1) 1790 for (var b2 = 0; b2 < 2; ++b2) 1791 for (var n1 = 0; n1 < 3; ++n1) 1792 for (var n2 = 0; n2 < 3; ++n2) 1793 for (var i in mutation) 1794 TestFastElementsLength(mutation[i], b1 != 0, b2 != 0, 20*n1, 20*n2); 1795 1796 for (var b1 = 0; b1 < 2; ++b1) 1797 for (var b2 = 0; b2 < 2; ++b2) 1798 for (var n = 0; n < 3; ++n) 1799 for (var i in mutationByIncr) 1800 TestFastElementsLength(mutationByIncr[i], b1 != 0, b2 != 0, 7*n, 7*n+1); 1801