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
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.
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.
28 // Flags: --harmony-proxies
29 // Flags: --allow-natives-syntax
31 var allObservers = [];
33 allObservers.forEach(function(observer) { observer.reset(); });
36 function stringifyNoThrow(arg) {
38 return JSON.stringify(arg);
40 return '{<circular reference>}';
44 function createObserver() {
45 "use strict"; // So that |this| in callback can be undefined.
51 this.records = undefined;
52 this.callbackCount = 0;
54 assertNotCalled: function() {
55 assertEquals(undefined, this.records);
56 assertEquals(0, this.callbackCount);
58 assertCalled: function() {
59 assertEquals(1, this.callbackCount);
61 assertRecordCount: function(count) {
63 assertEquals(count, this.records.length);
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]);
77 observer.callback = function(r) {
78 assertEquals(undefined, this);
79 assertEquals('object', typeof r);
80 assertTrue(r instanceof Array)
82 observer.callbackCount++;
86 allObservers.push(observer);
90 var observer = createObserver();
91 var observer2 = createObserver();
93 assertEquals("function", typeof observer.callback);
94 assertEquals("function", typeof observer2.callback);
98 function frozenFunction() {}
99 Object.freeze(frozenFunction);
100 var nonFunction = {};
101 var changeRecordWithAccessor = { type: 'foo' };
102 var recordCreated = false;
103 Object.defineProperty(changeRecordWithAccessor, 'name', {
105 recordCreated = true;
113 assertThrows(function() { Object.observe("non-object", observer.callback); },
115 assertThrows(function() { Object.observe(this, observer.callback); },
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));
130 assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError);
131 assertThrows(function() { Object.unobserve(this, observer.callback); },
133 assertThrows(function() { Object.unobserve(obj, nonFunction); }, TypeError);
134 assertEquals(obj, Object.unobserve(obj, observer.callback));
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);
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);
156 notifier.performChange('foo', function() {
157 assertEquals(global, this);
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
172 // Object.deliverChangeRecords
173 assertThrows(function() { Object.deliverChangeRecords(nonFunction); }, TypeError);
175 Object.observe(obj, observer.callback);
178 // notify uses to [[CreateOwnProperty]] to create changeRecord;
180 var protoExpandoAccessed = false;
181 Object.defineProperty(Object.prototype, 'protoExpando',
184 set: function() { protoExpandoAccessed = true; }
187 notifier.notify({ type: 'foo', protoExpando: 'val'});
188 assertFalse(protoExpandoAccessed);
189 delete Object.prototype.protoExpando;
190 Object.deliverChangeRecords(observer.callback);
193 // Multiple records are delivered.
202 object: notifier, // object property is ignored
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' }
213 // Non-string accept values are coerced to strings
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' }
233 // No delivery takes place if no records are pending
235 Object.deliverChangeRecords(observer.callback);
236 observer.assertNotCalled();
239 // Multiple observation has no effect.
241 Object.observe(obj, observer.callback);
242 Object.observe(obj, observer.callback);
243 Object.getNotifier(obj).notify({
246 Object.deliverChangeRecords(observer.callback);
247 observer.assertCalled();
250 // Observation can be stopped.
252 Object.unobserve(obj, observer.callback);
253 Object.getNotifier(obj).notify({
256 Object.deliverChangeRecords(observer.callback);
257 observer.assertNotCalled();
260 // Multiple unobservation has no effect
262 Object.unobserve(obj, observer.callback);
263 Object.unobserve(obj, observer.callback);
264 Object.getNotifier(obj).notify({
267 Object.deliverChangeRecords(observer.callback);
268 observer.assertNotCalled();
271 // Re-observation works and only includes changeRecords after of call.
273 Object.getNotifier(obj).notify({
276 Object.observe(obj, observer.callback);
277 Object.getNotifier(obj).notify({
281 Object.deliverChangeRecords(observer.callback);
282 observer.assertRecordCount(1);
284 // Get notifier prior to observing
287 Object.getNotifier(obj);
288 Object.observe(obj, observer.callback);
290 Object.deliverChangeRecords(observer.callback);
291 observer.assertCallbackRecords([
292 { object: obj, type: 'add', name: 'id' },
295 // The empty-string property is observable
298 Object.observe(obj, observer.callback);
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: ' ' },
309 // Object.preventExtensions
311 var obj = { foo: 'bar'};
312 Object.observe(obj, observer.callback);
314 Object.preventExtensions(obj);
316 Object.deliverChangeRecords(observer.callback);
317 observer.assertCallbackRecords([
318 { object: obj, type: 'add', name: 'baz' },
319 { object: obj, type: 'preventExtensions' },
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();
332 var obj = { a: 'a' };
333 Object.defineProperty(obj, 'b', {
338 Object.defineProperty(obj, 'c', {
343 Object.defineProperty(obj, 'd', {
348 Object.observe(obj, observer.callback);
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' },
360 var obj = { foo: 'bar'};
362 Object.observe(obj, observer.callback);
364 Object.deliverChangeRecords(observer.callback);
365 observer.assertNotCalled();
369 var obj = { a: 'a' };
370 Object.defineProperty(obj, 'b', {
375 Object.defineProperty(obj, 'c', {
380 Object.defineProperty(obj, 'd', {
385 Object.observe(obj, observer.callback);
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' },
396 var obj = { foo: 'bar'};
398 Object.observe(obj, observer.callback);
400 Object.deliverChangeRecords(observer.callback);
401 observer.assertNotCalled();
403 // Observing a continuous stream of changes, while itermittantly unobserving.
406 Object.observe(obj, observer.callback);
407 Object.getNotifier(obj).notify({
412 Object.unobserve(obj, observer.callback);
413 Object.getNotifier(obj).notify({
418 Object.observe(obj, observer.callback);
419 Object.getNotifier(obj).notify({
424 Object.unobserve(obj, observer.callback);
425 Object.getNotifier(obj).notify({
430 Object.observe(obj, observer.callback);
431 Object.getNotifier(obj).notify({
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 }
446 Object.observe(obj, observer.callback, ['somethingElse']);
447 Object.getNotifier(obj).notify({
450 Object.getNotifier(obj).notify({
453 Object.getNotifier(obj).notify({
456 Object.getNotifier(obj).notify({
459 Object.getNotifier(obj).notify({
462 Object.deliverChangeRecords(observer.callback);
463 observer.assertNotCalled();
466 Object.observe(obj, observer.callback, ['add', 'delete', 'setPrototype']);
467 Object.getNotifier(obj).notify({
470 Object.getNotifier(obj).notify({
473 Object.getNotifier(obj).notify({
476 Object.getNotifier(obj).notify({
479 Object.getNotifier(obj).notify({
482 Object.getNotifier(obj).notify({
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' }
494 Object.observe(obj, observer.callback, ['update', 'foo']);
495 Object.getNotifier(obj).notify({
498 Object.getNotifier(obj).notify({
501 Object.getNotifier(obj).notify({
504 Object.getNotifier(obj).notify({
507 Object.getNotifier(obj).notify({
510 Object.getNotifier(obj).notify({
513 Object.deliverChangeRecords(observer.callback);
514 observer.assertCallbackRecords([
515 { object: obj, type: 'update' },
516 { object: obj, type: 'foo' },
517 { object: obj, type: 'foo' }
521 function Thingy(a, b, c) {
526 Thingy.MULTIPLY = 'multiply';
527 Thingy.INCREMENT = 'increment';
528 Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply';
531 increment: function(amount) {
532 var notifier = Object.getNotifier(this);
535 notifier.performChange(Thingy.INCREMENT, function() {
541 }; // implicit notify
545 multiply: function(amount) {
546 var notifier = Object.getNotifier(this);
549 notifier.performChange(Thingy.MULTIPLY, function() {
555 }; // implicit notify
559 incrementAndMultiply: function(incAmount, multAmount) {
560 var notifier = Object.getNotifier(this);
563 notifier.performChange(Thingy.INCREMENT_AND_MULTIPLY, function() {
564 self.increment(incAmount);
565 self.multiply(multAmount);
568 incremented: incAmount,
569 multiplied: multAmount
570 }; // implicit notify
575 Thingy.observe = function(thingy, callback) {
576 Object.observe(thingy, callback, [Thingy.INCREMENT,
578 Thingy.INCREMENT_AND_MULTIPLY,
582 Thingy.unobserve = function(thingy, callback) {
583 Object.unobserve(thingy);
586 var thingy = new Thingy(2, 4);
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 }
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 },
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 },
617 type: Thingy.INCREMENT_AND_MULTIPLY,
623 // ArrayPush cached stub
626 function pushMultiple(arr) {
632 for (var i = 0; i < 5; i++) {
637 for (var i = 0; i < 5; i++) {
640 Object.observe(arr, observer.callback);
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 },
655 // ArrayPop cached stub
658 function popMultiple(arr) {
664 for (var i = 0; i < 5; i++) {
665 var arr = ['a', 'b', 'c'];
669 for (var i = 0; i < 5; i++) {
671 var arr = ['a', 'b', 'c'];
672 Object.observe(arr, observer.callback);
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 },
688 function RecursiveThingy() {}
690 RecursiveThingy.MULTIPLY_FIRST_N = 'multiplyFirstN';
692 RecursiveThingy.prototype = {
693 __proto__: Array.prototype,
695 multiplyFirstN: function(amount, n) {
698 var notifier = Object.getNotifier(this);
700 notifier.performChange(RecursiveThingy.MULTIPLY_FIRST_N, function() {
701 self[n-1] = self[n-1]*amount;
702 self.multiplyFirstN(amount, n-1);
706 type: RecursiveThingy.MULTIPLY_FIRST_N,
713 RecursiveThingy.observe = function(thingy, callback) {
714 Object.observe(thingy, callback, [RecursiveThingy.MULTIPLY_FIRST_N]);
717 RecursiveThingy.unobserve = function(thingy, callback) {
718 Object.unobserve(thingy);
721 var thingy = new RecursiveThingy;
722 thingy.push(1, 2, 3, 4);
724 Object.observe(thingy, observer.callback);
725 RecursiveThingy.observe(thingy, observer2.callback);
726 thingy.multiplyFirstN(2, 3); // [2, 4, 6, 4]
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 }
735 observer2.assertCallbackRecords([
736 { object: thingy, type: RecursiveThingy.MULTIPLY_FIRST_N, multiplied: 2, n: 3 }
740 function DeckSuit() {
741 this.push('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'A', 'Q', 'K');
744 DeckSuit.SHUFFLE = 'shuffle';
746 DeckSuit.prototype = {
747 __proto__: Array.prototype,
749 shuffle: function() {
750 var notifier = Object.getNotifier(this);
752 notifier.performChange(DeckSuit.SHUFFLE, function() {
754 self.sort(function() { return Math.random()* 2 - 1; });
755 var cut = self.splice(0, 6);
756 Array.prototype.push.apply(self, cut);
758 self.sort(function() { return Math.random()* 2 - 1; });
759 var cut = self.splice(0, 6);
760 Array.prototype.push.apply(self, cut);
762 self.sort(function() { return Math.random()* 2 - 1; });
766 type: DeckSuit.SHUFFLE
771 DeckSuit.observe = function(thingy, callback) {
772 Object.observe(thingy, callback, [DeckSuit.SHUFFLE]);
775 DeckSuit.unobserve = function(thingy, callback) {
776 Object.unobserve(thingy);
779 var deck = new DeckSuit;
781 DeckSuit.observe(deck, observer2.callback);
784 Object.deliverChangeRecords(observer2.callback);
785 observer2.assertCallbackRecords([
786 { object: deck, type: DeckSuit.SHUFFLE }
789 // Observing multiple objects; records appear in order.
793 Object.observe(obj, observer.callback);
794 Object.observe(obj3, observer.callback);
795 Object.observe(obj2, observer.callback);
796 Object.getNotifier(obj).notify({
799 Object.getNotifier(obj2).notify({
802 Object.getNotifier(obj3).notify({
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' }
814 // Recursive observation.
816 var callbackCount = 0;
817 function recursiveObserver(r) {
818 assertEquals(1, r.length);
820 if (r[0].oldValue < 100) ++obj[r[0].name];
822 Object.observe(obj, recursiveObserver);
824 Object.deliverChangeRecords(recursiveObserver);
825 assertEquals(100, callbackCount);
830 function recursiveObserver2(r) {
831 recordCount += r.length;
832 if (r[0].oldValue < 100) {
837 Object.observe(obj1, recursiveObserver2);
838 Object.observe(obj2, recursiveObserver2);
840 Object.deliverChangeRecords(recursiveObserver2);
841 assertEquals(199, recordCount);
844 // Observing named properties.
847 Object.observe(obj, observer.callback);
852 obj.a = 4; // ignored
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});
867 Object.defineProperty(obj, "a", {get: function() {}, configurable: true});
868 Object.defineProperty(obj, "a", {value: 9, writable: true});
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" },
902 // Observing indexed properties.
905 Object.observe(obj, observer.callback);
910 obj[1] = 4; // ignored
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});
925 Object.defineProperty(obj, "1", {get: function() {}, configurable: true});
926 Object.defineProperty(obj, "1", {value: 9, writable: true});
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" },
960 // Observing symbol properties (not).
964 var symbol = Symbol("secret");
965 Object.observe(obj, observer.callback);
968 Object.defineProperty(obj, symbol, {get: function() {}, configurable: true});
969 Object.defineProperty(obj, symbol, {value: 6});
970 Object.defineProperty(obj, symbol, {writable: false});
972 Object.defineProperty(obj, symbol, {value: 7});
977 obj.__defineSetter__(symbol, function() {});
978 obj.__defineGetter__(symbol, function() {});
979 Object.deliverChangeRecords(observer.callback);
980 observer.assertNotCalled();
983 // Test all kinds of objects generically.
984 function TestObserveConfigurable(obj, prop) {
986 Object.observe(obj, observer.callback);
987 Object.unobserve(obj, observer.callback);
989 Object.observe(obj, observer.callback);
994 obj[prop] = 4; // ignored
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() {});
1011 delete obj[prop]; // ignored
1012 obj.__defineGetter__(prop, function() {});
1014 Object.defineProperty(obj, prop, {get: function() {}, configurable: true});
1015 Object.defineProperty(obj, prop, {value: 9, writable: true});
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" },
1051 Object.unobserve(obj, observer.callback);
1055 function TestObserveNonConfigurable(obj, prop, desc) {
1057 Object.observe(obj, observer.callback);
1058 Object.unobserve(obj, observer.callback);
1060 Object.observe(obj, observer.callback);
1062 obj[prop] = 4; // ignored
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" },
1078 Object.unobserve(obj, observer.callback);
1081 // TODO(rafaelw) Enable when ES6 Proxies are implemented
1083 function createProxy(create, x) {
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;
1092 getOwnPropertyDescriptor: function(k) {
1093 return Object.getOwnPropertyDescriptor(this.target, k);
1095 defineProperty: function(k, desc) {
1096 var x = Object.defineProperty(this.target, k, desc);
1097 Object.deliverChangeRecords(this.callback);
1100 delete: function(k) {
1101 var x = delete this.target[k];
1102 Object.deliverChangeRecords(this.callback);
1105 getPropertyNames: function() {
1106 return Object.getOwnPropertyNames(this.target);
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);
1119 Object.observe(handler.target, handler.callback);
1120 return handler.proxy = create(handler, x);
1128 (function(){ return arguments })(),
1129 (function(){ "use strict"; return arguments })(),
1130 Object(1), Object(true), Object("bla"),
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(){}),
1139 var properties = ["a", "1", 1, "length", "setPrototype", "name", "caller"];
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)
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);
1161 // Observing array length (including truncation)
1163 var arr = ['a', 'b', 'c', 'd'];
1164 var arr2 = ['alpha', 'beta'];
1165 var arr3 = ['hello'];
1166 arr3[2] = 'goodbye';
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);
1180 Object.defineProperty(arr, 'length', {writable: false});
1182 arr2.length = 1; // no change expected
1183 Object.defineProperty(arr2, 'length', {value: 1, writable: false});
1188 Object.defineProperty(arr3, 'length', {value: 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' }
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 }
1235 // Updating length on large (slow) array
1237 var slow_arr = %NormalizeElements([]);
1238 slow_arr[500000000] = 'hello';
1239 slow_arr.length = 1000000000;
1240 Object.observe(slow_arr, observer.callback);
1242 function slowSpliceCallback(records) {
1243 spliceRecords = records;
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 },
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);
1267 // Assignments in loops (checking different IC states).
1270 Object.observe(obj, observer.callback);
1271 for (var i = 0; i < 5; i++) {
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" },
1285 Object.observe(obj, observer.callback);
1286 for (var i = 0; i < 5; i++) {
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" },
1299 // Adding elements past the end of an array should notify on length for
1300 // Object.observe and emit "splices" for Array.observe.
1302 var arr = [1, 2, 3];
1303 Object.observe(arr, observer.callback);
1304 Array.observe(arr, observer2.callback);
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' },
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' },
1332 // Tests for array methods, first on arrays and then on plain objects
1339 Object.observe(array, observer.callback);
1340 Array.observe(array, observer2.callback);
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 },
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 }
1361 Object.observe(array, observer.callback);
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 },
1375 Object.observe(array, observer.callback);
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 },
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 },
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 },
1416 var array = [3, 2, 1];
1417 Object.observe(array, observer.callback);
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 },
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];
1436 this.length_ = initial.length;
1437 this.observer = observer;
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']);
1446 return this.length_;
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 },
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 },
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 },
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 },
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 },
1501 // === PLAIN OBJECTS ===
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 },
1518 Object.observe(array, observer.callback);
1519 Array.observe(array, observer2.callback);
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 },
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 }
1539 Object.observe(array, observer.callback);
1540 Array.observe(array, observer2.callback);
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 },
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 }
1561 Object.observe(array, observer.callback);
1562 Array.observe(array, observer2.callback);
1563 array.unshift(3, 4);
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 },
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 }
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
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 },
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 },
1609 { object: array, name: '1', type: 'update', oldValue: 2 },
1610 { object: array, name: '2', type: 'update', oldValue: 3 },
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 },
1619 // Exercise StoreIC_ArrayLength
1622 Object.observe(dummy, observer.callback);
1623 Object.unobserve(dummy, observer.callback);
1625 Object.observe(array, observer.callback);
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},
1637 Object.observe(obj, observer.callback);
1638 var p = {foo: 'yes'};
1639 var q = {bar: 'no'};
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' },
1655 // Function.prototype
1657 var fun = function(){};
1658 Object.observe(fun, observer.callback);
1659 var myproto = {foo: 'bar'};
1660 fun.prototype = myproto;
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 },
1682 // Function.prototype should not be observable except on the object itself
1684 var fun = function(){};
1685 var obj = { __proto__: fun };
1686 Object.observe(obj, observer.callback);
1688 Object.deliverChangeRecords(observer.callback);
1689 observer.assertRecordCount(1);
1690 observer.assertCallbackRecords([
1691 { object: obj, name: 'prototype', type: 'add' },
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.
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",
1704 var props = [1, "1", "a"];
1706 function TestFastElements(prop, mutation, prepopulate, polymorphic, optimize) {
1707 var setElement = eval(
1708 "(function setElement(a, i, v) { " + mutation + "; " +
1709 "/* " + [].join.call(arguments, " ") + " */" +
1712 print("TestFastElements:", setElement);
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);
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 }
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);
1741 "a.length += newSize - oldSize",
1742 "a.length -= oldSize - newSize",
1745 var mutationByIncr = [
1750 function TestFastElementsLength(
1751 mutation, polymorphic, optimize, oldSize, newSize) {
1752 var setLength = eval(
1753 "(function setLength(a, v) { " + mutation + "; " +
1754 "/* " + [].join.call(arguments, " ") + " */"
1757 print("TestFastElementsLength:", setLength);
1760 var arr = new Array(n);
1761 for (var i = 0; i < n; ++i) arr[i] = i;
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);
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();
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);
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);
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);