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-observation --harmony-proxies
29 // Flags: --harmony-collections --harmony-weak-collections
30 // Flags: --harmony-symbols --allow-natives-syntax
32 var allObservers = [];
34 allObservers.forEach(function(observer) { observer.reset(); });
37 function stringifyNoThrow(arg) {
39 return JSON.stringify(arg);
41 return '{<circular reference>}';
45 function createObserver() {
46 "use strict"; // So that |this| in callback can be undefined.
52 this.records = undefined;
53 this.callbackCount = 0;
55 assertNotCalled: function() {
56 assertEquals(undefined, this.records);
57 assertEquals(0, this.callbackCount);
59 assertCalled: function() {
60 assertEquals(1, this.callbackCount);
62 assertRecordCount: function(count) {
64 assertEquals(count, this.records.length);
66 assertCallbackRecords: function(recs) {
67 this.assertRecordCount(recs.length);
68 for (var i = 0; i < recs.length; i++) {
69 if ('name' in recs[i]) recs[i].name = String(recs[i].name);
70 print(i, stringifyNoThrow(this.records[i]), stringifyNoThrow(recs[i]));
71 assertSame(this.records[i].object, recs[i].object);
72 assertEquals('string', typeof recs[i].type);
73 assertPropertiesEqual(this.records[i], recs[i]);
78 observer.callback = function(r) {
79 assertEquals(undefined, this);
80 assertEquals('object', typeof r);
81 assertTrue(r instanceof Array)
83 observer.callbackCount++;
87 allObservers.push(observer);
91 var observer = createObserver();
92 var observer2 = createObserver();
94 assertEquals("function", typeof observer.callback);
95 assertEquals("function", typeof observer2.callback);
99 function frozenFunction() {}
100 Object.freeze(frozenFunction);
101 var nonFunction = {};
102 var changeRecordWithAccessor = { type: 'foo' };
103 var recordCreated = false;
104 Object.defineProperty(changeRecordWithAccessor, 'name', {
106 recordCreated = true;
114 assertThrows(function() { Object.observe("non-object", observer.callback); },
116 assertThrows(function() { Object.observe(this, observer.callback); },
118 assertThrows(function() { Object.observe(obj, nonFunction); }, TypeError);
119 assertThrows(function() { Object.observe(obj, frozenFunction); }, TypeError);
120 assertEquals(obj, Object.observe(obj, observer.callback, [1]));
121 assertEquals(obj, Object.observe(obj, observer.callback, [true]));
122 assertEquals(obj, Object.observe(obj, observer.callback, ['foo', null]));
123 assertEquals(obj, Object.observe(obj, observer.callback, [undefined]));
124 assertEquals(obj, Object.observe(obj, observer.callback,
125 ['foo', 'bar', 'baz']));
126 assertEquals(obj, Object.observe(obj, observer.callback, []));
127 assertEquals(obj, Object.observe(obj, observer.callback, undefined));
128 assertEquals(obj, Object.observe(obj, observer.callback));
131 assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError);
132 assertThrows(function() { Object.unobserve(this, observer.callback); },
134 assertThrows(function() { Object.unobserve(obj, nonFunction); }, TypeError);
135 assertEquals(obj, Object.unobserve(obj, observer.callback));
138 // Object.getNotifier
139 var notifier = Object.getNotifier(obj);
140 assertSame(notifier, Object.getNotifier(obj));
141 assertEquals(null, Object.getNotifier(Object.freeze({})));
142 assertThrows(function() { Object.getNotifier(this) }, TypeError);
143 assertFalse(notifier.hasOwnProperty('notify'));
144 assertEquals([], Object.keys(notifier));
145 var notifyDesc = Object.getOwnPropertyDescriptor(notifier.__proto__, 'notify');
146 assertTrue(notifyDesc.configurable);
147 assertTrue(notifyDesc.writable);
148 assertFalse(notifyDesc.enumerable);
149 assertThrows(function() { notifier.notify({}); }, TypeError);
150 assertThrows(function() { notifier.notify({ type: 4 }); }, TypeError);
152 assertThrows(function() { notifier.performChange(1, function(){}); }, TypeError);
153 assertThrows(function() { notifier.performChange(undefined, function(){}); }, TypeError);
154 assertThrows(function() { notifier.performChange('foo', undefined); }, TypeError);
155 assertThrows(function() { notifier.performChange('foo', 'bar'); }, TypeError);
157 notifier.performChange('foo', function() {
158 assertEquals(global, this);
161 var notify = notifier.notify;
162 assertThrows(function() { notify.call(undefined, { type: 'a' }); }, TypeError);
163 assertThrows(function() { notify.call(null, { type: 'a' }); }, TypeError);
164 assertThrows(function() { notify.call(5, { type: 'a' }); }, TypeError);
165 assertThrows(function() { notify.call('hello', { type: 'a' }); }, TypeError);
166 assertThrows(function() { notify.call(false, { type: 'a' }); }, TypeError);
167 assertThrows(function() { notify.call({}, { type: 'a' }); }, TypeError);
168 assertFalse(recordCreated);
169 notifier.notify(changeRecordWithAccessor);
170 assertFalse(recordCreated); // not observed yet
173 // Object.deliverChangeRecords
174 assertThrows(function() { Object.deliverChangeRecords(nonFunction); }, TypeError);
176 Object.observe(obj, observer.callback);
179 // notify uses to [[CreateOwnProperty]] to create changeRecord;
181 var protoExpandoAccessed = false;
182 Object.defineProperty(Object.prototype, 'protoExpando',
185 set: function() { protoExpandoAccessed = true; }
188 notifier.notify({ type: 'foo', protoExpando: 'val'});
189 assertFalse(protoExpandoAccessed);
190 delete Object.prototype.protoExpando;
191 Object.deliverChangeRecords(observer.callback);
194 // Multiple records are delivered.
203 object: notifier, // object property is ignored
208 Object.deliverChangeRecords(observer.callback);
209 observer.assertCallbackRecords([
210 { object: obj, name: 'foo', type: 'update', expando: 1 },
211 { object: obj, name: 'bar', type: 'delete', expando2: 'str' }
214 // Non-string accept values are coerced to strings
216 Object.observe(obj, observer.callback, [true, 1, null, undefined]);
217 notifier = Object.getNotifier(obj);
218 notifier.notify({ type: 'true' });
219 notifier.notify({ type: 'false' });
220 notifier.notify({ type: '1' });
221 notifier.notify({ type: '-1' });
222 notifier.notify({ type: 'null' });
223 notifier.notify({ type: 'nill' });
224 notifier.notify({ type: 'undefined' });
225 notifier.notify({ type: 'defined' });
226 Object.deliverChangeRecords(observer.callback);
227 observer.assertCallbackRecords([
228 { object: obj, type: 'true' },
229 { object: obj, type: '1' },
230 { object: obj, type: 'null' },
231 { object: obj, type: 'undefined' }
234 // No delivery takes place if no records are pending
236 Object.deliverChangeRecords(observer.callback);
237 observer.assertNotCalled();
240 // Multiple observation has no effect.
242 Object.observe(obj, observer.callback);
243 Object.observe(obj, observer.callback);
244 Object.getNotifier(obj).notify({
247 Object.deliverChangeRecords(observer.callback);
248 observer.assertCalled();
251 // Observation can be stopped.
253 Object.unobserve(obj, observer.callback);
254 Object.getNotifier(obj).notify({
257 Object.deliverChangeRecords(observer.callback);
258 observer.assertNotCalled();
261 // Multiple unobservation has no effect
263 Object.unobserve(obj, observer.callback);
264 Object.unobserve(obj, observer.callback);
265 Object.getNotifier(obj).notify({
268 Object.deliverChangeRecords(observer.callback);
269 observer.assertNotCalled();
272 // Re-observation works and only includes changeRecords after of call.
274 Object.getNotifier(obj).notify({
277 Object.observe(obj, observer.callback);
278 Object.getNotifier(obj).notify({
282 Object.deliverChangeRecords(observer.callback);
283 observer.assertRecordCount(1);
285 // Get notifier prior to observing
288 Object.getNotifier(obj);
289 Object.observe(obj, observer.callback);
291 Object.deliverChangeRecords(observer.callback);
292 observer.assertCallbackRecords([
293 { object: obj, type: 'add', name: 'id' },
296 // The empty-string property is observable
299 Object.observe(obj, observer.callback);
303 Object.deliverChangeRecords(observer.callback);
304 observer.assertCallbackRecords([
305 { object: obj, type: 'add', name: '' },
306 { object: obj, type: 'update', name: '', oldValue: '' },
307 { object: obj, type: 'delete', name: '', oldValue: ' ' },
310 // Object.preventExtensions
312 var obj = { foo: 'bar'};
313 Object.observe(obj, observer.callback);
315 Object.preventExtensions(obj);
317 Object.deliverChangeRecords(observer.callback);
318 observer.assertCallbackRecords([
319 { object: obj, type: 'add', name: 'baz' },
320 { object: obj, type: 'preventExtensions' },
324 var obj = { foo: 'bar'};
325 Object.preventExtensions(obj);
326 Object.observe(obj, observer.callback);
327 Object.preventExtensions(obj);
328 Object.deliverChangeRecords(observer.callback);
329 observer.assertNotCalled();
333 var obj = { a: 'a' };
334 Object.defineProperty(obj, 'b', {
339 Object.defineProperty(obj, 'c', {
344 Object.defineProperty(obj, 'd', {
349 Object.observe(obj, observer.callback);
352 Object.deliverChangeRecords(observer.callback);
353 observer.assertCallbackRecords([
354 { object: obj, type: 'reconfigure', name: 'a' },
355 { object: obj, type: 'reconfigure', name: 'b' },
356 { object: obj, type: 'reconfigure', name: 'c' },
357 { object: obj, type: 'preventExtensions' },
361 var obj = { foo: 'bar'};
363 Object.observe(obj, observer.callback);
365 Object.deliverChangeRecords(observer.callback);
366 observer.assertNotCalled();
370 var obj = { a: 'a' };
371 Object.defineProperty(obj, 'b', {
376 Object.defineProperty(obj, 'c', {
381 Object.defineProperty(obj, 'd', {
386 Object.observe(obj, observer.callback);
389 Object.deliverChangeRecords(observer.callback);
390 observer.assertCallbackRecords([
391 { object: obj, type: 'reconfigure', name: 'a' },
392 { object: obj, type: 'reconfigure', name: 'b' },
393 { object: obj, type: 'preventExtensions' },
397 var obj = { foo: 'bar'};
399 Object.observe(obj, observer.callback);
401 Object.deliverChangeRecords(observer.callback);
402 observer.assertNotCalled();
404 // Observing a continuous stream of changes, while itermittantly unobserving.
407 Object.observe(obj, observer.callback);
408 Object.getNotifier(obj).notify({
413 Object.unobserve(obj, observer.callback);
414 Object.getNotifier(obj).notify({
419 Object.observe(obj, observer.callback);
420 Object.getNotifier(obj).notify({
425 Object.unobserve(obj, observer.callback);
426 Object.getNotifier(obj).notify({
431 Object.observe(obj, observer.callback);
432 Object.getNotifier(obj).notify({
437 Object.unobserve(obj, observer.callback);
438 Object.deliverChangeRecords(observer.callback);
439 observer.assertCallbackRecords([
440 { object: obj, type: 'update', val: 1 },
441 { object: obj, type: 'update', val: 3 },
442 { object: obj, type: 'update', val: 5 }
447 Object.observe(obj, observer.callback, ['somethingElse']);
448 Object.getNotifier(obj).notify({
451 Object.getNotifier(obj).notify({
454 Object.getNotifier(obj).notify({
457 Object.getNotifier(obj).notify({
460 Object.getNotifier(obj).notify({
463 Object.deliverChangeRecords(observer.callback);
464 observer.assertNotCalled();
467 Object.observe(obj, observer.callback, ['add', 'delete', 'setPrototype']);
468 Object.getNotifier(obj).notify({
471 Object.getNotifier(obj).notify({
474 Object.getNotifier(obj).notify({
477 Object.getNotifier(obj).notify({
480 Object.getNotifier(obj).notify({
483 Object.getNotifier(obj).notify({
486 Object.deliverChangeRecords(observer.callback);
487 observer.assertCallbackRecords([
488 { object: obj, type: 'add' },
489 { object: obj, type: 'delete' },
490 { object: obj, type: 'delete' },
491 { object: obj, type: 'setPrototype' }
495 Object.observe(obj, observer.callback, ['update', 'foo']);
496 Object.getNotifier(obj).notify({
499 Object.getNotifier(obj).notify({
502 Object.getNotifier(obj).notify({
505 Object.getNotifier(obj).notify({
508 Object.getNotifier(obj).notify({
511 Object.getNotifier(obj).notify({
514 Object.deliverChangeRecords(observer.callback);
515 observer.assertCallbackRecords([
516 { object: obj, type: 'update' },
517 { object: obj, type: 'foo' },
518 { object: obj, type: 'foo' }
522 function Thingy(a, b, c) {
527 Thingy.MULTIPLY = 'multiply';
528 Thingy.INCREMENT = 'increment';
529 Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply';
532 increment: function(amount) {
533 var notifier = Object.getNotifier(this);
536 notifier.performChange(Thingy.INCREMENT, function() {
542 }; // implicit notify
546 multiply: function(amount) {
547 var notifier = Object.getNotifier(this);
550 notifier.performChange(Thingy.MULTIPLY, function() {
556 }; // implicit notify
560 incrementAndMultiply: function(incAmount, multAmount) {
561 var notifier = Object.getNotifier(this);
564 notifier.performChange(Thingy.INCREMENT_AND_MULTIPLY, function() {
565 self.increment(incAmount);
566 self.multiply(multAmount);
569 incremented: incAmount,
570 multiplied: multAmount
571 }; // implicit notify
576 Thingy.observe = function(thingy, callback) {
577 Object.observe(thingy, callback, [Thingy.INCREMENT,
579 Thingy.INCREMENT_AND_MULTIPLY,
583 Thingy.unobserve = function(thingy, callback) {
584 Object.unobserve(thingy);
587 var thingy = new Thingy(2, 4);
589 Object.observe(thingy, observer.callback);
590 Thingy.observe(thingy, observer2.callback);
591 thingy.increment(3); // { a: 5, b: 7 }
592 thingy.b++; // { a: 5, b: 8 }
593 thingy.multiply(2); // { a: 10, b: 16 }
594 thingy.a++; // { a: 11, b: 16 }
595 thingy.incrementAndMultiply(2, 2); // { a: 26, b: 36 }
597 Object.deliverChangeRecords(observer.callback);
598 Object.deliverChangeRecords(observer2.callback);
599 observer.assertCallbackRecords([
600 { object: thingy, type: 'update', name: 'a', oldValue: 2 },
601 { object: thingy, type: 'update', name: 'b', oldValue: 4 },
602 { object: thingy, type: 'update', name: 'b', oldValue: 7 },
603 { object: thingy, type: 'update', name: 'a', oldValue: 5 },
604 { object: thingy, type: 'update', name: 'b', oldValue: 8 },
605 { object: thingy, type: 'update', name: 'a', oldValue: 10 },
606 { object: thingy, type: 'update', name: 'a', oldValue: 11 },
607 { object: thingy, type: 'update', name: 'b', oldValue: 16 },
608 { object: thingy, type: 'update', name: 'a', oldValue: 13 },
609 { object: thingy, type: 'update', name: 'b', oldValue: 18 },
611 observer2.assertCallbackRecords([
612 { object: thingy, type: Thingy.INCREMENT, incremented: 3 },
613 { object: thingy, type: 'update', name: 'b', oldValue: 7 },
614 { object: thingy, type: Thingy.MULTIPLY, multiplied: 2 },
615 { object: thingy, type: 'update', name: 'a', oldValue: 10 },
618 type: Thingy.INCREMENT_AND_MULTIPLY,
624 // ArrayPush cached stub
627 function pushMultiple(arr) {
633 for (var i = 0; i < 5; i++) {
638 for (var i = 0; i < 5; i++) {
641 Object.observe(arr, observer.callback);
643 Object.unobserve(arr, observer.callback);
644 Object.deliverChangeRecords(observer.callback);
645 observer.assertCallbackRecords([
646 { object: arr, type: 'add', name: '0' },
647 { object: arr, type: 'update', name: 'length', oldValue: 0 },
648 { object: arr, type: 'add', name: '1' },
649 { object: arr, type: 'update', name: 'length', oldValue: 1 },
650 { object: arr, type: 'add', name: '2' },
651 { object: arr, type: 'update', name: 'length', oldValue: 2 },
656 // ArrayPop cached stub
659 function popMultiple(arr) {
665 for (var i = 0; i < 5; i++) {
666 var arr = ['a', 'b', 'c'];
670 for (var i = 0; i < 5; i++) {
672 var arr = ['a', 'b', 'c'];
673 Object.observe(arr, observer.callback);
675 Object.unobserve(arr, observer.callback);
676 Object.deliverChangeRecords(observer.callback);
677 observer.assertCallbackRecords([
678 { object: arr, type: 'delete', name: '2', oldValue: 'c' },
679 { object: arr, type: 'update', name: 'length', oldValue: 3 },
680 { object: arr, type: 'delete', name: '1', oldValue: 'b' },
681 { object: arr, type: 'update', name: 'length', oldValue: 2 },
682 { object: arr, type: 'delete', name: '0', oldValue: 'a' },
683 { object: arr, type: 'update', name: 'length', oldValue: 1 },
689 function RecursiveThingy() {}
691 RecursiveThingy.MULTIPLY_FIRST_N = 'multiplyFirstN';
693 RecursiveThingy.prototype = {
694 __proto__: Array.prototype,
696 multiplyFirstN: function(amount, n) {
699 var notifier = Object.getNotifier(this);
701 notifier.performChange(RecursiveThingy.MULTIPLY_FIRST_N, function() {
702 self[n-1] = self[n-1]*amount;
703 self.multiplyFirstN(amount, n-1);
707 type: RecursiveThingy.MULTIPLY_FIRST_N,
714 RecursiveThingy.observe = function(thingy, callback) {
715 Object.observe(thingy, callback, [RecursiveThingy.MULTIPLY_FIRST_N]);
718 RecursiveThingy.unobserve = function(thingy, callback) {
719 Object.unobserve(thingy);
722 var thingy = new RecursiveThingy;
723 thingy.push(1, 2, 3, 4);
725 Object.observe(thingy, observer.callback);
726 RecursiveThingy.observe(thingy, observer2.callback);
727 thingy.multiplyFirstN(2, 3); // [2, 4, 6, 4]
729 Object.deliverChangeRecords(observer.callback);
730 Object.deliverChangeRecords(observer2.callback);
731 observer.assertCallbackRecords([
732 { object: thingy, type: 'update', name: '2', oldValue: 3 },
733 { object: thingy, type: 'update', name: '1', oldValue: 2 },
734 { object: thingy, type: 'update', name: '0', oldValue: 1 }
736 observer2.assertCallbackRecords([
737 { object: thingy, type: RecursiveThingy.MULTIPLY_FIRST_N, multiplied: 2, n: 3 }
741 function DeckSuit() {
742 this.push('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'A', 'Q', 'K');
745 DeckSuit.SHUFFLE = 'shuffle';
747 DeckSuit.prototype = {
748 __proto__: Array.prototype,
750 shuffle: function() {
751 var notifier = Object.getNotifier(this);
753 notifier.performChange(DeckSuit.SHUFFLE, function() {
755 self.sort(function() { return Math.random()* 2 - 1; });
756 var cut = self.splice(0, 6);
757 Array.prototype.push.apply(self, cut);
759 self.sort(function() { return Math.random()* 2 - 1; });
760 var cut = self.splice(0, 6);
761 Array.prototype.push.apply(self, cut);
763 self.sort(function() { return Math.random()* 2 - 1; });
767 type: DeckSuit.SHUFFLE
772 DeckSuit.observe = function(thingy, callback) {
773 Object.observe(thingy, callback, [DeckSuit.SHUFFLE]);
776 DeckSuit.unobserve = function(thingy, callback) {
777 Object.unobserve(thingy);
780 var deck = new DeckSuit;
782 DeckSuit.observe(deck, observer2.callback);
785 Object.deliverChangeRecords(observer2.callback);
786 observer2.assertCallbackRecords([
787 { object: deck, type: DeckSuit.SHUFFLE }
790 // Observing multiple objects; records appear in order.
794 Object.observe(obj, observer.callback);
795 Object.observe(obj3, observer.callback);
796 Object.observe(obj2, observer.callback);
797 Object.getNotifier(obj).notify({
800 Object.getNotifier(obj2).notify({
803 Object.getNotifier(obj3).notify({
806 Object.observe(obj3, observer.callback);
807 Object.deliverChangeRecords(observer.callback);
808 observer.assertCallbackRecords([
809 { object: obj, type: 'add' },
810 { object: obj2, type: 'update' },
811 { object: obj3, type: 'delete' }
815 // Recursive observation.
817 var callbackCount = 0;
818 function recursiveObserver(r) {
819 assertEquals(1, r.length);
821 if (r[0].oldValue < 100) ++obj[r[0].name];
823 Object.observe(obj, recursiveObserver);
825 Object.deliverChangeRecords(recursiveObserver);
826 assertEquals(100, callbackCount);
831 function recursiveObserver2(r) {
832 recordCount += r.length;
833 if (r[0].oldValue < 100) {
838 Object.observe(obj1, recursiveObserver2);
839 Object.observe(obj2, recursiveObserver2);
841 Object.deliverChangeRecords(recursiveObserver2);
842 assertEquals(199, recordCount);
845 // Observing named properties.
848 Object.observe(obj, observer.callback);
853 obj.a = 4; // ignored
855 Object.defineProperty(obj, "a", {value: 6});
856 Object.defineProperty(obj, "a", {writable: false});
857 obj.a = 7; // ignored
858 Object.defineProperty(obj, "a", {value: 8});
859 Object.defineProperty(obj, "a", {value: 7, writable: true});
860 Object.defineProperty(obj, "a", {get: function() {}});
861 Object.defineProperty(obj, "a", {get: frozenFunction});
862 Object.defineProperty(obj, "a", {get: frozenFunction}); // ignored
863 Object.defineProperty(obj, "a", {get: frozenFunction, set: frozenFunction});
864 Object.defineProperty(obj, "a", {set: frozenFunction}); // ignored
865 Object.defineProperty(obj, "a", {get: undefined, set: frozenFunction});
868 Object.defineProperty(obj, "a", {get: function() {}, configurable: true});
869 Object.defineProperty(obj, "a", {value: 9, writable: true});
875 Object.defineProperty(obj, "a", {value: 11, configurable: true});
876 Object.deliverChangeRecords(observer.callback);
877 observer.assertCallbackRecords([
878 { object: obj, name: "a", type: "update", oldValue: 1 },
879 { object: obj, name: "a", type: "update", oldValue: 2 },
880 { object: obj, name: "a", type: "delete", oldValue: 3 },
881 { object: obj, name: "a", type: "add" },
882 { object: obj, name: "a", type: "update", oldValue: 4 },
883 { object: obj, name: "a", type: "update", oldValue: 5 },
884 { object: obj, name: "a", type: "reconfigure" },
885 { object: obj, name: "a", type: "update", oldValue: 6 },
886 { object: obj, name: "a", type: "reconfigure", oldValue: 8 },
887 { object: obj, name: "a", type: "reconfigure", oldValue: 7 },
888 { object: obj, name: "a", type: "reconfigure" },
889 { object: obj, name: "a", type: "reconfigure" },
890 { object: obj, name: "a", type: "reconfigure" },
891 { object: obj, name: "a", type: "delete" },
892 { object: obj, name: "a", type: "add" },
893 { object: obj, name: "a", type: "reconfigure" },
894 { object: obj, name: "a", type: "update", oldValue: 9 },
895 { object: obj, name: "a", type: "update", oldValue: 10 },
896 { object: obj, name: "a", type: "update", oldValue: 11 },
897 { object: obj, name: "a", type: "update", oldValue: 12 },
898 { object: obj, name: "a", type: "delete", oldValue: 36 },
899 { object: obj, name: "a", type: "add" },
903 // Observing indexed properties.
906 Object.observe(obj, observer.callback);
911 obj[1] = 4; // ignored
913 Object.defineProperty(obj, "1", {value: 6});
914 Object.defineProperty(obj, "1", {writable: false});
915 obj[1] = 7; // ignored
916 Object.defineProperty(obj, "1", {value: 8});
917 Object.defineProperty(obj, "1", {value: 7, writable: true});
918 Object.defineProperty(obj, "1", {get: function() {}});
919 Object.defineProperty(obj, "1", {get: frozenFunction});
920 Object.defineProperty(obj, "1", {get: frozenFunction}); // ignored
921 Object.defineProperty(obj, "1", {get: frozenFunction, set: frozenFunction});
922 Object.defineProperty(obj, "1", {set: frozenFunction}); // ignored
923 Object.defineProperty(obj, "1", {get: undefined, set: frozenFunction});
926 Object.defineProperty(obj, "1", {get: function() {}, configurable: true});
927 Object.defineProperty(obj, "1", {value: 9, writable: true});
933 Object.defineProperty(obj, "1", {value: 11, configurable: true});
934 Object.deliverChangeRecords(observer.callback);
935 observer.assertCallbackRecords([
936 { object: obj, name: "1", type: "update", oldValue: 1 },
937 { object: obj, name: "1", type: "update", oldValue: 2 },
938 { object: obj, name: "1", type: "delete", oldValue: 3 },
939 { object: obj, name: "1", type: "add" },
940 { object: obj, name: "1", type: "update", oldValue: 4 },
941 { object: obj, name: "1", type: "update", oldValue: 5 },
942 { object: obj, name: "1", type: "reconfigure" },
943 { object: obj, name: "1", type: "update", oldValue: 6 },
944 { object: obj, name: "1", type: "reconfigure", oldValue: 8 },
945 { object: obj, name: "1", type: "reconfigure", oldValue: 7 },
946 { object: obj, name: "1", type: "reconfigure" },
947 { object: obj, name: "1", type: "reconfigure" },
948 { object: obj, name: "1", type: "reconfigure" },
949 { object: obj, name: "1", type: "delete" },
950 { object: obj, name: "1", type: "add" },
951 { object: obj, name: "1", type: "reconfigure" },
952 { object: obj, name: "1", type: "update", oldValue: 9 },
953 { object: obj, name: "1", type: "update", oldValue: 10 },
954 { object: obj, name: "1", type: "update", oldValue: 11 },
955 { object: obj, name: "1", type: "update", oldValue: 12 },
956 { object: obj, name: "1", type: "delete", oldValue: 36 },
957 { object: obj, name: "1", type: "add" },
961 // Observing symbol properties (not).
965 var symbol = Symbol("secret");
966 Object.observe(obj, observer.callback);
969 Object.defineProperty(obj, symbol, {get: function() {}, configurable: true});
970 Object.defineProperty(obj, symbol, {value: 6});
971 Object.defineProperty(obj, symbol, {writable: false});
973 Object.defineProperty(obj, symbol, {value: 7});
978 obj.__defineSetter__(symbol, function() {});
979 obj.__defineGetter__(symbol, function() {});
980 Object.deliverChangeRecords(observer.callback);
981 observer.assertNotCalled();
984 // Test all kinds of objects generically.
985 function TestObserveConfigurable(obj, prop) {
987 Object.observe(obj, observer.callback);
988 Object.unobserve(obj, observer.callback);
990 Object.observe(obj, observer.callback);
995 obj[prop] = 4; // ignored
997 Object.defineProperty(obj, prop, {value: 6});
998 Object.defineProperty(obj, prop, {writable: false});
999 obj[prop] = 7; // ignored
1000 Object.defineProperty(obj, prop, {value: 8});
1001 Object.defineProperty(obj, prop, {value: 7, writable: true});
1002 Object.defineProperty(obj, prop, {get: function() {}});
1003 Object.defineProperty(obj, prop, {get: frozenFunction});
1004 Object.defineProperty(obj, prop, {get: frozenFunction}); // ignored
1005 Object.defineProperty(obj, prop, {get: frozenFunction, set: frozenFunction});
1006 Object.defineProperty(obj, prop, {set: frozenFunction}); // ignored
1007 Object.defineProperty(obj, prop, {get: undefined, set: frozenFunction});
1008 obj.__defineSetter__(prop, frozenFunction); // ignored
1009 obj.__defineSetter__(prop, function() {});
1010 obj.__defineGetter__(prop, function() {});
1012 delete obj[prop]; // ignored
1013 obj.__defineGetter__(prop, function() {});
1015 Object.defineProperty(obj, prop, {get: function() {}, configurable: true});
1016 Object.defineProperty(obj, prop, {value: 9, writable: true});
1022 Object.defineProperty(obj, prop, {value: 11, configurable: true});
1023 Object.deliverChangeRecords(observer.callback);
1024 observer.assertCallbackRecords([
1025 { object: obj, name: prop, type: "update", oldValue: 1 },
1026 { object: obj, name: prop, type: "update", oldValue: 2 },
1027 { object: obj, name: prop, type: "delete", oldValue: 3 },
1028 { object: obj, name: prop, type: "add" },
1029 { object: obj, name: prop, type: "update", oldValue: 4 },
1030 { object: obj, name: prop, type: "update", oldValue: 5 },
1031 { object: obj, name: prop, type: "reconfigure" },
1032 { object: obj, name: prop, type: "update", oldValue: 6 },
1033 { object: obj, name: prop, type: "reconfigure", oldValue: 8 },
1034 { object: obj, name: prop, type: "reconfigure", oldValue: 7 },
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: "reconfigure" },
1040 { object: obj, name: prop, type: "delete" },
1041 { object: obj, name: prop, type: "add" },
1042 { object: obj, name: prop, type: "delete" },
1043 { object: obj, name: prop, type: "add" },
1044 { object: obj, name: prop, type: "reconfigure" },
1045 { object: obj, name: prop, type: "update", oldValue: 9 },
1046 { object: obj, name: prop, type: "update", oldValue: 10 },
1047 { object: obj, name: prop, type: "update", oldValue: 11 },
1048 { object: obj, name: prop, type: "update", oldValue: 12 },
1049 { object: obj, name: prop, type: "delete", oldValue: 36 },
1050 { object: obj, name: prop, type: "add" },
1052 Object.unobserve(obj, observer.callback);
1056 function TestObserveNonConfigurable(obj, prop, desc) {
1058 Object.observe(obj, observer.callback);
1059 Object.unobserve(obj, observer.callback);
1061 Object.observe(obj, observer.callback);
1063 obj[prop] = 4; // ignored
1065 Object.defineProperty(obj, prop, {value: 6});
1066 Object.defineProperty(obj, prop, {value: 6}); // ignored
1067 Object.defineProperty(obj, prop, {value: 7});
1068 Object.defineProperty(obj, prop, {enumerable: desc.enumerable}); // ignored
1069 Object.defineProperty(obj, prop, {writable: false});
1070 obj[prop] = 7; // ignored
1071 Object.deliverChangeRecords(observer.callback);
1072 observer.assertCallbackRecords([
1073 { object: obj, name: prop, type: "update", oldValue: 1 },
1074 { object: obj, name: prop, type: "update", oldValue: 4 },
1075 { object: obj, name: prop, type: "update", oldValue: 5 },
1076 { object: obj, name: prop, type: "update", oldValue: 6 },
1077 { object: obj, name: prop, type: "reconfigure" },
1079 Object.unobserve(obj, observer.callback);
1082 // TODO(rafaelw) Enable when ES6 Proxies are implemented
1084 function createProxy(create, x) {
1086 getPropertyDescriptor: function(k) {
1087 for (var o = this.target; o; o = Object.getPrototypeOf(o)) {
1088 var desc = Object.getOwnPropertyDescriptor(o, k);
1089 if (desc) return desc;
1093 getOwnPropertyDescriptor: function(k) {
1094 return Object.getOwnPropertyDescriptor(this.target, k);
1096 defineProperty: function(k, desc) {
1097 var x = Object.defineProperty(this.target, k, desc);
1098 Object.deliverChangeRecords(this.callback);
1101 delete: function(k) {
1102 var x = delete this.target[k];
1103 Object.deliverChangeRecords(this.callback);
1106 getPropertyNames: function() {
1107 return Object.getOwnPropertyNames(this.target);
1109 target: {isProxy: true},
1110 callback: function(changeRecords) {
1111 print("callback", stringifyNoThrow(handler.proxy), stringifyNoThrow(got));
1112 for (var i in changeRecords) {
1113 var got = changeRecords[i];
1114 var change = {object: handler.proxy, name: got.name, type: got.type};
1115 if ("oldValue" in got) change.oldValue = got.oldValue;
1116 Object.getNotifier(handler.proxy).notify(change);
1120 Object.observe(handler.target, handler.callback);
1121 return handler.proxy = create(handler, x);
1129 (function(){ return arguments })(),
1130 (function(){ "use strict"; return arguments })(),
1131 Object(1), Object(true), Object("bla"),
1133 Object, Function, Date, RegExp,
1134 new Set, new Map, new WeakMap,
1135 new ArrayBuffer(10), new Int32Array(5)
1136 // TODO(rafaelw) Enable when ES6 Proxies are implemented.
1137 // createProxy(Proxy.create, null),
1138 // createProxy(Proxy.createFunction, function(){}),
1140 var properties = ["a", "1", 1, "length", "setPrototype", "name", "caller"];
1142 // Cases that yield non-standard results.
1143 function blacklisted(obj, prop) {
1144 return (obj instanceof Int32Array && prop == 1) ||
1145 (obj instanceof Int32Array && prop === "length") ||
1146 (obj instanceof ArrayBuffer && prop == 1)
1149 for (var i in objects) for (var j in properties) {
1150 var obj = objects[i];
1151 var prop = properties[j];
1152 if (blacklisted(obj, prop)) continue;
1153 var desc = Object.getOwnPropertyDescriptor(obj, prop);
1154 print("***", typeof obj, stringifyNoThrow(obj), prop);
1155 if (!desc || desc.configurable)
1156 TestObserveConfigurable(obj, prop);
1157 else if (desc.writable)
1158 TestObserveNonConfigurable(obj, prop, desc);
1162 // Observing array length (including truncation)
1164 var arr = ['a', 'b', 'c', 'd'];
1165 var arr2 = ['alpha', 'beta'];
1166 var arr3 = ['hello'];
1167 arr3[2] = 'goodbye';
1169 Object.defineProperty(arr, '0', {configurable: false});
1170 Object.defineProperty(arr, '2', {get: function(){}});
1171 Object.defineProperty(arr2, '0', {get: function(){}, configurable: false});
1172 Object.observe(arr, observer.callback);
1173 Array.observe(arr, observer2.callback);
1174 Object.observe(arr2, observer.callback);
1175 Array.observe(arr2, observer2.callback);
1176 Object.observe(arr3, observer.callback);
1177 Array.observe(arr3, observer2.callback);
1181 Object.defineProperty(arr, 'length', {writable: false});
1183 arr2.length = 1; // no change expected
1184 Object.defineProperty(arr2, 'length', {value: 1, writable: false});
1189 Object.defineProperty(arr3, 'length', {value: 5});
1191 Object.defineProperty(arr3, 'length', {value: 1, writable: false});
1192 Object.deliverChangeRecords(observer.callback);
1193 observer.assertCallbackRecords([
1194 { object: arr, name: '3', type: 'delete', oldValue: 'd' },
1195 { object: arr, name: '2', type: 'delete' },
1196 { object: arr, name: 'length', type: 'update', oldValue: 4 },
1197 { object: arr, name: '1', type: 'delete', oldValue: 'b' },
1198 { object: arr, name: 'length', type: 'update', oldValue: 2 },
1199 { object: arr, name: 'length', type: 'update', oldValue: 1 },
1200 { object: arr, name: 'length', type: 'reconfigure' },
1201 { object: arr2, name: '1', type: 'delete', oldValue: 'beta' },
1202 { object: arr2, name: 'length', type: 'update', oldValue: 2 },
1203 { object: arr2, name: 'length', type: 'reconfigure' },
1204 { object: arr3, name: '2', type: 'delete', oldValue: 'goodbye' },
1205 { object: arr3, name: '0', type: 'delete', oldValue: 'hello' },
1206 { object: arr3, name: 'length', type: 'update', oldValue: 6 },
1207 { object: arr3, name: 'length', type: 'update', oldValue: 0 },
1208 { object: arr3, name: 'length', type: 'update', oldValue: 1 },
1209 { object: arr3, name: 'length', type: 'update', oldValue: 2 },
1210 { object: arr3, name: 'length', type: 'update', oldValue: 1 },
1211 { object: arr3, name: '4', type: 'add' },
1212 { object: arr3, name: '4', type: 'delete', oldValue: 5 },
1213 // TODO(rafaelw): It breaks spec compliance to get two records here.
1214 // When the TODO in v8natives.js::DefineArrayProperty is addressed
1215 // which prevents DefineProperty from over-writing the magic length
1216 // property, these will collapse into a single record.
1217 { object: arr3, name: 'length', type: 'update', oldValue: 5 },
1218 { object: arr3, name: 'length', type: 'reconfigure' }
1220 Object.deliverChangeRecords(observer2.callback);
1221 observer2.assertCallbackRecords([
1222 { object: arr, type: 'splice', index: 2, removed: [, 'd'], addedCount: 0 },
1223 { object: arr, type: 'splice', index: 1, removed: ['b'], addedCount: 0 },
1224 { object: arr, type: 'splice', index: 1, removed: [], addedCount: 9 },
1225 { object: arr2, type: 'splice', index: 1, removed: ['beta'], addedCount: 0 },
1226 { object: arr3, type: 'splice', index: 0, removed: ['hello',, 'goodbye',,,,], addedCount: 0 },
1227 { object: arr3, type: 'splice', index: 0, removed: [], addedCount: 1 },
1228 { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 1 },
1229 { object: arr3, type: 'splice', index: 1, removed: [,], addedCount: 0 },
1230 { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 4 },
1231 { object: arr3, name: '4', type: 'add' },
1232 { object: arr3, type: 'splice', index: 1, removed: [,,,5], addedCount: 0 }
1236 // Updating length on large (slow) array
1238 var slow_arr = new Array(1000000000);
1239 slow_arr[500000000] = 'hello';
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.assertNotCalled();
1692 // Check that changes in observation status are detected in all IC states and
1693 // in optimized code, especially in cases usually using fast elements.
1696 "a[i] ? ++a[i] : a[i] = v",
1697 "a[i] ? a[i]++ : a[i] = v",
1698 "a[i] ? a[i] += 1 : a[i] = v",
1699 "a[i] ? a[i] -= -1 : a[i] = v",
1702 var props = [1, "1", "a"];
1704 function TestFastElements(prop, mutation, prepopulate, polymorphic, optimize) {
1705 var setElement = eval(
1706 "(function setElement(a, i, v) { " + mutation + "; " +
1707 "/* " + [].join.call(arguments, " ") + " */" +
1710 print("TestFastElements:", setElement);
1712 var arr = prepopulate ? [1, 2, 3, 4, 5] : [0];
1713 if (prepopulate) arr[prop] = 2; // for non-element case
1714 setElement(arr, prop, 3);
1715 setElement(arr, prop, 4);
1716 if (polymorphic) setElement(["M", "i", "l", "n", "e", "r"], 0, "m");
1717 if (optimize) %OptimizeFunctionOnNextCall(setElement);
1718 setElement(arr, prop, 5);
1721 Object.observe(arr, observer.callback);
1722 setElement(arr, prop, 989898);
1723 Object.deliverChangeRecords(observer.callback);
1724 observer.assertCallbackRecords([
1725 { object: arr, name: "" + prop, type: 'update', oldValue: 5 }
1729 for (var b1 = 0; b1 < 2; ++b1)
1730 for (var b2 = 0; b2 < 2; ++b2)
1731 for (var b3 = 0; b3 < 2; ++b3)
1732 for (var i in props)
1733 for (var j in mutation)
1734 TestFastElements(props[i], mutation[j], b1 != 0, b2 != 0, b3 != 0);
1739 "a.length += newSize - oldSize",
1740 "a.length -= oldSize - newSize",
1743 var mutationByIncr = [
1748 function TestFastElementsLength(
1749 mutation, polymorphic, optimize, oldSize, newSize) {
1750 var setLength = eval(
1751 "(function setLength(a, v) { " + mutation + "; " +
1752 "/* " + [].join.call(arguments, " ") + " */"
1755 print("TestFastElementsLength:", setLength);
1758 var arr = new Array(n);
1759 for (var i = 0; i < n; ++i) arr[i] = i;
1763 setLength(array(oldSize), newSize);
1764 setLength(array(oldSize), newSize);
1765 if (polymorphic) setLength(array(oldSize).map(isNaN), newSize);
1766 if (optimize) %OptimizeFunctionOnNextCall(setLength);
1767 setLength(array(oldSize), newSize);
1770 var arr = array(oldSize);
1771 Object.observe(arr, observer.callback);
1772 setLength(arr, newSize);
1773 Object.deliverChangeRecords(observer.callback);
1774 if (oldSize === newSize) {
1775 observer.assertNotCalled();
1777 var count = oldSize > newSize ? oldSize - newSize : 0;
1778 observer.assertRecordCount(count + 1);
1779 var lengthRecord = observer.records[count];
1780 assertSame(arr, lengthRecord.object);
1781 assertEquals('length', lengthRecord.name);
1782 assertEquals('update', lengthRecord.type);
1783 assertSame(oldSize, lengthRecord.oldValue);
1787 for (var b1 = 0; b1 < 2; ++b1)
1788 for (var b2 = 0; b2 < 2; ++b2)
1789 for (var n1 = 0; n1 < 3; ++n1)
1790 for (var n2 = 0; n2 < 3; ++n2)
1791 for (var i in mutation)
1792 TestFastElementsLength(mutation[i], b1 != 0, b2 != 0, 20*n1, 20*n2);
1794 for (var b1 = 0; b1 < 2; ++b1)
1795 for (var b2 = 0; b2 < 2; ++b2)
1796 for (var n = 0; n < 3; ++n)
1797 for (var i in mutationByIncr)
1798 TestFastElementsLength(mutationByIncr[i], b1 != 0, b2 != 0, 7*n, 7*n+1);