OLD | NEW |
| (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); | |
OLD | NEW |