Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(549)

Side by Side Diff: test/mjsunit/es7/object-observe.js

Issue 190853007: Revert "Enable Object.observe by default" (Closed) Base URL: https://v8.googlecode.com/svn/branches/bleeding_edge
Patch Set: Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « test/mjsunit/debug-script.js ('k') | test/mjsunit/harmony/object-observe.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2012 the V8 project authors. All rights reserved.
2 // Redistribution and use in source and binary forms, with or without
3 // modification, are permitted provided that the following conditions are
4 // met:
5 //
6 // * Redistributions of source code must retain the above copyright
7 // notice, this list of conditions and the following disclaimer.
8 // * Redistributions in binary form must reproduce the above
9 // copyright notice, this list of conditions and the following
10 // disclaimer in the documentation and/or other materials provided
11 // with the distribution.
12 // * Neither the name of Google Inc. nor the names of its
13 // contributors may be used to endorse or promote products derived
14 // from this software without specific prior written permission.
15 //
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28 // Flags: --harmony-observation --harmony-proxies --harmony-collections
29 // Flags: --harmony-symbols --allow-natives-syntax
30
31 var allObservers = [];
32 function reset() {
33 allObservers.forEach(function(observer) { observer.reset(); });
34 }
35
36 function stringifyNoThrow(arg) {
37 try {
38 return JSON.stringify(arg);
39 } catch (e) {
40 return '{<circular reference>}';
41 }
42 }
43
44 function createObserver() {
45 "use strict"; // So that |this| in callback can be undefined.
46
47 var observer = {
48 records: undefined,
49 callbackCount: 0,
50 reset: function() {
51 this.records = undefined;
52 this.callbackCount = 0;
53 },
54 assertNotCalled: function() {
55 assertEquals(undefined, this.records);
56 assertEquals(0, this.callbackCount);
57 },
58 assertCalled: function() {
59 assertEquals(1, this.callbackCount);
60 },
61 assertRecordCount: function(count) {
62 this.assertCalled();
63 assertEquals(count, this.records.length);
64 },
65 assertCallbackRecords: function(recs) {
66 this.assertRecordCount(recs.length);
67 for (var i = 0; i < recs.length; i++) {
68 if ('name' in recs[i]) recs[i].name = String(recs[i].name);
69 print(i, stringifyNoThrow(this.records[i]), stringifyNoThrow(recs[i]));
70 assertSame(this.records[i].object, recs[i].object);
71 assertEquals('string', typeof recs[i].type);
72 assertPropertiesEqual(this.records[i], recs[i]);
73 }
74 }
75 };
76
77 observer.callback = function(r) {
78 assertEquals(undefined, this);
79 assertEquals('object', typeof r);
80 assertTrue(r instanceof Array)
81 observer.records = r;
82 observer.callbackCount++;
83 };
84
85 observer.reset();
86 allObservers.push(observer);
87 return observer;
88 }
89
90 var observer = createObserver();
91 var observer2 = createObserver();
92
93 assertEquals("function", typeof observer.callback);
94 assertEquals("function", typeof observer2.callback);
95
96 var obj = {};
97
98 function frozenFunction() {}
99 Object.freeze(frozenFunction);
100 var nonFunction = {};
101 var changeRecordWithAccessor = { type: 'foo' };
102 var recordCreated = false;
103 Object.defineProperty(changeRecordWithAccessor, 'name', {
104 get: function() {
105 recordCreated = true;
106 return "bar";
107 },
108 enumerable: true
109 })
110
111
112 // Object.observe
113 assertThrows(function() { Object.observe("non-object", observer.callback); },
114 TypeError);
115 assertThrows(function() { Object.observe(obj, nonFunction); }, TypeError);
116 assertThrows(function() { Object.observe(obj, frozenFunction); }, TypeError);
117 assertEquals(obj, Object.observe(obj, observer.callback, [1]));
118 assertEquals(obj, Object.observe(obj, observer.callback, [true]));
119 assertEquals(obj, Object.observe(obj, observer.callback, ['foo', null]));
120 assertEquals(obj, Object.observe(obj, observer.callback, [undefined]));
121 assertEquals(obj, Object.observe(obj, observer.callback,
122 ['foo', 'bar', 'baz']));
123 assertEquals(obj, Object.observe(obj, observer.callback, []));
124 assertEquals(obj, Object.observe(obj, observer.callback, undefined));
125 assertEquals(obj, Object.observe(obj, observer.callback));
126
127 // Object.unobserve
128 assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError);
129 assertThrows(function() { Object.unobserve(obj, nonFunction); }, TypeError);
130 assertEquals(obj, Object.unobserve(obj, observer.callback));
131
132
133 // Object.getNotifier
134 var notifier = Object.getNotifier(obj);
135 assertSame(notifier, Object.getNotifier(obj));
136 assertEquals(null, Object.getNotifier(Object.freeze({})));
137 assertFalse(notifier.hasOwnProperty('notify'));
138 assertEquals([], Object.keys(notifier));
139 var notifyDesc = Object.getOwnPropertyDescriptor(notifier.__proto__, 'notify');
140 assertTrue(notifyDesc.configurable);
141 assertTrue(notifyDesc.writable);
142 assertFalse(notifyDesc.enumerable);
143 assertThrows(function() { notifier.notify({}); }, TypeError);
144 assertThrows(function() { notifier.notify({ type: 4 }); }, TypeError);
145
146 assertThrows(function() { notifier.performChange(1, function(){}); }, TypeError) ;
147 assertThrows(function() { notifier.performChange(undefined, function(){}); }, Ty peError);
148 assertThrows(function() { notifier.performChange('foo', undefined); }, TypeError );
149 assertThrows(function() { notifier.performChange('foo', 'bar'); }, TypeError);
150 var global = this;
151 notifier.performChange('foo', function() {
152 assertEquals(global, this);
153 });
154
155 var notify = notifier.notify;
156 assertThrows(function() { notify.call(undefined, { type: 'a' }); }, TypeError);
157 assertThrows(function() { notify.call(null, { type: 'a' }); }, TypeError);
158 assertThrows(function() { notify.call(5, { type: 'a' }); }, TypeError);
159 assertThrows(function() { notify.call('hello', { type: 'a' }); }, TypeError);
160 assertThrows(function() { notify.call(false, { type: 'a' }); }, TypeError);
161 assertThrows(function() { notify.call({}, { type: 'a' }); }, TypeError);
162 assertFalse(recordCreated);
163 notifier.notify(changeRecordWithAccessor);
164 assertFalse(recordCreated); // not observed yet
165
166
167 // Object.deliverChangeRecords
168 assertThrows(function() { Object.deliverChangeRecords(nonFunction); }, TypeError );
169
170 Object.observe(obj, observer.callback);
171
172
173 // notify uses to [[CreateOwnProperty]] to create changeRecord;
174 reset();
175 var protoExpandoAccessed = false;
176 Object.defineProperty(Object.prototype, 'protoExpando',
177 {
178 configurable: true,
179 set: function() { protoExpandoAccessed = true; }
180 }
181 );
182 notifier.notify({ type: 'foo', protoExpando: 'val'});
183 assertFalse(protoExpandoAccessed);
184 delete Object.prototype.protoExpando;
185 Object.deliverChangeRecords(observer.callback);
186
187
188 // Multiple records are delivered.
189 reset();
190 notifier.notify({
191 type: 'update',
192 name: 'foo',
193 expando: 1
194 });
195
196 notifier.notify({
197 object: notifier, // object property is ignored
198 type: 'delete',
199 name: 'bar',
200 expando2: 'str'
201 });
202 Object.deliverChangeRecords(observer.callback);
203 observer.assertCallbackRecords([
204 { object: obj, name: 'foo', type: 'update', expando: 1 },
205 { object: obj, name: 'bar', type: 'delete', expando2: 'str' }
206 ]);
207
208 // Non-string accept values are coerced to strings
209 reset();
210 Object.observe(obj, observer.callback, [true, 1, null, undefined]);
211 notifier = Object.getNotifier(obj);
212 notifier.notify({ type: 'true' });
213 notifier.notify({ type: 'false' });
214 notifier.notify({ type: '1' });
215 notifier.notify({ type: '-1' });
216 notifier.notify({ type: 'null' });
217 notifier.notify({ type: 'nill' });
218 notifier.notify({ type: 'undefined' });
219 notifier.notify({ type: 'defined' });
220 Object.deliverChangeRecords(observer.callback);
221 observer.assertCallbackRecords([
222 { object: obj, type: 'true' },
223 { object: obj, type: '1' },
224 { object: obj, type: 'null' },
225 { object: obj, type: 'undefined' }
226 ]);
227
228 // No delivery takes place if no records are pending
229 reset();
230 Object.deliverChangeRecords(observer.callback);
231 observer.assertNotCalled();
232
233
234 // Multiple observation has no effect.
235 reset();
236 Object.observe(obj, observer.callback);
237 Object.observe(obj, observer.callback);
238 Object.getNotifier(obj).notify({
239 type: 'update',
240 });
241 Object.deliverChangeRecords(observer.callback);
242 observer.assertCalled();
243
244
245 // Observation can be stopped.
246 reset();
247 Object.unobserve(obj, observer.callback);
248 Object.getNotifier(obj).notify({
249 type: 'update',
250 });
251 Object.deliverChangeRecords(observer.callback);
252 observer.assertNotCalled();
253
254
255 // Multiple unobservation has no effect
256 reset();
257 Object.unobserve(obj, observer.callback);
258 Object.unobserve(obj, observer.callback);
259 Object.getNotifier(obj).notify({
260 type: 'update',
261 });
262 Object.deliverChangeRecords(observer.callback);
263 observer.assertNotCalled();
264
265
266 // Re-observation works and only includes changeRecords after of call.
267 reset();
268 Object.getNotifier(obj).notify({
269 type: 'update',
270 });
271 Object.observe(obj, observer.callback);
272 Object.getNotifier(obj).notify({
273 type: 'update',
274 });
275 records = undefined;
276 Object.deliverChangeRecords(observer.callback);
277 observer.assertRecordCount(1);
278
279 // Get notifier prior to observing
280 reset();
281 var obj = {};
282 Object.getNotifier(obj);
283 Object.observe(obj, observer.callback);
284 obj.id = 1;
285 Object.deliverChangeRecords(observer.callback);
286 observer.assertCallbackRecords([
287 { object: obj, type: 'add', name: 'id' },
288 ]);
289
290 // The empty-string property is observable
291 reset();
292 var obj = {};
293 Object.observe(obj, observer.callback);
294 obj[''] = '';
295 obj[''] = ' ';
296 delete obj[''];
297 Object.deliverChangeRecords(observer.callback);
298 observer.assertCallbackRecords([
299 { object: obj, type: 'add', name: '' },
300 { object: obj, type: 'update', name: '', oldValue: '' },
301 { object: obj, type: 'delete', name: '', oldValue: ' ' },
302 ]);
303
304 // Object.preventExtensions
305 reset();
306 var obj = { foo: 'bar'};
307 Object.observe(obj, observer.callback);
308 obj.baz = 'bat';
309 Object.preventExtensions(obj);
310
311 Object.deliverChangeRecords(observer.callback);
312 observer.assertCallbackRecords([
313 { object: obj, type: 'add', name: 'baz' },
314 { object: obj, type: 'preventExtensions' },
315 ]);
316
317 reset();
318 var obj = { foo: 'bar'};
319 Object.preventExtensions(obj);
320 Object.observe(obj, observer.callback);
321 Object.preventExtensions(obj);
322 Object.deliverChangeRecords(observer.callback);
323 observer.assertNotCalled();
324
325 // Object.freeze
326 reset();
327 var obj = { a: 'a' };
328 Object.defineProperty(obj, 'b', {
329 writable: false,
330 configurable: true,
331 value: 'b'
332 });
333 Object.defineProperty(obj, 'c', {
334 writable: true,
335 configurable: false,
336 value: 'c'
337 });
338 Object.defineProperty(obj, 'd', {
339 writable: false,
340 configurable: false,
341 value: 'd'
342 });
343 Object.observe(obj, observer.callback);
344 Object.freeze(obj);
345
346 Object.deliverChangeRecords(observer.callback);
347 observer.assertCallbackRecords([
348 { object: obj, type: 'reconfigure', name: 'a' },
349 { object: obj, type: 'reconfigure', name: 'b' },
350 { object: obj, type: 'reconfigure', name: 'c' },
351 { object: obj, type: 'preventExtensions' },
352 ]);
353
354 reset();
355 var obj = { foo: 'bar'};
356 Object.freeze(obj);
357 Object.observe(obj, observer.callback);
358 Object.freeze(obj);
359 Object.deliverChangeRecords(observer.callback);
360 observer.assertNotCalled();
361
362 // Object.seal
363 reset();
364 var obj = { a: 'a' };
365 Object.defineProperty(obj, 'b', {
366 writable: false,
367 configurable: true,
368 value: 'b'
369 });
370 Object.defineProperty(obj, 'c', {
371 writable: true,
372 configurable: false,
373 value: 'c'
374 });
375 Object.defineProperty(obj, 'd', {
376 writable: false,
377 configurable: false,
378 value: 'd'
379 });
380 Object.observe(obj, observer.callback);
381 Object.seal(obj);
382
383 Object.deliverChangeRecords(observer.callback);
384 observer.assertCallbackRecords([
385 { object: obj, type: 'reconfigure', name: 'a' },
386 { object: obj, type: 'reconfigure', name: 'b' },
387 { object: obj, type: 'preventExtensions' },
388 ]);
389
390 reset();
391 var obj = { foo: 'bar'};
392 Object.seal(obj);
393 Object.observe(obj, observer.callback);
394 Object.seal(obj);
395 Object.deliverChangeRecords(observer.callback);
396 observer.assertNotCalled();
397
398 // Observing a continuous stream of changes, while itermittantly unobserving.
399 reset();
400 var obj = {};
401 Object.observe(obj, observer.callback);
402 Object.getNotifier(obj).notify({
403 type: 'update',
404 val: 1
405 });
406
407 Object.unobserve(obj, observer.callback);
408 Object.getNotifier(obj).notify({
409 type: 'update',
410 val: 2
411 });
412
413 Object.observe(obj, observer.callback);
414 Object.getNotifier(obj).notify({
415 type: 'update',
416 val: 3
417 });
418
419 Object.unobserve(obj, observer.callback);
420 Object.getNotifier(obj).notify({
421 type: 'update',
422 val: 4
423 });
424
425 Object.observe(obj, observer.callback);
426 Object.getNotifier(obj).notify({
427 type: 'update',
428 val: 5
429 });
430
431 Object.unobserve(obj, observer.callback);
432 Object.deliverChangeRecords(observer.callback);
433 observer.assertCallbackRecords([
434 { object: obj, type: 'update', val: 1 },
435 { object: obj, type: 'update', val: 3 },
436 { object: obj, type: 'update', val: 5 }
437 ]);
438
439 // Accept
440 reset();
441 Object.observe(obj, observer.callback, ['somethingElse']);
442 Object.getNotifier(obj).notify({
443 type: 'add'
444 });
445 Object.getNotifier(obj).notify({
446 type: 'update'
447 });
448 Object.getNotifier(obj).notify({
449 type: 'delete'
450 });
451 Object.getNotifier(obj).notify({
452 type: 'reconfigure'
453 });
454 Object.getNotifier(obj).notify({
455 type: 'setPrototype'
456 });
457 Object.deliverChangeRecords(observer.callback);
458 observer.assertNotCalled();
459
460 reset();
461 Object.observe(obj, observer.callback, ['add', 'delete', 'setPrototype']);
462 Object.getNotifier(obj).notify({
463 type: 'add'
464 });
465 Object.getNotifier(obj).notify({
466 type: 'update'
467 });
468 Object.getNotifier(obj).notify({
469 type: 'delete'
470 });
471 Object.getNotifier(obj).notify({
472 type: 'delete'
473 });
474 Object.getNotifier(obj).notify({
475 type: 'reconfigure'
476 });
477 Object.getNotifier(obj).notify({
478 type: 'setPrototype'
479 });
480 Object.deliverChangeRecords(observer.callback);
481 observer.assertCallbackRecords([
482 { object: obj, type: 'add' },
483 { object: obj, type: 'delete' },
484 { object: obj, type: 'delete' },
485 { object: obj, type: 'setPrototype' }
486 ]);
487
488 reset();
489 Object.observe(obj, observer.callback, ['update', 'foo']);
490 Object.getNotifier(obj).notify({
491 type: 'add'
492 });
493 Object.getNotifier(obj).notify({
494 type: 'update'
495 });
496 Object.getNotifier(obj).notify({
497 type: 'delete'
498 });
499 Object.getNotifier(obj).notify({
500 type: 'foo'
501 });
502 Object.getNotifier(obj).notify({
503 type: 'bar'
504 });
505 Object.getNotifier(obj).notify({
506 type: 'foo'
507 });
508 Object.deliverChangeRecords(observer.callback);
509 observer.assertCallbackRecords([
510 { object: obj, type: 'update' },
511 { object: obj, type: 'foo' },
512 { object: obj, type: 'foo' }
513 ]);
514
515 reset();
516 function Thingy(a, b, c) {
517 this.a = a;
518 this.b = b;
519 }
520
521 Thingy.MULTIPLY = 'multiply';
522 Thingy.INCREMENT = 'increment';
523 Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply';
524
525 Thingy.prototype = {
526 increment: function(amount) {
527 var notifier = Object.getNotifier(this);
528
529 var self = this;
530 notifier.performChange(Thingy.INCREMENT, function() {
531 self.a += amount;
532 self.b += amount;
533
534 return {
535 incremented: amount
536 }; // implicit notify
537 });
538 },
539
540 multiply: function(amount) {
541 var notifier = Object.getNotifier(this);
542
543 var self = this;
544 notifier.performChange(Thingy.MULTIPLY, function() {
545 self.a *= amount;
546 self.b *= amount;
547
548 return {
549 multiplied: amount
550 }; // implicit notify
551 });
552 },
553
554 incrementAndMultiply: function(incAmount, multAmount) {
555 var notifier = Object.getNotifier(this);
556
557 var self = this;
558 notifier.performChange(Thingy.INCREMENT_AND_MULTIPLY, function() {
559 self.increment(incAmount);
560 self.multiply(multAmount);
561
562 return {
563 incremented: incAmount,
564 multiplied: multAmount
565 }; // implicit notify
566 });
567 }
568 }
569
570 Thingy.observe = function(thingy, callback) {
571 Object.observe(thingy, callback, [Thingy.INCREMENT,
572 Thingy.MULTIPLY,
573 Thingy.INCREMENT_AND_MULTIPLY,
574 'update']);
575 }
576
577 Thingy.unobserve = function(thingy, callback) {
578 Object.unobserve(thingy);
579 }
580
581 var thingy = new Thingy(2, 4);
582
583 Object.observe(thingy, observer.callback);
584 Thingy.observe(thingy, observer2.callback);
585 thingy.increment(3); // { a: 5, b: 7 }
586 thingy.b++; // { a: 5, b: 8 }
587 thingy.multiply(2); // { a: 10, b: 16 }
588 thingy.a++; // { a: 11, b: 16 }
589 thingy.incrementAndMultiply(2, 2); // { a: 26, b: 36 }
590
591 Object.deliverChangeRecords(observer.callback);
592 Object.deliverChangeRecords(observer2.callback);
593 observer.assertCallbackRecords([
594 { object: thingy, type: 'update', name: 'a', oldValue: 2 },
595 { object: thingy, type: 'update', name: 'b', oldValue: 4 },
596 { object: thingy, type: 'update', name: 'b', oldValue: 7 },
597 { object: thingy, type: 'update', name: 'a', oldValue: 5 },
598 { object: thingy, type: 'update', name: 'b', oldValue: 8 },
599 { object: thingy, type: 'update', name: 'a', oldValue: 10 },
600 { object: thingy, type: 'update', name: 'a', oldValue: 11 },
601 { object: thingy, type: 'update', name: 'b', oldValue: 16 },
602 { object: thingy, type: 'update', name: 'a', oldValue: 13 },
603 { object: thingy, type: 'update', name: 'b', oldValue: 18 },
604 ]);
605 observer2.assertCallbackRecords([
606 { object: thingy, type: Thingy.INCREMENT, incremented: 3 },
607 { object: thingy, type: 'update', name: 'b', oldValue: 7 },
608 { object: thingy, type: Thingy.MULTIPLY, multiplied: 2 },
609 { object: thingy, type: 'update', name: 'a', oldValue: 10 },
610 {
611 object: thingy,
612 type: Thingy.INCREMENT_AND_MULTIPLY,
613 incremented: 2,
614 multiplied: 2
615 }
616 ]);
617
618 // ArrayPush cached stub
619 reset();
620
621 function pushMultiple(arr) {
622 arr.push('a');
623 arr.push('b');
624 arr.push('c');
625 }
626
627 for (var i = 0; i < 5; i++) {
628 var arr = [];
629 pushMultiple(arr);
630 }
631
632 for (var i = 0; i < 5; i++) {
633 reset();
634 var arr = [];
635 Object.observe(arr, observer.callback);
636 pushMultiple(arr);
637 Object.unobserve(arr, observer.callback);
638 Object.deliverChangeRecords(observer.callback);
639 observer.assertCallbackRecords([
640 { object: arr, type: 'add', name: '0' },
641 { object: arr, type: 'update', name: 'length', oldValue: 0 },
642 { object: arr, type: 'add', name: '1' },
643 { object: arr, type: 'update', name: 'length', oldValue: 1 },
644 { object: arr, type: 'add', name: '2' },
645 { object: arr, type: 'update', name: 'length', oldValue: 2 },
646 ]);
647 }
648
649
650 // ArrayPop cached stub
651 reset();
652
653 function popMultiple(arr) {
654 arr.pop();
655 arr.pop();
656 arr.pop();
657 }
658
659 for (var i = 0; i < 5; i++) {
660 var arr = ['a', 'b', 'c'];
661 popMultiple(arr);
662 }
663
664 for (var i = 0; i < 5; i++) {
665 reset();
666 var arr = ['a', 'b', 'c'];
667 Object.observe(arr, observer.callback);
668 popMultiple(arr);
669 Object.unobserve(arr, observer.callback);
670 Object.deliverChangeRecords(observer.callback);
671 observer.assertCallbackRecords([
672 { object: arr, type: 'delete', name: '2', oldValue: 'c' },
673 { object: arr, type: 'update', name: 'length', oldValue: 3 },
674 { object: arr, type: 'delete', name: '1', oldValue: 'b' },
675 { object: arr, type: 'update', name: 'length', oldValue: 2 },
676 { object: arr, type: 'delete', name: '0', oldValue: 'a' },
677 { object: arr, type: 'update', name: 'length', oldValue: 1 },
678 ]);
679 }
680
681
682 reset();
683 function RecursiveThingy() {}
684
685 RecursiveThingy.MULTIPLY_FIRST_N = 'multiplyFirstN';
686
687 RecursiveThingy.prototype = {
688 __proto__: Array.prototype,
689
690 multiplyFirstN: function(amount, n) {
691 if (!n)
692 return;
693 var notifier = Object.getNotifier(this);
694 var self = this;
695 notifier.performChange(RecursiveThingy.MULTIPLY_FIRST_N, function() {
696 self[n-1] = self[n-1]*amount;
697 self.multiplyFirstN(amount, n-1);
698 });
699
700 notifier.notify({
701 type: RecursiveThingy.MULTIPLY_FIRST_N,
702 multiplied: amount,
703 n: n
704 });
705 },
706 }
707
708 RecursiveThingy.observe = function(thingy, callback) {
709 Object.observe(thingy, callback, [RecursiveThingy.MULTIPLY_FIRST_N]);
710 }
711
712 RecursiveThingy.unobserve = function(thingy, callback) {
713 Object.unobserve(thingy);
714 }
715
716 var thingy = new RecursiveThingy;
717 thingy.push(1, 2, 3, 4);
718
719 Object.observe(thingy, observer.callback);
720 RecursiveThingy.observe(thingy, observer2.callback);
721 thingy.multiplyFirstN(2, 3); // [2, 4, 6, 4]
722
723 Object.deliverChangeRecords(observer.callback);
724 Object.deliverChangeRecords(observer2.callback);
725 observer.assertCallbackRecords([
726 { object: thingy, type: 'update', name: '2', oldValue: 3 },
727 { object: thingy, type: 'update', name: '1', oldValue: 2 },
728 { object: thingy, type: 'update', name: '0', oldValue: 1 }
729 ]);
730 observer2.assertCallbackRecords([
731 { object: thingy, type: RecursiveThingy.MULTIPLY_FIRST_N, multiplied: 2, n: 3 }
732 ]);
733
734 reset();
735 function DeckSuit() {
736 this.push('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'A', 'Q', 'K');
737 }
738
739 DeckSuit.SHUFFLE = 'shuffle';
740
741 DeckSuit.prototype = {
742 __proto__: Array.prototype,
743
744 shuffle: function() {
745 var notifier = Object.getNotifier(this);
746 var self = this;
747 notifier.performChange(DeckSuit.SHUFFLE, function() {
748 self.reverse();
749 self.sort(function() { return Math.random()* 2 - 1; });
750 var cut = self.splice(0, 6);
751 Array.prototype.push.apply(self, cut);
752 self.reverse();
753 self.sort(function() { return Math.random()* 2 - 1; });
754 var cut = self.splice(0, 6);
755 Array.prototype.push.apply(self, cut);
756 self.reverse();
757 self.sort(function() { return Math.random()* 2 - 1; });
758 });
759
760 notifier.notify({
761 type: DeckSuit.SHUFFLE
762 });
763 },
764 }
765
766 DeckSuit.observe = function(thingy, callback) {
767 Object.observe(thingy, callback, [DeckSuit.SHUFFLE]);
768 }
769
770 DeckSuit.unobserve = function(thingy, callback) {
771 Object.unobserve(thingy);
772 }
773
774 var deck = new DeckSuit;
775
776 DeckSuit.observe(deck, observer2.callback);
777 deck.shuffle();
778
779 Object.deliverChangeRecords(observer2.callback);
780 observer2.assertCallbackRecords([
781 { object: deck, type: DeckSuit.SHUFFLE }
782 ]);
783
784 // Observing multiple objects; records appear in order.
785 reset();
786 var obj2 = {};
787 var obj3 = {}
788 Object.observe(obj, observer.callback);
789 Object.observe(obj3, observer.callback);
790 Object.observe(obj2, observer.callback);
791 Object.getNotifier(obj).notify({
792 type: 'add',
793 });
794 Object.getNotifier(obj2).notify({
795 type: 'update',
796 });
797 Object.getNotifier(obj3).notify({
798 type: 'delete',
799 });
800 Object.observe(obj3, observer.callback);
801 Object.deliverChangeRecords(observer.callback);
802 observer.assertCallbackRecords([
803 { object: obj, type: 'add' },
804 { object: obj2, type: 'update' },
805 { object: obj3, type: 'delete' }
806 ]);
807
808
809 // Recursive observation.
810 var obj = {a: 1};
811 var callbackCount = 0;
812 function recursiveObserver(r) {
813 assertEquals(1, r.length);
814 ++callbackCount;
815 if (r[0].oldValue < 100) ++obj[r[0].name];
816 }
817 Object.observe(obj, recursiveObserver);
818 ++obj.a;
819 Object.deliverChangeRecords(recursiveObserver);
820 assertEquals(100, callbackCount);
821
822 var obj1 = {a: 1};
823 var obj2 = {a: 1};
824 var recordCount = 0;
825 function recursiveObserver2(r) {
826 recordCount += r.length;
827 if (r[0].oldValue < 100) {
828 ++obj1.a;
829 ++obj2.a;
830 }
831 }
832 Object.observe(obj1, recursiveObserver2);
833 Object.observe(obj2, recursiveObserver2);
834 ++obj1.a;
835 Object.deliverChangeRecords(recursiveObserver2);
836 assertEquals(199, recordCount);
837
838
839 // Observing named properties.
840 reset();
841 var obj = {a: 1}
842 Object.observe(obj, observer.callback);
843 obj.a = 2;
844 obj["a"] = 3;
845 delete obj.a;
846 obj.a = 4;
847 obj.a = 4; // ignored
848 obj.a = 5;
849 Object.defineProperty(obj, "a", {value: 6});
850 Object.defineProperty(obj, "a", {writable: false});
851 obj.a = 7; // ignored
852 Object.defineProperty(obj, "a", {value: 8});
853 Object.defineProperty(obj, "a", {value: 7, writable: true});
854 Object.defineProperty(obj, "a", {get: function() {}});
855 Object.defineProperty(obj, "a", {get: frozenFunction});
856 Object.defineProperty(obj, "a", {get: frozenFunction}); // ignored
857 Object.defineProperty(obj, "a", {get: frozenFunction, set: frozenFunction});
858 Object.defineProperty(obj, "a", {set: frozenFunction}); // ignored
859 Object.defineProperty(obj, "a", {get: undefined, set: frozenFunction});
860 delete obj.a;
861 delete obj.a;
862 Object.defineProperty(obj, "a", {get: function() {}, configurable: true});
863 Object.defineProperty(obj, "a", {value: 9, writable: true});
864 obj.a = 10;
865 ++obj.a;
866 obj.a++;
867 obj.a *= 3;
868 delete obj.a;
869 Object.defineProperty(obj, "a", {value: 11, configurable: true});
870 Object.deliverChangeRecords(observer.callback);
871 observer.assertCallbackRecords([
872 { object: obj, name: "a", type: "update", oldValue: 1 },
873 { object: obj, name: "a", type: "update", oldValue: 2 },
874 { object: obj, name: "a", type: "delete", oldValue: 3 },
875 { object: obj, name: "a", type: "add" },
876 { object: obj, name: "a", type: "update", oldValue: 4 },
877 { object: obj, name: "a", type: "update", oldValue: 5 },
878 { object: obj, name: "a", type: "reconfigure" },
879 { object: obj, name: "a", type: "update", oldValue: 6 },
880 { object: obj, name: "a", type: "reconfigure", oldValue: 8 },
881 { object: obj, name: "a", type: "reconfigure", oldValue: 7 },
882 { object: obj, name: "a", type: "reconfigure" },
883 { object: obj, name: "a", type: "reconfigure" },
884 { object: obj, name: "a", type: "reconfigure" },
885 { object: obj, name: "a", type: "delete" },
886 { object: obj, name: "a", type: "add" },
887 { object: obj, name: "a", type: "reconfigure" },
888 { object: obj, name: "a", type: "update", oldValue: 9 },
889 { object: obj, name: "a", type: "update", oldValue: 10 },
890 { object: obj, name: "a", type: "update", oldValue: 11 },
891 { object: obj, name: "a", type: "update", oldValue: 12 },
892 { object: obj, name: "a", type: "delete", oldValue: 36 },
893 { object: obj, name: "a", type: "add" },
894 ]);
895
896
897 // Observing indexed properties.
898 reset();
899 var obj = {'1': 1}
900 Object.observe(obj, observer.callback);
901 obj[1] = 2;
902 obj[1] = 3;
903 delete obj[1];
904 obj[1] = 4;
905 obj[1] = 4; // ignored
906 obj[1] = 5;
907 Object.defineProperty(obj, "1", {value: 6});
908 Object.defineProperty(obj, "1", {writable: false});
909 obj[1] = 7; // ignored
910 Object.defineProperty(obj, "1", {value: 8});
911 Object.defineProperty(obj, "1", {value: 7, writable: true});
912 Object.defineProperty(obj, "1", {get: function() {}});
913 Object.defineProperty(obj, "1", {get: frozenFunction});
914 Object.defineProperty(obj, "1", {get: frozenFunction}); // ignored
915 Object.defineProperty(obj, "1", {get: frozenFunction, set: frozenFunction});
916 Object.defineProperty(obj, "1", {set: frozenFunction}); // ignored
917 Object.defineProperty(obj, "1", {get: undefined, set: frozenFunction});
918 delete obj[1];
919 delete obj[1];
920 Object.defineProperty(obj, "1", {get: function() {}, configurable: true});
921 Object.defineProperty(obj, "1", {value: 9, writable: true});
922 obj[1] = 10;
923 ++obj[1];
924 obj[1]++;
925 obj[1] *= 3;
926 delete obj[1];
927 Object.defineProperty(obj, "1", {value: 11, configurable: true});
928 Object.deliverChangeRecords(observer.callback);
929 observer.assertCallbackRecords([
930 { object: obj, name: "1", type: "update", oldValue: 1 },
931 { object: obj, name: "1", type: "update", oldValue: 2 },
932 { object: obj, name: "1", type: "delete", oldValue: 3 },
933 { object: obj, name: "1", type: "add" },
934 { object: obj, name: "1", type: "update", oldValue: 4 },
935 { object: obj, name: "1", type: "update", oldValue: 5 },
936 { object: obj, name: "1", type: "reconfigure" },
937 { object: obj, name: "1", type: "update", oldValue: 6 },
938 { object: obj, name: "1", type: "reconfigure", oldValue: 8 },
939 { object: obj, name: "1", type: "reconfigure", oldValue: 7 },
940 { object: obj, name: "1", type: "reconfigure" },
941 { object: obj, name: "1", type: "reconfigure" },
942 { object: obj, name: "1", type: "reconfigure" },
943 { object: obj, name: "1", type: "delete" },
944 { object: obj, name: "1", type: "add" },
945 { object: obj, name: "1", type: "reconfigure" },
946 { object: obj, name: "1", type: "update", oldValue: 9 },
947 { object: obj, name: "1", type: "update", oldValue: 10 },
948 { object: obj, name: "1", type: "update", oldValue: 11 },
949 { object: obj, name: "1", type: "update", oldValue: 12 },
950 { object: obj, name: "1", type: "delete", oldValue: 36 },
951 { object: obj, name: "1", type: "add" },
952 ]);
953
954
955 // Observing symbol properties (not).
956 print("*****")
957 reset();
958 var obj = {}
959 var symbol = Symbol("secret");
960 Object.observe(obj, observer.callback);
961 obj[symbol] = 3;
962 delete obj[symbol];
963 Object.defineProperty(obj, symbol, {get: function() {}, configurable: true});
964 Object.defineProperty(obj, symbol, {value: 6});
965 Object.defineProperty(obj, symbol, {writable: false});
966 delete obj[symbol];
967 Object.defineProperty(obj, symbol, {value: 7});
968 ++obj[symbol];
969 obj[symbol]++;
970 obj[symbol] *= 3;
971 delete obj[symbol];
972 obj.__defineSetter__(symbol, function() {});
973 obj.__defineGetter__(symbol, function() {});
974 Object.deliverChangeRecords(observer.callback);
975 observer.assertNotCalled();
976
977
978 // Test all kinds of objects generically.
979 function TestObserveConfigurable(obj, prop) {
980 reset();
981 Object.observe(obj, observer.callback);
982 Object.unobserve(obj, observer.callback);
983 obj[prop] = 1;
984 Object.observe(obj, observer.callback);
985 obj[prop] = 2;
986 obj[prop] = 3;
987 delete obj[prop];
988 obj[prop] = 4;
989 obj[prop] = 4; // ignored
990 obj[prop] = 5;
991 Object.defineProperty(obj, prop, {value: 6});
992 Object.defineProperty(obj, prop, {writable: false});
993 obj[prop] = 7; // ignored
994 Object.defineProperty(obj, prop, {value: 8});
995 Object.defineProperty(obj, prop, {value: 7, writable: true});
996 Object.defineProperty(obj, prop, {get: function() {}});
997 Object.defineProperty(obj, prop, {get: frozenFunction});
998 Object.defineProperty(obj, prop, {get: frozenFunction}); // ignored
999 Object.defineProperty(obj, prop, {get: frozenFunction, set: frozenFunction});
1000 Object.defineProperty(obj, prop, {set: frozenFunction}); // ignored
1001 Object.defineProperty(obj, prop, {get: undefined, set: frozenFunction});
1002 obj.__defineSetter__(prop, frozenFunction); // ignored
1003 obj.__defineSetter__(prop, function() {});
1004 obj.__defineGetter__(prop, function() {});
1005 delete obj[prop];
1006 delete obj[prop]; // ignored
1007 obj.__defineGetter__(prop, function() {});
1008 delete obj[prop];
1009 Object.defineProperty(obj, prop, {get: function() {}, configurable: true});
1010 Object.defineProperty(obj, prop, {value: 9, writable: true});
1011 obj[prop] = 10;
1012 ++obj[prop];
1013 obj[prop]++;
1014 obj[prop] *= 3;
1015 delete obj[prop];
1016 Object.defineProperty(obj, prop, {value: 11, configurable: true});
1017 Object.deliverChangeRecords(observer.callback);
1018 observer.assertCallbackRecords([
1019 { object: obj, name: prop, type: "update", oldValue: 1 },
1020 { object: obj, name: prop, type: "update", oldValue: 2 },
1021 { object: obj, name: prop, type: "delete", oldValue: 3 },
1022 { object: obj, name: prop, type: "add" },
1023 { object: obj, name: prop, type: "update", oldValue: 4 },
1024 { object: obj, name: prop, type: "update", oldValue: 5 },
1025 { object: obj, name: prop, type: "reconfigure" },
1026 { object: obj, name: prop, type: "update", oldValue: 6 },
1027 { object: obj, name: prop, type: "reconfigure", oldValue: 8 },
1028 { object: obj, name: prop, type: "reconfigure", oldValue: 7 },
1029 { object: obj, name: prop, type: "reconfigure" },
1030 { object: obj, name: prop, type: "reconfigure" },
1031 { object: obj, name: prop, type: "reconfigure" },
1032 { object: obj, name: prop, type: "reconfigure" },
1033 { object: obj, name: prop, type: "reconfigure" },
1034 { object: obj, name: prop, type: "delete" },
1035 { object: obj, name: prop, type: "add" },
1036 { object: obj, name: prop, type: "delete" },
1037 { object: obj, name: prop, type: "add" },
1038 { object: obj, name: prop, type: "reconfigure" },
1039 { object: obj, name: prop, type: "update", oldValue: 9 },
1040 { object: obj, name: prop, type: "update", oldValue: 10 },
1041 { object: obj, name: prop, type: "update", oldValue: 11 },
1042 { object: obj, name: prop, type: "update", oldValue: 12 },
1043 { object: obj, name: prop, type: "delete", oldValue: 36 },
1044 { object: obj, name: prop, type: "add" },
1045 ]);
1046 Object.unobserve(obj, observer.callback);
1047 delete obj[prop];
1048 }
1049
1050 function TestObserveNonConfigurable(obj, prop, desc) {
1051 reset();
1052 Object.observe(obj, observer.callback);
1053 Object.unobserve(obj, observer.callback);
1054 obj[prop] = 1;
1055 Object.observe(obj, observer.callback);
1056 obj[prop] = 4;
1057 obj[prop] = 4; // ignored
1058 obj[prop] = 5;
1059 Object.defineProperty(obj, prop, {value: 6});
1060 Object.defineProperty(obj, prop, {value: 6}); // ignored
1061 Object.defineProperty(obj, prop, {value: 7});
1062 Object.defineProperty(obj, prop, {enumerable: desc.enumerable}); // ignored
1063 Object.defineProperty(obj, prop, {writable: false});
1064 obj[prop] = 7; // ignored
1065 Object.deliverChangeRecords(observer.callback);
1066 observer.assertCallbackRecords([
1067 { object: obj, name: prop, type: "update", oldValue: 1 },
1068 { object: obj, name: prop, type: "update", oldValue: 4 },
1069 { object: obj, name: prop, type: "update", oldValue: 5 },
1070 { object: obj, name: prop, type: "update", oldValue: 6 },
1071 { object: obj, name: prop, type: "reconfigure" },
1072 ]);
1073 Object.unobserve(obj, observer.callback);
1074 }
1075
1076 function createProxy(create, x) {
1077 var handler = {
1078 getPropertyDescriptor: function(k) {
1079 for (var o = this.target; o; o = Object.getPrototypeOf(o)) {
1080 var desc = Object.getOwnPropertyDescriptor(o, k);
1081 if (desc) return desc;
1082 }
1083 return undefined;
1084 },
1085 getOwnPropertyDescriptor: function(k) {
1086 return Object.getOwnPropertyDescriptor(this.target, k);
1087 },
1088 defineProperty: function(k, desc) {
1089 var x = Object.defineProperty(this.target, k, desc);
1090 Object.deliverChangeRecords(this.callback);
1091 return x;
1092 },
1093 delete: function(k) {
1094 var x = delete this.target[k];
1095 Object.deliverChangeRecords(this.callback);
1096 return x;
1097 },
1098 getPropertyNames: function() {
1099 return Object.getOwnPropertyNames(this.target);
1100 },
1101 target: {isProxy: true},
1102 callback: function(changeRecords) {
1103 print("callback", stringifyNoThrow(handler.proxy), stringifyNoThrow(got));
1104 for (var i in changeRecords) {
1105 var got = changeRecords[i];
1106 var change = {object: handler.proxy, name: got.name, type: got.type};
1107 if ("oldValue" in got) change.oldValue = got.oldValue;
1108 Object.getNotifier(handler.proxy).notify(change);
1109 }
1110 },
1111 };
1112 Object.observe(handler.target, handler.callback);
1113 return handler.proxy = create(handler, x);
1114 }
1115
1116 var objects = [
1117 {},
1118 [],
1119 this, // global object
1120 function(){},
1121 (function(){ return arguments })(),
1122 (function(){ "use strict"; return arguments })(),
1123 Object(1), Object(true), Object("bla"),
1124 new Date(),
1125 Object, Function, Date, RegExp,
1126 new Set, new Map, new WeakMap,
1127 new ArrayBuffer(10), new Int32Array(5),
1128 createProxy(Proxy.create, null),
1129 createProxy(Proxy.createFunction, function(){}),
1130 ];
1131 var properties = ["a", "1", 1, "length", "setPrototype", "name", "caller"];
1132
1133 // Cases that yield non-standard results.
1134 function blacklisted(obj, prop) {
1135 return (obj instanceof Int32Array && prop == 1) ||
1136 (obj instanceof Int32Array && prop === "length") ||
1137 (obj instanceof ArrayBuffer && prop == 1)
1138 }
1139
1140 for (var i in objects) for (var j in properties) {
1141 var obj = objects[i];
1142 var prop = properties[j];
1143 if (blacklisted(obj, prop)) continue;
1144 var desc = Object.getOwnPropertyDescriptor(obj, prop);
1145 print("***", typeof obj, stringifyNoThrow(obj), prop);
1146 if (!desc || desc.configurable)
1147 TestObserveConfigurable(obj, prop);
1148 else if (desc.writable)
1149 TestObserveNonConfigurable(obj, prop, desc);
1150 }
1151
1152
1153 // Observing array length (including truncation)
1154 reset();
1155 var arr = ['a', 'b', 'c', 'd'];
1156 var arr2 = ['alpha', 'beta'];
1157 var arr3 = ['hello'];
1158 arr3[2] = 'goodbye';
1159 arr3.length = 6;
1160 Object.defineProperty(arr, '0', {configurable: false});
1161 Object.defineProperty(arr, '2', {get: function(){}});
1162 Object.defineProperty(arr2, '0', {get: function(){}, configurable: false});
1163 Object.observe(arr, observer.callback);
1164 Array.observe(arr, observer2.callback);
1165 Object.observe(arr2, observer.callback);
1166 Array.observe(arr2, observer2.callback);
1167 Object.observe(arr3, observer.callback);
1168 Array.observe(arr3, observer2.callback);
1169 arr.length = 2;
1170 arr.length = 0;
1171 arr.length = 10;
1172 Object.defineProperty(arr, 'length', {writable: false});
1173 arr2.length = 0;
1174 arr2.length = 1; // no change expected
1175 Object.defineProperty(arr2, 'length', {value: 1, writable: false});
1176 arr3.length = 0;
1177 ++arr3.length;
1178 arr3.length++;
1179 arr3.length /= 2;
1180 Object.defineProperty(arr3, 'length', {value: 5});
1181 arr3[4] = 5;
1182 Object.defineProperty(arr3, 'length', {value: 1, writable: false});
1183 Object.deliverChangeRecords(observer.callback);
1184 observer.assertCallbackRecords([
1185 { object: arr, name: '3', type: 'delete', oldValue: 'd' },
1186 { object: arr, name: '2', type: 'delete' },
1187 { object: arr, name: 'length', type: 'update', oldValue: 4 },
1188 { object: arr, name: '1', type: 'delete', oldValue: 'b' },
1189 { object: arr, name: 'length', type: 'update', oldValue: 2 },
1190 { object: arr, name: 'length', type: 'update', oldValue: 1 },
1191 { object: arr, name: 'length', type: 'reconfigure' },
1192 { object: arr2, name: '1', type: 'delete', oldValue: 'beta' },
1193 { object: arr2, name: 'length', type: 'update', oldValue: 2 },
1194 { object: arr2, name: 'length', type: 'reconfigure' },
1195 { object: arr3, name: '2', type: 'delete', oldValue: 'goodbye' },
1196 { object: arr3, name: '0', type: 'delete', oldValue: 'hello' },
1197 { object: arr3, name: 'length', type: 'update', oldValue: 6 },
1198 { object: arr3, name: 'length', type: 'update', oldValue: 0 },
1199 { object: arr3, name: 'length', type: 'update', oldValue: 1 },
1200 { object: arr3, name: 'length', type: 'update', oldValue: 2 },
1201 { object: arr3, name: 'length', type: 'update', oldValue: 1 },
1202 { object: arr3, name: '4', type: 'add' },
1203 { object: arr3, name: '4', type: 'delete', oldValue: 5 },
1204 // TODO(rafaelw): It breaks spec compliance to get two records here.
1205 // When the TODO in v8natives.js::DefineArrayProperty is addressed
1206 // which prevents DefineProperty from over-writing the magic length
1207 // property, these will collapse into a single record.
1208 { object: arr3, name: 'length', type: 'update', oldValue: 5 },
1209 { object: arr3, name: 'length', type: 'reconfigure' }
1210 ]);
1211 Object.deliverChangeRecords(observer2.callback);
1212 observer2.assertCallbackRecords([
1213 { object: arr, type: 'splice', index: 2, removed: [, 'd'], addedCount: 0 },
1214 { object: arr, type: 'splice', index: 1, removed: ['b'], addedCount: 0 },
1215 { object: arr, type: 'splice', index: 1, removed: [], addedCount: 9 },
1216 { object: arr2, type: 'splice', index: 1, removed: ['beta'], addedCount: 0 },
1217 { object: arr3, type: 'splice', index: 0, removed: ['hello',, 'goodbye',,,,], addedCount: 0 },
1218 { object: arr3, type: 'splice', index: 0, removed: [], addedCount: 1 },
1219 { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 1 },
1220 { object: arr3, type: 'splice', index: 1, removed: [,], addedCount: 0 },
1221 { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 4 },
1222 { object: arr3, name: '4', type: 'add' },
1223 { object: arr3, type: 'splice', index: 1, removed: [,,,5], addedCount: 0 }
1224 ]);
1225
1226
1227 // Updating length on large (slow) array
1228 reset();
1229 var slow_arr = new Array(1000000000);
1230 slow_arr[500000000] = 'hello';
1231 Object.observe(slow_arr, observer.callback);
1232 var spliceRecords;
1233 function slowSpliceCallback(records) {
1234 spliceRecords = records;
1235 }
1236 Array.observe(slow_arr, slowSpliceCallback);
1237 slow_arr.length = 100;
1238 Object.deliverChangeRecords(observer.callback);
1239 observer.assertCallbackRecords([
1240 { object: slow_arr, name: '500000000', type: 'delete', oldValue: 'hello' },
1241 { object: slow_arr, name: 'length', type: 'update', oldValue: 1000000000 },
1242 ]);
1243 Object.deliverChangeRecords(slowSpliceCallback);
1244 assertEquals(spliceRecords.length, 1);
1245 // Have to custom assert this splice record because the removed array is huge.
1246 var splice = spliceRecords[0];
1247 assertSame(splice.object, slow_arr);
1248 assertEquals(splice.type, 'splice');
1249 assertEquals(splice.index, 100);
1250 assertEquals(splice.addedCount, 0);
1251 var array_keys = %GetArrayKeys(splice.removed, splice.removed.length);
1252 assertEquals(array_keys.length, 1);
1253 assertEquals(array_keys[0], 499999900);
1254 assertEquals(splice.removed[499999900], 'hello');
1255 assertEquals(splice.removed.length, 999999900);
1256
1257
1258 // Assignments in loops (checking different IC states).
1259 reset();
1260 var obj = {};
1261 Object.observe(obj, observer.callback);
1262 for (var i = 0; i < 5; i++) {
1263 obj["a" + i] = i;
1264 }
1265 Object.deliverChangeRecords(observer.callback);
1266 observer.assertCallbackRecords([
1267 { object: obj, name: "a0", type: "add" },
1268 { object: obj, name: "a1", type: "add" },
1269 { object: obj, name: "a2", type: "add" },
1270 { object: obj, name: "a3", type: "add" },
1271 { object: obj, name: "a4", type: "add" },
1272 ]);
1273
1274 reset();
1275 var obj = {};
1276 Object.observe(obj, observer.callback);
1277 for (var i = 0; i < 5; i++) {
1278 obj[i] = i;
1279 }
1280 Object.deliverChangeRecords(observer.callback);
1281 observer.assertCallbackRecords([
1282 { object: obj, name: "0", type: "add" },
1283 { object: obj, name: "1", type: "add" },
1284 { object: obj, name: "2", type: "add" },
1285 { object: obj, name: "3", type: "add" },
1286 { object: obj, name: "4", type: "add" },
1287 ]);
1288
1289
1290 // Adding elements past the end of an array should notify on length for
1291 // Object.observe and emit "splices" for Array.observe.
1292 reset();
1293 var arr = [1, 2, 3];
1294 Object.observe(arr, observer.callback);
1295 Array.observe(arr, observer2.callback);
1296 arr[3] = 10;
1297 arr[100] = 20;
1298 Object.defineProperty(arr, '200', {value: 7});
1299 Object.defineProperty(arr, '400', {get: function(){}});
1300 arr[50] = 30; // no length change expected
1301 Object.deliverChangeRecords(observer.callback);
1302 observer.assertCallbackRecords([
1303 { object: arr, name: '3', type: 'add' },
1304 { object: arr, name: 'length', type: 'update', oldValue: 3 },
1305 { object: arr, name: '100', type: 'add' },
1306 { object: arr, name: 'length', type: 'update', oldValue: 4 },
1307 { object: arr, name: '200', type: 'add' },
1308 { object: arr, name: 'length', type: 'update', oldValue: 101 },
1309 { object: arr, name: '400', type: 'add' },
1310 { object: arr, name: 'length', type: 'update', oldValue: 201 },
1311 { object: arr, name: '50', type: 'add' },
1312 ]);
1313 Object.deliverChangeRecords(observer2.callback);
1314 observer2.assertCallbackRecords([
1315 { object: arr, type: 'splice', index: 3, removed: [], addedCount: 1 },
1316 { object: arr, type: 'splice', index: 4, removed: [], addedCount: 97 },
1317 { object: arr, type: 'splice', index: 101, removed: [], addedCount: 100 },
1318 { object: arr, type: 'splice', index: 201, removed: [], addedCount: 200 },
1319 { object: arr, type: 'add', name: '50' },
1320 ]);
1321
1322
1323 // Tests for array methods, first on arrays and then on plain objects
1324 //
1325 // === ARRAYS ===
1326 //
1327 // Push
1328 reset();
1329 var array = [1, 2];
1330 Object.observe(array, observer.callback);
1331 Array.observe(array, observer2.callback);
1332 array.push(3, 4);
1333 array.push(5);
1334 Object.deliverChangeRecords(observer.callback);
1335 observer.assertCallbackRecords([
1336 { object: array, name: '2', type: 'add' },
1337 { object: array, name: 'length', type: 'update', oldValue: 2 },
1338 { object: array, name: '3', type: 'add' },
1339 { object: array, name: 'length', type: 'update', oldValue: 3 },
1340 { object: array, name: '4', type: 'add' },
1341 { object: array, name: 'length', type: 'update', oldValue: 4 },
1342 ]);
1343 Object.deliverChangeRecords(observer2.callback);
1344 observer2.assertCallbackRecords([
1345 { object: array, type: 'splice', index: 2, removed: [], addedCount: 2 },
1346 { object: array, type: 'splice', index: 4, removed: [], addedCount: 1 }
1347 ]);
1348
1349 // Pop
1350 reset();
1351 var array = [1, 2];
1352 Object.observe(array, observer.callback);
1353 array.pop();
1354 array.pop();
1355 Object.deliverChangeRecords(observer.callback);
1356 observer.assertCallbackRecords([
1357 { object: array, name: '1', type: 'delete', oldValue: 2 },
1358 { object: array, name: 'length', type: 'update', oldValue: 2 },
1359 { object: array, name: '0', type: 'delete', oldValue: 1 },
1360 { object: array, name: 'length', type: 'update', oldValue: 1 },
1361 ]);
1362
1363 // Shift
1364 reset();
1365 var array = [1, 2];
1366 Object.observe(array, observer.callback);
1367 array.shift();
1368 array.shift();
1369 Object.deliverChangeRecords(observer.callback);
1370 observer.assertCallbackRecords([
1371 { object: array, name: '0', type: 'update', oldValue: 1 },
1372 { object: array, name: '1', type: 'delete', oldValue: 2 },
1373 { object: array, name: 'length', type: 'update', oldValue: 2 },
1374 { object: array, name: '0', type: 'delete', oldValue: 2 },
1375 { object: array, name: 'length', type: 'update', oldValue: 1 },
1376 ]);
1377
1378 // Unshift
1379 reset();
1380 var array = [1, 2];
1381 Object.observe(array, observer.callback);
1382 array.unshift(3, 4);
1383 Object.deliverChangeRecords(observer.callback);
1384 observer.assertCallbackRecords([
1385 { object: array, name: '3', type: 'add' },
1386 { object: array, name: 'length', type: 'update', oldValue: 2 },
1387 { object: array, name: '2', type: 'add' },
1388 { object: array, name: '0', type: 'update', oldValue: 1 },
1389 { object: array, name: '1', type: 'update', oldValue: 2 },
1390 ]);
1391
1392 // Splice
1393 reset();
1394 var array = [1, 2, 3];
1395 Object.observe(array, observer.callback);
1396 array.splice(1, 1, 4, 5);
1397 Object.deliverChangeRecords(observer.callback);
1398 observer.assertCallbackRecords([
1399 { object: array, name: '3', type: 'add' },
1400 { object: array, name: 'length', type: 'update', oldValue: 3 },
1401 { object: array, name: '1', type: 'update', oldValue: 2 },
1402 { object: array, name: '2', type: 'update', oldValue: 3 },
1403 ]);
1404
1405 // Sort
1406 reset();
1407 var array = [3, 2, 1];
1408 Object.observe(array, observer.callback);
1409 array.sort();
1410 assertEquals(1, array[0]);
1411 assertEquals(2, array[1]);
1412 assertEquals(3, array[2]);
1413 Object.deliverChangeRecords(observer.callback);
1414 observer.assertCallbackRecords([
1415 { object: array, name: '1', type: 'update', oldValue: 2 },
1416 { object: array, name: '0', type: 'update', oldValue: 3 },
1417 { object: array, name: '2', type: 'update', oldValue: 1 },
1418 { object: array, name: '1', type: 'update', oldValue: 3 },
1419 { object: array, name: '0', type: 'update', oldValue: 2 },
1420 ]);
1421
1422 // Splice emitted after Array mutation methods
1423 function MockArray(initial, observer) {
1424 for (var i = 0; i < initial.length; i++)
1425 this[i] = initial[i];
1426
1427 this.length_ = initial.length;
1428 this.observer = observer;
1429 }
1430 MockArray.prototype = {
1431 set length(length) {
1432 Object.getNotifier(this).notify({ type: 'lengthChange' });
1433 this.length_ = length;
1434 Object.observe(this, this.observer.callback, ['splice']);
1435 },
1436 get length() {
1437 return this.length_;
1438 }
1439 }
1440
1441 reset();
1442 var array = new MockArray([], observer);
1443 Object.observe(array, observer.callback, ['lengthChange']);
1444 Array.prototype.push.call(array, 1);
1445 Object.deliverChangeRecords(observer.callback);
1446 observer.assertCallbackRecords([
1447 { object: array, type: 'lengthChange' },
1448 { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 },
1449 ]);
1450
1451 reset();
1452 var array = new MockArray([1], observer);
1453 Object.observe(array, observer.callback, ['lengthChange']);
1454 Array.prototype.pop.call(array);
1455 Object.deliverChangeRecords(observer.callback);
1456 observer.assertCallbackRecords([
1457 { object: array, type: 'lengthChange' },
1458 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
1459 ]);
1460
1461 reset();
1462 var array = new MockArray([1], observer);
1463 Object.observe(array, observer.callback, ['lengthChange']);
1464 Array.prototype.shift.call(array);
1465 Object.deliverChangeRecords(observer.callback);
1466 observer.assertCallbackRecords([
1467 { object: array, type: 'lengthChange' },
1468 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
1469 ]);
1470
1471 reset();
1472 var array = new MockArray([], observer);
1473 Object.observe(array, observer.callback, ['lengthChange']);
1474 Array.prototype.unshift.call(array, 1);
1475 Object.deliverChangeRecords(observer.callback);
1476 observer.assertCallbackRecords([
1477 { object: array, type: 'lengthChange' },
1478 { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 },
1479 ]);
1480
1481 reset();
1482 var array = new MockArray([0, 1, 2], observer);
1483 Object.observe(array, observer.callback, ['lengthChange']);
1484 Array.prototype.splice.call(array, 1, 1);
1485 Object.deliverChangeRecords(observer.callback);
1486 observer.assertCallbackRecords([
1487 { object: array, type: 'lengthChange' },
1488 { object: array, type: 'splice', index: 1, removed: [1], addedCount: 0 },
1489 ]);
1490
1491 //
1492 // === PLAIN OBJECTS ===
1493 //
1494 // Push
1495 reset()
1496 var array = {0: 1, 1: 2, length: 2}
1497 Object.observe(array, observer.callback);
1498 Array.prototype.push.call(array, 3, 4);
1499 Object.deliverChangeRecords(observer.callback);
1500 observer.assertCallbackRecords([
1501 { object: array, name: '2', type: 'add' },
1502 { object: array, name: '3', type: 'add' },
1503 { object: array, name: 'length', type: 'update', oldValue: 2 },
1504 ]);
1505
1506 // Pop
1507 reset();
1508 var array = [1, 2];
1509 Object.observe(array, observer.callback);
1510 Array.observe(array, observer2.callback);
1511 array.pop();
1512 array.pop();
1513 array.pop();
1514 Object.deliverChangeRecords(observer.callback);
1515 observer.assertCallbackRecords([
1516 { object: array, name: '1', type: 'delete', oldValue: 2 },
1517 { object: array, name: 'length', type: 'update', oldValue: 2 },
1518 { object: array, name: '0', type: 'delete', oldValue: 1 },
1519 { object: array, name: 'length', type: 'update', oldValue: 1 },
1520 ]);
1521 Object.deliverChangeRecords(observer2.callback);
1522 observer2.assertCallbackRecords([
1523 { object: array, type: 'splice', index: 1, removed: [2], addedCount: 0 },
1524 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }
1525 ]);
1526
1527 // Shift
1528 reset();
1529 var array = [1, 2];
1530 Object.observe(array, observer.callback);
1531 Array.observe(array, observer2.callback);
1532 array.shift();
1533 array.shift();
1534 array.shift();
1535 Object.deliverChangeRecords(observer.callback);
1536 observer.assertCallbackRecords([
1537 { object: array, name: '0', type: 'update', oldValue: 1 },
1538 { object: array, name: '1', type: 'delete', oldValue: 2 },
1539 { object: array, name: 'length', type: 'update', oldValue: 2 },
1540 { object: array, name: '0', type: 'delete', oldValue: 2 },
1541 { object: array, name: 'length', type: 'update', oldValue: 1 },
1542 ]);
1543 Object.deliverChangeRecords(observer2.callback);
1544 observer2.assertCallbackRecords([
1545 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 },
1546 { object: array, type: 'splice', index: 0, removed: [2], addedCount: 0 }
1547 ]);
1548
1549 // Unshift
1550 reset();
1551 var array = [1, 2];
1552 Object.observe(array, observer.callback);
1553 Array.observe(array, observer2.callback);
1554 array.unshift(3, 4);
1555 array.unshift(5);
1556 Object.deliverChangeRecords(observer.callback);
1557 observer.assertCallbackRecords([
1558 { object: array, name: '3', type: 'add' },
1559 { object: array, name: 'length', type: 'update', oldValue: 2 },
1560 { object: array, name: '2', type: 'add' },
1561 { object: array, name: '0', type: 'update', oldValue: 1 },
1562 { object: array, name: '1', type: 'update', oldValue: 2 },
1563 { object: array, name: '4', type: 'add' },
1564 { object: array, name: 'length', type: 'update', oldValue: 4 },
1565 { object: array, name: '3', type: 'update', oldValue: 2 },
1566 { object: array, name: '2', type: 'update', oldValue: 1 },
1567 { object: array, name: '1', type: 'update', oldValue: 4 },
1568 { object: array, name: '0', type: 'update', oldValue: 3 },
1569 ]);
1570 Object.deliverChangeRecords(observer2.callback);
1571 observer2.assertCallbackRecords([
1572 { object: array, type: 'splice', index: 0, removed: [], addedCount: 2 },
1573 { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }
1574 ]);
1575
1576 // Splice
1577 reset();
1578 var array = [1, 2, 3];
1579 Object.observe(array, observer.callback);
1580 Array.observe(array, observer2.callback);
1581 array.splice(1, 0, 4, 5); // 1 4 5 2 3
1582 array.splice(0, 2); // 5 2 3
1583 array.splice(1, 2, 6, 7); // 5 6 7
1584 array.splice(2, 0);
1585 Object.deliverChangeRecords(observer.callback);
1586 observer.assertCallbackRecords([
1587 { object: array, name: '4', type: 'add' },
1588 { object: array, name: 'length', type: 'update', oldValue: 3 },
1589 { object: array, name: '3', type: 'add' },
1590 { object: array, name: '1', type: 'update', oldValue: 2 },
1591 { object: array, name: '2', type: 'update', oldValue: 3 },
1592
1593 { object: array, name: '0', type: 'update', oldValue: 1 },
1594 { object: array, name: '1', type: 'update', oldValue: 4 },
1595 { object: array, name: '2', type: 'update', oldValue: 5 },
1596 { object: array, name: '4', type: 'delete', oldValue: 3 },
1597 { object: array, name: '3', type: 'delete', oldValue: 2 },
1598 { object: array, name: 'length', type: 'update', oldValue: 5 },
1599
1600 { object: array, name: '1', type: 'update', oldValue: 2 },
1601 { object: array, name: '2', type: 'update', oldValue: 3 },
1602 ]);
1603 Object.deliverChangeRecords(observer2.callback);
1604 observer2.assertCallbackRecords([
1605 { object: array, type: 'splice', index: 1, removed: [], addedCount: 2 },
1606 { object: array, type: 'splice', index: 0, removed: [1, 4], addedCount: 0 },
1607 { object: array, type: 'splice', index: 1, removed: [2, 3], addedCount: 2 },
1608 ]);
1609
1610 // Exercise StoreIC_ArrayLength
1611 reset();
1612 var dummy = {};
1613 Object.observe(dummy, observer.callback);
1614 Object.unobserve(dummy, observer.callback);
1615 var array = [0];
1616 Object.observe(array, observer.callback);
1617 array.splice(0, 1);
1618 Object.deliverChangeRecords(observer.callback);
1619 observer.assertCallbackRecords([
1620 { object: array, name: '0', type: 'delete', oldValue: 0 },
1621 { object: array, name: 'length', type: 'update', oldValue: 1},
1622 ]);
1623
1624
1625 // __proto__
1626 reset();
1627 var obj = {};
1628 Object.observe(obj, observer.callback);
1629 var p = {foo: 'yes'};
1630 var q = {bar: 'no'};
1631 obj.__proto__ = p;
1632 obj.__proto__ = p; // ignored
1633 obj.__proto__ = null;
1634 obj.__proto__ = q; // the __proto__ accessor is gone
1635 // TODO(adamk): Add tests for objects with hidden prototypes
1636 // once we support observing the global object.
1637 Object.deliverChangeRecords(observer.callback);
1638 observer.assertCallbackRecords([
1639 { object: obj, name: '__proto__', type: 'setPrototype',
1640 oldValue: Object.prototype },
1641 { object: obj, name: '__proto__', type: 'setPrototype', oldValue: p },
1642 { object: obj, name: '__proto__', type: 'add' },
1643 ]);
1644
1645
1646 // Function.prototype
1647 reset();
1648 var fun = function(){};
1649 Object.observe(fun, observer.callback);
1650 var myproto = {foo: 'bar'};
1651 fun.prototype = myproto;
1652 fun.prototype = 7;
1653 fun.prototype = 7; // ignored
1654 Object.defineProperty(fun, 'prototype', {value: 8});
1655 Object.deliverChangeRecords(observer.callback);
1656 observer.assertRecordCount(3);
1657 // Manually examine the first record in order to test
1658 // lazy creation of oldValue
1659 assertSame(fun, observer.records[0].object);
1660 assertEquals('prototype', observer.records[0].name);
1661 assertEquals('update', observer.records[0].type);
1662 // The only existing reference to the oldValue object is in this
1663 // record, so to test that lazy creation happened correctly
1664 // we compare its constructor to our function (one of the invariants
1665 // ensured when creating an object via AllocateFunctionPrototype).
1666 assertSame(fun, observer.records[0].oldValue.constructor);
1667 observer.records.splice(0, 1);
1668 observer.assertCallbackRecords([
1669 { object: fun, name: 'prototype', type: 'update', oldValue: myproto },
1670 { object: fun, name: 'prototype', type: 'update', oldValue: 7 },
1671 ]);
1672
1673 // Function.prototype should not be observable except on the object itself
1674 reset();
1675 var fun = function(){};
1676 var obj = { __proto__: fun };
1677 Object.observe(obj, observer.callback);
1678 obj.prototype = 7;
1679 Object.deliverChangeRecords(observer.callback);
1680 observer.assertNotCalled();
1681
1682
1683 // Check that changes in observation status are detected in all IC states and
1684 // in optimized code, especially in cases usually using fast elements.
1685 var mutation = [
1686 "a[i] = v",
1687 "a[i] ? ++a[i] : a[i] = v",
1688 "a[i] ? a[i]++ : a[i] = v",
1689 "a[i] ? a[i] += 1 : a[i] = v",
1690 "a[i] ? a[i] -= -1 : a[i] = v",
1691 ];
1692
1693 var props = [1, "1", "a"];
1694
1695 function TestFastElements(prop, mutation, prepopulate, polymorphic, optimize) {
1696 var setElement = eval(
1697 "(function setElement(a, i, v) { " + mutation + "; " +
1698 "/* " + [].join.call(arguments, " ") + " */" +
1699 "})"
1700 );
1701 print("TestFastElements:", setElement);
1702
1703 var arr = prepopulate ? [1, 2, 3, 4, 5] : [0];
1704 if (prepopulate) arr[prop] = 2; // for non-element case
1705 setElement(arr, prop, 3);
1706 setElement(arr, prop, 4);
1707 if (polymorphic) setElement(["M", "i", "l", "n", "e", "r"], 0, "m");
1708 if (optimize) %OptimizeFunctionOnNextCall(setElement);
1709 setElement(arr, prop, 5);
1710
1711 reset();
1712 Object.observe(arr, observer.callback);
1713 setElement(arr, prop, 989898);
1714 Object.deliverChangeRecords(observer.callback);
1715 observer.assertCallbackRecords([
1716 { object: arr, name: "" + prop, type: 'update', oldValue: 5 }
1717 ]);
1718 }
1719
1720 for (var b1 = 0; b1 < 2; ++b1)
1721 for (var b2 = 0; b2 < 2; ++b2)
1722 for (var b3 = 0; b3 < 2; ++b3)
1723 for (var i in props)
1724 for (var j in mutation)
1725 TestFastElements(props[i], mutation[j], b1 != 0, b2 != 0, b3 != 0);
1726
1727
1728 var mutation = [
1729 "a.length = v",
1730 "a.length += newSize - oldSize",
1731 "a.length -= oldSize - newSize",
1732 ];
1733
1734 var mutationByIncr = [
1735 "++a.length",
1736 "a.length++",
1737 ];
1738
1739 function TestFastElementsLength(
1740 mutation, polymorphic, optimize, oldSize, newSize) {
1741 var setLength = eval(
1742 "(function setLength(a, v) { " + mutation + "; " +
1743 "/* " + [].join.call(arguments, " ") + " */"
1744 + "})"
1745 );
1746 print("TestFastElementsLength:", setLength);
1747
1748 function array(n) {
1749 var arr = new Array(n);
1750 for (var i = 0; i < n; ++i) arr[i] = i;
1751 return arr;
1752 }
1753
1754 setLength(array(oldSize), newSize);
1755 setLength(array(oldSize), newSize);
1756 if (polymorphic) setLength(array(oldSize).map(isNaN), newSize);
1757 if (optimize) %OptimizeFunctionOnNextCall(setLength);
1758 setLength(array(oldSize), newSize);
1759
1760 reset();
1761 var arr = array(oldSize);
1762 Object.observe(arr, observer.callback);
1763 setLength(arr, newSize);
1764 Object.deliverChangeRecords(observer.callback);
1765 if (oldSize === newSize) {
1766 observer.assertNotCalled();
1767 } else {
1768 var count = oldSize > newSize ? oldSize - newSize : 0;
1769 observer.assertRecordCount(count + 1);
1770 var lengthRecord = observer.records[count];
1771 assertSame(arr, lengthRecord.object);
1772 assertEquals('length', lengthRecord.name);
1773 assertEquals('update', lengthRecord.type);
1774 assertSame(oldSize, lengthRecord.oldValue);
1775 }
1776 }
1777
1778 for (var b1 = 0; b1 < 2; ++b1)
1779 for (var b2 = 0; b2 < 2; ++b2)
1780 for (var n1 = 0; n1 < 3; ++n1)
1781 for (var n2 = 0; n2 < 3; ++n2)
1782 for (var i in mutation)
1783 TestFastElementsLength(mutation[i], b1 != 0, b2 != 0, 20*n1, 20*n2);
1784
1785 for (var b1 = 0; b1 < 2; ++b1)
1786 for (var b2 = 0; b2 < 2; ++b2)
1787 for (var n = 0; n < 3; ++n)
1788 for (var i in mutationByIncr)
1789 TestFastElementsLength(mutationByIncr[i], b1 != 0, b2 != 0, 7*n, 7*n+1);
OLDNEW
« no previous file with comments | « test/mjsunit/debug-script.js ('k') | test/mjsunit/harmony/object-observe.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698