The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.
Please select the desired protocol below to get the URL.
This URL has Read-Only access.
main_repo / deps / v8 / test / mjsunit / harmony / object-observe.js @ f230a1cf
History | View | Annotate | Download (53.7 KB)
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(){}); }, TypeError); |
148 |
assertThrows(function() { notifier.performChange('foo', undefined); }, TypeError); |
149 |
assertThrows(function() { notifier.performChange('foo', 'bar'); }, TypeError); |
150 |
notifier.performChange('foo', function() { |
151 |
assertEquals(undefined, this); |
152 |
}); |
153 |
|
154 |
var notify = notifier.notify;
|
155 |
assertThrows(function() { notify.call(undefined, { type: 'a' }); }, TypeError); |
156 |
assertThrows(function() { notify.call(null, { type: 'a' }); }, TypeError); |
157 |
assertThrows(function() { notify.call(5, { type: 'a' }); }, TypeError); |
158 |
assertThrows(function() { notify.call('hello', { type: 'a' }); }, TypeError); |
159 |
assertThrows(function() { notify.call(false, { type: 'a' }); }, TypeError); |
160 |
assertThrows(function() { notify.call({}, { type: 'a' }); }, TypeError); |
161 |
assertFalse(recordCreated); |
162 |
notifier.notify(changeRecordWithAccessor); |
163 |
assertFalse(recordCreated); // not observed yet
|
164 |
|
165 |
|
166 |
// Object.deliverChangeRecords
|
167 |
assertThrows(function() { Object.deliverChangeRecords(nonFunction); }, TypeError);
|
168 |
|
169 |
Object.observe(obj, observer.callback); |
170 |
|
171 |
|
172 |
// notify uses to [[CreateOwnProperty]] to create changeRecord;
|
173 |
reset(); |
174 |
var protoExpandoAccessed = false; |
175 |
Object.defineProperty(Object.prototype, 'protoExpando',
|
176 |
{ |
177 |
configurable: true, |
178 |
set: function() { protoExpandoAccessed = true; } |
179 |
} |
180 |
); |
181 |
notifier.notify({ type: 'foo', protoExpando: 'val'}); |
182 |
assertFalse(protoExpandoAccessed); |
183 |
delete Object.prototype.protoExpando;
|
184 |
Object.deliverChangeRecords(observer.callback); |
185 |
|
186 |
|
187 |
// Multiple records are delivered.
|
188 |
reset(); |
189 |
notifier.notify({ |
190 |
type: 'updated', |
191 |
name: 'foo', |
192 |
expando: 1 |
193 |
}); |
194 |
|
195 |
notifier.notify({ |
196 |
object: notifier, // object property is ignored |
197 |
type: 'deleted', |
198 |
name: 'bar', |
199 |
expando2: 'str' |
200 |
}); |
201 |
Object.deliverChangeRecords(observer.callback); |
202 |
observer.assertCallbackRecords([ |
203 |
{ object: obj, name: 'foo', type: 'updated', expando: 1 }, |
204 |
{ object: obj, name: 'bar', type: 'deleted', expando2: 'str' } |
205 |
]); |
206 |
|
207 |
// Non-string accept values are coerced to strings
|
208 |
reset(); |
209 |
Object.observe(obj, observer.callback, [true, 1, null, undefined]); |
210 |
notifier = Object.getNotifier(obj); |
211 |
notifier.notify({ type: 'true' }); |
212 |
notifier.notify({ type: 'false' }); |
213 |
notifier.notify({ type: '1' }); |
214 |
notifier.notify({ type: '-1' }); |
215 |
notifier.notify({ type: 'null' }); |
216 |
notifier.notify({ type: 'nill' }); |
217 |
notifier.notify({ type: 'undefined' }); |
218 |
notifier.notify({ type: 'defined' }); |
219 |
Object.deliverChangeRecords(observer.callback); |
220 |
observer.assertCallbackRecords([ |
221 |
{ object: obj, type: 'true' }, |
222 |
{ object: obj, type: '1' }, |
223 |
{ object: obj, type: 'null' }, |
224 |
{ object: obj, type: 'undefined' } |
225 |
]); |
226 |
|
227 |
// No delivery takes place if no records are pending
|
228 |
reset(); |
229 |
Object.deliverChangeRecords(observer.callback); |
230 |
observer.assertNotCalled(); |
231 |
|
232 |
|
233 |
// Multiple observation has no effect.
|
234 |
reset(); |
235 |
Object.observe(obj, observer.callback); |
236 |
Object.observe(obj, observer.callback); |
237 |
Object.getNotifier(obj).notify({ |
238 |
type: 'updated', |
239 |
}); |
240 |
Object.deliverChangeRecords(observer.callback); |
241 |
observer.assertCalled(); |
242 |
|
243 |
|
244 |
// Observation can be stopped.
|
245 |
reset(); |
246 |
Object.unobserve(obj, observer.callback); |
247 |
Object.getNotifier(obj).notify({ |
248 |
type: 'updated', |
249 |
}); |
250 |
Object.deliverChangeRecords(observer.callback); |
251 |
observer.assertNotCalled(); |
252 |
|
253 |
|
254 |
// Multiple unobservation has no effect
|
255 |
reset(); |
256 |
Object.unobserve(obj, observer.callback); |
257 |
Object.unobserve(obj, observer.callback); |
258 |
Object.getNotifier(obj).notify({ |
259 |
type: 'updated', |
260 |
}); |
261 |
Object.deliverChangeRecords(observer.callback); |
262 |
observer.assertNotCalled(); |
263 |
|
264 |
|
265 |
// Re-observation works and only includes changeRecords after of call.
|
266 |
reset(); |
267 |
Object.getNotifier(obj).notify({ |
268 |
type: 'updated', |
269 |
}); |
270 |
Object.observe(obj, observer.callback); |
271 |
Object.getNotifier(obj).notify({ |
272 |
type: 'updated', |
273 |
}); |
274 |
records = undefined;
|
275 |
Object.deliverChangeRecords(observer.callback); |
276 |
observer.assertRecordCount(1);
|
277 |
|
278 |
// Get notifier prior to observing
|
279 |
reset(); |
280 |
var obj = {};
|
281 |
Object.getNotifier(obj); |
282 |
Object.observe(obj, observer.callback); |
283 |
obj.id = 1;
|
284 |
Object.deliverChangeRecords(observer.callback); |
285 |
observer.assertCallbackRecords([ |
286 |
{ object: obj, type: 'new', name: 'id' }, |
287 |
]); |
288 |
|
289 |
// The empty-string property is observable
|
290 |
reset(); |
291 |
var obj = {};
|
292 |
Object.observe(obj, observer.callback); |
293 |
obj[''] = ''; |
294 |
obj[''] = ' '; |
295 |
delete obj['']; |
296 |
Object.deliverChangeRecords(observer.callback); |
297 |
observer.assertCallbackRecords([ |
298 |
{ object: obj, type: 'new', name: '' }, |
299 |
{ object: obj, type: 'updated', name: '', oldValue: '' }, |
300 |
{ object: obj, type: 'deleted', name: '', oldValue: ' ' }, |
301 |
]); |
302 |
|
303 |
// Observing a continuous stream of changes, while itermittantly unobserving.
|
304 |
reset(); |
305 |
Object.observe(obj, observer.callback); |
306 |
Object.getNotifier(obj).notify({ |
307 |
type: 'updated', |
308 |
val: 1 |
309 |
}); |
310 |
|
311 |
Object.unobserve(obj, observer.callback); |
312 |
Object.getNotifier(obj).notify({ |
313 |
type: 'updated', |
314 |
val: 2 |
315 |
}); |
316 |
|
317 |
Object.observe(obj, observer.callback); |
318 |
Object.getNotifier(obj).notify({ |
319 |
type: 'updated', |
320 |
val: 3 |
321 |
}); |
322 |
|
323 |
Object.unobserve(obj, observer.callback); |
324 |
Object.getNotifier(obj).notify({ |
325 |
type: 'updated', |
326 |
val: 4 |
327 |
}); |
328 |
|
329 |
Object.observe(obj, observer.callback); |
330 |
Object.getNotifier(obj).notify({ |
331 |
type: 'updated', |
332 |
val: 5 |
333 |
}); |
334 |
|
335 |
Object.unobserve(obj, observer.callback); |
336 |
Object.deliverChangeRecords(observer.callback); |
337 |
observer.assertCallbackRecords([ |
338 |
{ object: obj, type: 'updated', val: 1 }, |
339 |
{ object: obj, type: 'updated', val: 3 }, |
340 |
{ object: obj, type: 'updated', val: 5 } |
341 |
]); |
342 |
|
343 |
// Accept
|
344 |
reset(); |
345 |
Object.observe(obj, observer.callback, ['somethingElse']);
|
346 |
Object.getNotifier(obj).notify({ |
347 |
type: 'new' |
348 |
}); |
349 |
Object.getNotifier(obj).notify({ |
350 |
type: 'updated' |
351 |
}); |
352 |
Object.getNotifier(obj).notify({ |
353 |
type: 'deleted' |
354 |
}); |
355 |
Object.getNotifier(obj).notify({ |
356 |
type: 'reconfigured' |
357 |
}); |
358 |
Object.getNotifier(obj).notify({ |
359 |
type: 'prototype' |
360 |
}); |
361 |
Object.deliverChangeRecords(observer.callback); |
362 |
observer.assertNotCalled(); |
363 |
|
364 |
reset(); |
365 |
Object.observe(obj, observer.callback, ['new', 'deleted', 'prototype']); |
366 |
Object.getNotifier(obj).notify({ |
367 |
type: 'new' |
368 |
}); |
369 |
Object.getNotifier(obj).notify({ |
370 |
type: 'updated' |
371 |
}); |
372 |
Object.getNotifier(obj).notify({ |
373 |
type: 'deleted' |
374 |
}); |
375 |
Object.getNotifier(obj).notify({ |
376 |
type: 'deleted' |
377 |
}); |
378 |
Object.getNotifier(obj).notify({ |
379 |
type: 'reconfigured' |
380 |
}); |
381 |
Object.getNotifier(obj).notify({ |
382 |
type: 'prototype' |
383 |
}); |
384 |
Object.deliverChangeRecords(observer.callback); |
385 |
observer.assertCallbackRecords([ |
386 |
{ object: obj, type: 'new' }, |
387 |
{ object: obj, type: 'deleted' }, |
388 |
{ object: obj, type: 'deleted' }, |
389 |
{ object: obj, type: 'prototype' } |
390 |
]); |
391 |
|
392 |
reset(); |
393 |
Object.observe(obj, observer.callback, ['updated', 'foo']); |
394 |
Object.getNotifier(obj).notify({ |
395 |
type: 'new' |
396 |
}); |
397 |
Object.getNotifier(obj).notify({ |
398 |
type: 'updated' |
399 |
}); |
400 |
Object.getNotifier(obj).notify({ |
401 |
type: 'deleted' |
402 |
}); |
403 |
Object.getNotifier(obj).notify({ |
404 |
type: 'foo' |
405 |
}); |
406 |
Object.getNotifier(obj).notify({ |
407 |
type: 'bar' |
408 |
}); |
409 |
Object.getNotifier(obj).notify({ |
410 |
type: 'foo' |
411 |
}); |
412 |
Object.deliverChangeRecords(observer.callback); |
413 |
observer.assertCallbackRecords([ |
414 |
{ object: obj, type: 'updated' }, |
415 |
{ object: obj, type: 'foo' }, |
416 |
{ object: obj, type: 'foo' } |
417 |
]); |
418 |
|
419 |
reset(); |
420 |
function Thingy(a, b, c) { |
421 |
this.a = a;
|
422 |
this.b = b;
|
423 |
} |
424 |
|
425 |
Thingy.MULTIPLY = 'multiply';
|
426 |
Thingy.INCREMENT = 'increment';
|
427 |
Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply';
|
428 |
|
429 |
Thingy.prototype = { |
430 |
increment: function(amount) { |
431 |
var notifier = Object.getNotifier(this); |
432 |
|
433 |
var self = this; |
434 |
notifier.performChange(Thingy.INCREMENT, function() {
|
435 |
self.a += amount; |
436 |
self.b += amount; |
437 |
}); |
438 |
|
439 |
notifier.notify({ |
440 |
object: this, |
441 |
type: Thingy.INCREMENT,
|
442 |
incremented: amount
|
443 |
}); |
444 |
}, |
445 |
|
446 |
multiply: function(amount) { |
447 |
var notifier = Object.getNotifier(this); |
448 |
|
449 |
var self = this; |
450 |
notifier.performChange(Thingy.MULTIPLY, function() {
|
451 |
self.a *= amount; |
452 |
self.b *= amount; |
453 |
}); |
454 |
|
455 |
notifier.notify({ |
456 |
object: this, |
457 |
type: Thingy.MULTIPLY,
|
458 |
multiplied: amount
|
459 |
}); |
460 |
}, |
461 |
|
462 |
incrementAndMultiply: function(incAmount, multAmount) { |
463 |
var notifier = Object.getNotifier(this); |
464 |
|
465 |
var self = this; |
466 |
notifier.performChange(Thingy.INCREMENT_AND_MULTIPLY, function() {
|
467 |
self.increment(incAmount); |
468 |
self.multiply(multAmount); |
469 |
}); |
470 |
|
471 |
notifier.notify({ |
472 |
object: this, |
473 |
type: Thingy.INCREMENT_AND_MULTIPLY,
|
474 |
incremented: incAmount,
|
475 |
multiplied: multAmount
|
476 |
}); |
477 |
} |
478 |
} |
479 |
|
480 |
Thingy.observe = function(thingy, callback) { |
481 |
Object.observe(thingy, callback, [Thingy.INCREMENT, |
482 |
Thingy.MULTIPLY, |
483 |
Thingy.INCREMENT_AND_MULTIPLY, |
484 |
'updated']);
|
485 |
} |
486 |
|
487 |
Thingy.unobserve = function(thingy, callback) { |
488 |
Object.unobserve(thingy); |
489 |
} |
490 |
|
491 |
var thingy = new Thingy(2, 4); |
492 |
|
493 |
Object.observe(thingy, observer.callback); |
494 |
Thingy.observe(thingy, observer2.callback); |
495 |
thingy.increment(3); // { a: 5, b: 7 } |
496 |
thingy.b++; // { a: 5, b: 8 }
|
497 |
thingy.multiply(2); // { a: 10, b: 16 } |
498 |
thingy.a++; // { a: 11, b: 16 }
|
499 |
thingy.incrementAndMultiply(2, 2); // { a: 26, b: 36 } |
500 |
|
501 |
Object.deliverChangeRecords(observer.callback); |
502 |
Object.deliverChangeRecords(observer2.callback); |
503 |
observer.assertCallbackRecords([ |
504 |
{ object: thingy, type: 'updated', name: 'a', oldValue: 2 }, |
505 |
{ object: thingy, type: 'updated', name: 'b', oldValue: 4 }, |
506 |
{ object: thingy, type: 'updated', name: 'b', oldValue: 7 }, |
507 |
{ object: thingy, type: 'updated', name: 'a', oldValue: 5 }, |
508 |
{ object: thingy, type: 'updated', name: 'b', oldValue: 8 }, |
509 |
{ object: thingy, type: 'updated', name: 'a', oldValue: 10 }, |
510 |
{ object: thingy, type: 'updated', name: 'a', oldValue: 11 }, |
511 |
{ object: thingy, type: 'updated', name: 'b', oldValue: 16 }, |
512 |
{ object: thingy, type: 'updated', name: 'a', oldValue: 13 }, |
513 |
{ object: thingy, type: 'updated', name: 'b', oldValue: 18 }, |
514 |
]); |
515 |
observer2.assertCallbackRecords([ |
516 |
{ object: thingy, type: Thingy.INCREMENT, incremented: 3 }, |
517 |
{ object: thingy, type: 'updated', name: 'b', oldValue: 7 }, |
518 |
{ object: thingy, type: Thingy.MULTIPLY, multiplied: 2 }, |
519 |
{ object: thingy, type: 'updated', name: 'a', oldValue: 10 }, |
520 |
{ |
521 |
object: thingy,
|
522 |
type: Thingy.INCREMENT_AND_MULTIPLY,
|
523 |
incremented: 2, |
524 |
multiplied: 2 |
525 |
} |
526 |
]); |
527 |
|
528 |
|
529 |
reset(); |
530 |
function RecursiveThingy() {} |
531 |
|
532 |
RecursiveThingy.MULTIPLY_FIRST_N = 'multiplyFirstN';
|
533 |
|
534 |
RecursiveThingy.prototype = { |
535 |
__proto__: Array.prototype,
|
536 |
|
537 |
multiplyFirstN: function(amount, n) { |
538 |
if (!n)
|
539 |
return;
|
540 |
var notifier = Object.getNotifier(this); |
541 |
var self = this; |
542 |
notifier.performChange(RecursiveThingy.MULTIPLY_FIRST_N, function() {
|
543 |
self[n-1] = self[n-1]*amount; |
544 |
self.multiplyFirstN(amount, n-1);
|
545 |
}); |
546 |
|
547 |
notifier.notify({ |
548 |
object: this, |
549 |
type: RecursiveThingy.MULTIPLY_FIRST_N,
|
550 |
multiplied: amount,
|
551 |
n: n
|
552 |
}); |
553 |
}, |
554 |
} |
555 |
|
556 |
RecursiveThingy.observe = function(thingy, callback) { |
557 |
Object.observe(thingy, callback, [RecursiveThingy.MULTIPLY_FIRST_N]); |
558 |
} |
559 |
|
560 |
RecursiveThingy.unobserve = function(thingy, callback) { |
561 |
Object.unobserve(thingy); |
562 |
} |
563 |
|
564 |
var thingy = new RecursiveThingy; |
565 |
thingy.push(1, 2, 3, 4); |
566 |
|
567 |
Object.observe(thingy, observer.callback); |
568 |
RecursiveThingy.observe(thingy, observer2.callback); |
569 |
thingy.multiplyFirstN(2, 3); // [2, 4, 6, 4] |
570 |
|
571 |
Object.deliverChangeRecords(observer.callback); |
572 |
Object.deliverChangeRecords(observer2.callback); |
573 |
observer.assertCallbackRecords([ |
574 |
{ object: thingy, type: 'updated', name: '2', oldValue: 3 }, |
575 |
{ object: thingy, type: 'updated', name: '1', oldValue: 2 }, |
576 |
{ object: thingy, type: 'updated', name: '0', oldValue: 1 } |
577 |
]); |
578 |
observer2.assertCallbackRecords([ |
579 |
{ object: thingy, type: RecursiveThingy.MULTIPLY_FIRST_N, multiplied: 2, n: 3 } |
580 |
]); |
581 |
|
582 |
reset(); |
583 |
function DeckSuit() { |
584 |
this.push('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'A', 'Q', 'K'); |
585 |
} |
586 |
|
587 |
DeckSuit.SHUFFLE = 'shuffle';
|
588 |
|
589 |
DeckSuit.prototype = { |
590 |
__proto__: Array.prototype,
|
591 |
|
592 |
shuffle: function() { |
593 |
var notifier = Object.getNotifier(this); |
594 |
var self = this; |
595 |
notifier.performChange(DeckSuit.SHUFFLE, function() {
|
596 |
self.reverse(); |
597 |
self.sort(function() { return Math.random()* 2 - 1; }); |
598 |
var cut = self.splice(0, 6); |
599 |
Array.prototype.push.apply(self, cut); |
600 |
self.reverse(); |
601 |
self.sort(function() { return Math.random()* 2 - 1; }); |
602 |
var cut = self.splice(0, 6); |
603 |
Array.prototype.push.apply(self, cut); |
604 |
self.reverse(); |
605 |
self.sort(function() { return Math.random()* 2 - 1; }); |
606 |
}); |
607 |
|
608 |
notifier.notify({ |
609 |
object: this, |
610 |
type: DeckSuit.SHUFFLE
|
611 |
}); |
612 |
}, |
613 |
} |
614 |
|
615 |
DeckSuit.observe = function(thingy, callback) { |
616 |
Object.observe(thingy, callback, [DeckSuit.SHUFFLE]); |
617 |
} |
618 |
|
619 |
DeckSuit.unobserve = function(thingy, callback) { |
620 |
Object.unobserve(thingy); |
621 |
} |
622 |
|
623 |
var deck = new DeckSuit; |
624 |
|
625 |
DeckSuit.observe(deck, observer2.callback); |
626 |
deck.shuffle(); |
627 |
|
628 |
Object.deliverChangeRecords(observer2.callback); |
629 |
observer2.assertCallbackRecords([ |
630 |
{ object: deck, type: DeckSuit.SHUFFLE } |
631 |
]); |
632 |
|
633 |
// Observing multiple objects; records appear in order.
|
634 |
reset(); |
635 |
var obj2 = {};
|
636 |
var obj3 = {}
|
637 |
Object.observe(obj, observer.callback); |
638 |
Object.observe(obj3, observer.callback); |
639 |
Object.observe(obj2, observer.callback); |
640 |
Object.getNotifier(obj).notify({ |
641 |
type: 'new', |
642 |
}); |
643 |
Object.getNotifier(obj2).notify({ |
644 |
type: 'updated', |
645 |
}); |
646 |
Object.getNotifier(obj3).notify({ |
647 |
type: 'deleted', |
648 |
}); |
649 |
Object.observe(obj3, observer.callback); |
650 |
Object.deliverChangeRecords(observer.callback); |
651 |
observer.assertCallbackRecords([ |
652 |
{ object: obj, type: 'new' }, |
653 |
{ object: obj2, type: 'updated' }, |
654 |
{ object: obj3, type: 'deleted' } |
655 |
]); |
656 |
|
657 |
|
658 |
// Recursive observation.
|
659 |
var obj = {a: 1}; |
660 |
var callbackCount = 0; |
661 |
function recursiveObserver(r) { |
662 |
assertEquals(1, r.length);
|
663 |
++callbackCount; |
664 |
if (r[0].oldValue < 100) ++obj[r[0].name]; |
665 |
} |
666 |
Object.observe(obj, recursiveObserver); |
667 |
++obj.a; |
668 |
Object.deliverChangeRecords(recursiveObserver); |
669 |
assertEquals(100, callbackCount);
|
670 |
|
671 |
var obj1 = {a: 1}; |
672 |
var obj2 = {a: 1}; |
673 |
var recordCount = 0; |
674 |
function recursiveObserver2(r) { |
675 |
recordCount += r.length; |
676 |
if (r[0].oldValue < 100) { |
677 |
++obj1.a; |
678 |
++obj2.a; |
679 |
} |
680 |
} |
681 |
Object.observe(obj1, recursiveObserver2); |
682 |
Object.observe(obj2, recursiveObserver2); |
683 |
++obj1.a; |
684 |
Object.deliverChangeRecords(recursiveObserver2); |
685 |
assertEquals(199, recordCount);
|
686 |
|
687 |
|
688 |
// Observing named properties.
|
689 |
reset(); |
690 |
var obj = {a: 1} |
691 |
Object.observe(obj, observer.callback); |
692 |
obj.a = 2;
|
693 |
obj["a"] = 3; |
694 |
delete obj.a;
|
695 |
obj.a = 4;
|
696 |
obj.a = 4; // ignored |
697 |
obj.a = 5;
|
698 |
Object.defineProperty(obj, "a", {value: 6}); |
699 |
Object.defineProperty(obj, "a", {writable: false}); |
700 |
obj.a = 7; // ignored |
701 |
Object.defineProperty(obj, "a", {value: 8}); |
702 |
Object.defineProperty(obj, "a", {value: 7, writable: true}); |
703 |
Object.defineProperty(obj, "a", {get: function() {}}); |
704 |
Object.defineProperty(obj, "a", {get: frozenFunction}); |
705 |
Object.defineProperty(obj, "a", {get: frozenFunction}); // ignored |
706 |
Object.defineProperty(obj, "a", {get: frozenFunction, set: frozenFunction}); |
707 |
Object.defineProperty(obj, "a", {set: frozenFunction}); // ignored |
708 |
Object.defineProperty(obj, "a", {get: undefined, set: frozenFunction}); |
709 |
delete obj.a;
|
710 |
delete obj.a;
|
711 |
Object.defineProperty(obj, "a", {get: function() {}, configurable: true}); |
712 |
Object.defineProperty(obj, "a", {value: 9, writable: true}); |
713 |
obj.a = 10;
|
714 |
++obj.a; |
715 |
obj.a++; |
716 |
obj.a *= 3;
|
717 |
delete obj.a;
|
718 |
Object.defineProperty(obj, "a", {value: 11, configurable: true}); |
719 |
Object.deliverChangeRecords(observer.callback); |
720 |
observer.assertCallbackRecords([ |
721 |
{ object: obj, name: "a", type: "updated", oldValue: 1 }, |
722 |
{ object: obj, name: "a", type: "updated", oldValue: 2 }, |
723 |
{ object: obj, name: "a", type: "deleted", oldValue: 3 }, |
724 |
{ object: obj, name: "a", type: "new" }, |
725 |
{ object: obj, name: "a", type: "updated", oldValue: 4 }, |
726 |
{ object: obj, name: "a", type: "updated", oldValue: 5 }, |
727 |
{ object: obj, name: "a", type: "reconfigured" }, |
728 |
{ object: obj, name: "a", type: "updated", oldValue: 6 }, |
729 |
{ object: obj, name: "a", type: "reconfigured", oldValue: 8 }, |
730 |
{ object: obj, name: "a", type: "reconfigured", oldValue: 7 }, |
731 |
{ object: obj, name: "a", type: "reconfigured" }, |
732 |
{ object: obj, name: "a", type: "reconfigured" }, |
733 |
{ object: obj, name: "a", type: "reconfigured" }, |
734 |
{ object: obj, name: "a", type: "deleted" }, |
735 |
{ object: obj, name: "a", type: "new" }, |
736 |
{ object: obj, name: "a", type: "reconfigured" }, |
737 |
{ object: obj, name: "a", type: "updated", oldValue: 9 }, |
738 |
{ object: obj, name: "a", type: "updated", oldValue: 10 }, |
739 |
{ object: obj, name: "a", type: "updated", oldValue: 11 }, |
740 |
{ object: obj, name: "a", type: "updated", oldValue: 12 }, |
741 |
{ object: obj, name: "a", type: "deleted", oldValue: 36 }, |
742 |
{ object: obj, name: "a", type: "new" }, |
743 |
]); |
744 |
|
745 |
|
746 |
// Observing indexed properties.
|
747 |
reset(); |
748 |
var obj = {'1': 1} |
749 |
Object.observe(obj, observer.callback); |
750 |
obj[1] = 2; |
751 |
obj[1] = 3; |
752 |
delete obj[1]; |
753 |
obj[1] = 4; |
754 |
obj[1] = 4; // ignored |
755 |
obj[1] = 5; |
756 |
Object.defineProperty(obj, "1", {value: 6}); |
757 |
Object.defineProperty(obj, "1", {writable: false}); |
758 |
obj[1] = 7; // ignored |
759 |
Object.defineProperty(obj, "1", {value: 8}); |
760 |
Object.defineProperty(obj, "1", {value: 7, writable: true}); |
761 |
Object.defineProperty(obj, "1", {get: function() {}}); |
762 |
Object.defineProperty(obj, "1", {get: frozenFunction}); |
763 |
Object.defineProperty(obj, "1", {get: frozenFunction}); // ignored |
764 |
Object.defineProperty(obj, "1", {get: frozenFunction, set: frozenFunction}); |
765 |
Object.defineProperty(obj, "1", {set: frozenFunction}); // ignored |
766 |
Object.defineProperty(obj, "1", {get: undefined, set: frozenFunction}); |
767 |
delete obj[1]; |
768 |
delete obj[1]; |
769 |
Object.defineProperty(obj, "1", {get: function() {}, configurable: true}); |
770 |
Object.defineProperty(obj, "1", {value: 9, writable: true}); |
771 |
obj[1] = 10; |
772 |
++obj[1];
|
773 |
obj[1]++;
|
774 |
obj[1] *= 3; |
775 |
delete obj[1]; |
776 |
Object.defineProperty(obj, "1", {value: 11, configurable: true}); |
777 |
Object.deliverChangeRecords(observer.callback); |
778 |
observer.assertCallbackRecords([ |
779 |
{ object: obj, name: "1", type: "updated", oldValue: 1 }, |
780 |
{ object: obj, name: "1", type: "updated", oldValue: 2 }, |
781 |
{ object: obj, name: "1", type: "deleted", oldValue: 3 }, |
782 |
{ object: obj, name: "1", type: "new" }, |
783 |
{ object: obj, name: "1", type: "updated", oldValue: 4 }, |
784 |
{ object: obj, name: "1", type: "updated", oldValue: 5 }, |
785 |
{ object: obj, name: "1", type: "reconfigured" }, |
786 |
{ object: obj, name: "1", type: "updated", oldValue: 6 }, |
787 |
{ object: obj, name: "1", type: "reconfigured", oldValue: 8 }, |
788 |
{ object: obj, name: "1", type: "reconfigured", oldValue: 7 }, |
789 |
{ object: obj, name: "1", type: "reconfigured" }, |
790 |
{ object: obj, name: "1", type: "reconfigured" }, |
791 |
{ object: obj, name: "1", type: "reconfigured" }, |
792 |
{ object: obj, name: "1", type: "deleted" }, |
793 |
{ object: obj, name: "1", type: "new" }, |
794 |
{ object: obj, name: "1", type: "reconfigured" }, |
795 |
{ object: obj, name: "1", type: "updated", oldValue: 9 }, |
796 |
{ object: obj, name: "1", type: "updated", oldValue: 10 }, |
797 |
{ object: obj, name: "1", type: "updated", oldValue: 11 }, |
798 |
{ object: obj, name: "1", type: "updated", oldValue: 12 }, |
799 |
{ object: obj, name: "1", type: "deleted", oldValue: 36 }, |
800 |
{ object: obj, name: "1", type: "new" }, |
801 |
]); |
802 |
|
803 |
|
804 |
// Observing symbol properties (not).
|
805 |
print("*****")
|
806 |
reset(); |
807 |
var obj = {}
|
808 |
var symbol = Symbol("secret"); |
809 |
Object.observe(obj, observer.callback); |
810 |
obj[symbol] = 3;
|
811 |
delete obj[symbol];
|
812 |
Object.defineProperty(obj, symbol, {get: function() {}, configurable: true}); |
813 |
Object.defineProperty(obj, symbol, {value: 6}); |
814 |
Object.defineProperty(obj, symbol, {writable: false}); |
815 |
delete obj[symbol];
|
816 |
Object.defineProperty(obj, symbol, {value: 7}); |
817 |
++obj[symbol]; |
818 |
obj[symbol]++; |
819 |
obj[symbol] *= 3;
|
820 |
delete obj[symbol];
|
821 |
obj.__defineSetter__(symbol, function() {});
|
822 |
obj.__defineGetter__(symbol, function() {});
|
823 |
Object.deliverChangeRecords(observer.callback); |
824 |
observer.assertNotCalled(); |
825 |
|
826 |
|
827 |
// Test all kinds of objects generically.
|
828 |
function TestObserveConfigurable(obj, prop) { |
829 |
reset(); |
830 |
Object.observe(obj, observer.callback); |
831 |
Object.unobserve(obj, observer.callback); |
832 |
obj[prop] = 1;
|
833 |
Object.observe(obj, observer.callback); |
834 |
obj[prop] = 2;
|
835 |
obj[prop] = 3;
|
836 |
delete obj[prop];
|
837 |
obj[prop] = 4;
|
838 |
obj[prop] = 4; // ignored |
839 |
obj[prop] = 5;
|
840 |
Object.defineProperty(obj, prop, {value: 6}); |
841 |
Object.defineProperty(obj, prop, {writable: false}); |
842 |
obj[prop] = 7; // ignored |
843 |
Object.defineProperty(obj, prop, {value: 8}); |
844 |
Object.defineProperty(obj, prop, {value: 7, writable: true}); |
845 |
Object.defineProperty(obj, prop, {get: function() {}}); |
846 |
Object.defineProperty(obj, prop, {get: frozenFunction});
|
847 |
Object.defineProperty(obj, prop, {get: frozenFunction}); // ignored |
848 |
Object.defineProperty(obj, prop, {get: frozenFunction, set: frozenFunction}); |
849 |
Object.defineProperty(obj, prop, {set: frozenFunction}); // ignored |
850 |
Object.defineProperty(obj, prop, {get: undefined, set: frozenFunction}); |
851 |
obj.__defineSetter__(prop, frozenFunction); // ignored
|
852 |
obj.__defineSetter__(prop, function() {});
|
853 |
obj.__defineGetter__(prop, function() {});
|
854 |
delete obj[prop];
|
855 |
delete obj[prop]; // ignored |
856 |
obj.__defineGetter__(prop, function() {});
|
857 |
delete obj[prop];
|
858 |
Object.defineProperty(obj, prop, {get: function() {}, configurable: true}); |
859 |
Object.defineProperty(obj, prop, {value: 9, writable: true}); |
860 |
obj[prop] = 10;
|
861 |
++obj[prop]; |
862 |
obj[prop]++; |
863 |
obj[prop] *= 3;
|
864 |
delete obj[prop];
|
865 |
Object.defineProperty(obj, prop, {value: 11, configurable: true}); |
866 |
Object.deliverChangeRecords(observer.callback); |
867 |
observer.assertCallbackRecords([ |
868 |
{ object: obj, name: prop, type: "updated", oldValue: 1 }, |
869 |
{ object: obj, name: prop, type: "updated", oldValue: 2 }, |
870 |
{ object: obj, name: prop, type: "deleted", oldValue: 3 }, |
871 |
{ object: obj, name: prop, type: "new" }, |
872 |
{ object: obj, name: prop, type: "updated", oldValue: 4 }, |
873 |
{ object: obj, name: prop, type: "updated", oldValue: 5 }, |
874 |
{ object: obj, name: prop, type: "reconfigured" }, |
875 |
{ object: obj, name: prop, type: "updated", oldValue: 6 }, |
876 |
{ object: obj, name: prop, type: "reconfigured", oldValue: 8 }, |
877 |
{ object: obj, name: prop, type: "reconfigured", oldValue: 7 }, |
878 |
{ object: obj, name: prop, type: "reconfigured" }, |
879 |
{ object: obj, name: prop, type: "reconfigured" }, |
880 |
{ object: obj, name: prop, type: "reconfigured" }, |
881 |
{ object: obj, name: prop, type: "reconfigured" }, |
882 |
{ object: obj, name: prop, type: "reconfigured" }, |
883 |
{ object: obj, name: prop, type: "deleted" }, |
884 |
{ object: obj, name: prop, type: "new" }, |
885 |
{ object: obj, name: prop, type: "deleted" }, |
886 |
{ object: obj, name: prop, type: "new" }, |
887 |
{ object: obj, name: prop, type: "reconfigured" }, |
888 |
{ object: obj, name: prop, type: "updated", oldValue: 9 }, |
889 |
{ object: obj, name: prop, type: "updated", oldValue: 10 }, |
890 |
{ object: obj, name: prop, type: "updated", oldValue: 11 }, |
891 |
{ object: obj, name: prop, type: "updated", oldValue: 12 }, |
892 |
{ object: obj, name: prop, type: "deleted", oldValue: 36 }, |
893 |
{ object: obj, name: prop, type: "new" }, |
894 |
]); |
895 |
Object.unobserve(obj, observer.callback); |
896 |
delete obj[prop];
|
897 |
} |
898 |
|
899 |
function TestObserveNonConfigurable(obj, prop, desc) { |
900 |
reset(); |
901 |
Object.observe(obj, observer.callback); |
902 |
Object.unobserve(obj, observer.callback); |
903 |
obj[prop] = 1;
|
904 |
Object.observe(obj, observer.callback); |
905 |
obj[prop] = 4;
|
906 |
obj[prop] = 4; // ignored |
907 |
obj[prop] = 5;
|
908 |
Object.defineProperty(obj, prop, {value: 6}); |
909 |
Object.defineProperty(obj, prop, {value: 6}); // ignored |
910 |
Object.defineProperty(obj, prop, {value: 7}); |
911 |
Object.defineProperty(obj, prop, {enumerable: desc.enumerable}); // ignored |
912 |
Object.defineProperty(obj, prop, {writable: false}); |
913 |
obj[prop] = 7; // ignored |
914 |
Object.deliverChangeRecords(observer.callback); |
915 |
observer.assertCallbackRecords([ |
916 |
{ object: obj, name: prop, type: "updated", oldValue: 1 }, |
917 |
{ object: obj, name: prop, type: "updated", oldValue: 4 }, |
918 |
{ object: obj, name: prop, type: "updated", oldValue: 5 }, |
919 |
{ object: obj, name: prop, type: "updated", oldValue: 6 }, |
920 |
{ object: obj, name: prop, type: "reconfigured" }, |
921 |
]); |
922 |
Object.unobserve(obj, observer.callback); |
923 |
} |
924 |
|
925 |
function createProxy(create, x) { |
926 |
var handler = {
|
927 |
getPropertyDescriptor: function(k) { |
928 |
for (var o = this.target; o; o = Object.getPrototypeOf(o)) { |
929 |
var desc = Object.getOwnPropertyDescriptor(o, k);
|
930 |
if (desc) return desc; |
931 |
} |
932 |
return undefined; |
933 |
}, |
934 |
getOwnPropertyDescriptor: function(k) { |
935 |
return Object.getOwnPropertyDescriptor(this.target, k); |
936 |
}, |
937 |
defineProperty: function(k, desc) { |
938 |
var x = Object.defineProperty(this.target, k, desc); |
939 |
Object.deliverChangeRecords(this.callback);
|
940 |
return x;
|
941 |
}, |
942 |
delete: function(k) { |
943 |
var x = delete this.target[k]; |
944 |
Object.deliverChangeRecords(this.callback);
|
945 |
return x;
|
946 |
}, |
947 |
getPropertyNames: function() { |
948 |
return Object.getOwnPropertyNames(this.target); |
949 |
}, |
950 |
target: {isProxy: true}, |
951 |
callback: function(changeRecords) { |
952 |
print("callback", stringifyNoThrow(handler.proxy), stringifyNoThrow(got));
|
953 |
for (var i in changeRecords) { |
954 |
var got = changeRecords[i];
|
955 |
var change = {object: handler.proxy, name: got.name, type: got.type}; |
956 |
if ("oldValue" in got) change.oldValue = got.oldValue; |
957 |
Object.getNotifier(handler.proxy).notify(change); |
958 |
} |
959 |
}, |
960 |
}; |
961 |
Object.observe(handler.target, handler.callback); |
962 |
return handler.proxy = create(handler, x);
|
963 |
} |
964 |
|
965 |
var objects = [
|
966 |
{}, |
967 |
[], |
968 |
this, // global object |
969 |
function(){},
|
970 |
(function(){ return arguments })(), |
971 |
(function(){ "use strict"; return arguments })(), |
972 |
Object(1), Object(true), Object("bla"), |
973 |
new Date(),
|
974 |
Object, Function, Date, RegExp, |
975 |
new Set, new Map, new WeakMap, |
976 |
new ArrayBuffer(10), new Int32Array(5), |
977 |
createProxy(Proxy.create, null),
|
978 |
createProxy(Proxy.createFunction, function(){}),
|
979 |
]; |
980 |
var properties = ["a", "1", 1, "length", "prototype", "name", "caller"]; |
981 |
|
982 |
// Cases that yield non-standard results.
|
983 |
function blacklisted(obj, prop) { |
984 |
return (obj instanceof Int32Array && prop == 1) || |
985 |
(obj instanceof Int32Array && prop === "length") || |
986 |
(obj instanceof ArrayBuffer && prop == 1) |
987 |
} |
988 |
|
989 |
for (var i in objects) for (var j in properties) { |
990 |
var obj = objects[i];
|
991 |
var prop = properties[j];
|
992 |
if (blacklisted(obj, prop)) continue; |
993 |
var desc = Object.getOwnPropertyDescriptor(obj, prop);
|
994 |
print("***", typeof obj, stringifyNoThrow(obj), prop); |
995 |
if (!desc || desc.configurable)
|
996 |
TestObserveConfigurable(obj, prop); |
997 |
else if (desc.writable) |
998 |
TestObserveNonConfigurable(obj, prop, desc); |
999 |
} |
1000 |
|
1001 |
|
1002 |
// Observing array length (including truncation)
|
1003 |
reset(); |
1004 |
var arr = ['a', 'b', 'c', 'd']; |
1005 |
var arr2 = ['alpha', 'beta']; |
1006 |
var arr3 = ['hello']; |
1007 |
arr3[2] = 'goodbye'; |
1008 |
arr3.length = 6;
|
1009 |
Object.defineProperty(arr, '0', {configurable: false}); |
1010 |
Object.defineProperty(arr, '2', {get: function(){}}); |
1011 |
Object.defineProperty(arr2, '0', {get: function(){}, configurable: false}); |
1012 |
Object.observe(arr, observer.callback); |
1013 |
Array.observe(arr, observer2.callback); |
1014 |
Object.observe(arr2, observer.callback); |
1015 |
Array.observe(arr2, observer2.callback); |
1016 |
Object.observe(arr3, observer.callback); |
1017 |
Array.observe(arr3, observer2.callback); |
1018 |
arr.length = 2;
|
1019 |
arr.length = 0;
|
1020 |
arr.length = 10;
|
1021 |
Object.defineProperty(arr, 'length', {writable: false}); |
1022 |
arr2.length = 0;
|
1023 |
arr2.length = 1; // no change expected |
1024 |
Object.defineProperty(arr2, 'length', {value: 1, writable: false}); |
1025 |
arr3.length = 0;
|
1026 |
++arr3.length; |
1027 |
arr3.length++; |
1028 |
arr3.length /= 2;
|
1029 |
Object.defineProperty(arr3, 'length', {value: 5}); |
1030 |
arr3[4] = 5; |
1031 |
Object.defineProperty(arr3, 'length', {value: 1, writable: false}); |
1032 |
Object.deliverChangeRecords(observer.callback); |
1033 |
observer.assertCallbackRecords([ |
1034 |
{ object: arr, name: '3', type: 'deleted', oldValue: 'd' }, |
1035 |
{ object: arr, name: '2', type: 'deleted' }, |
1036 |
{ object: arr, name: 'length', type: 'updated', oldValue: 4 }, |
1037 |
{ object: arr, name: '1', type: 'deleted', oldValue: 'b' }, |
1038 |
{ object: arr, name: 'length', type: 'updated', oldValue: 2 }, |
1039 |
{ object: arr, name: 'length', type: 'updated', oldValue: 1 }, |
1040 |
{ object: arr, name: 'length', type: 'reconfigured' }, |
1041 |
{ object: arr2, name: '1', type: 'deleted', oldValue: 'beta' }, |
1042 |
{ object: arr2, name: 'length', type: 'updated', oldValue: 2 }, |
1043 |
{ object: arr2, name: 'length', type: 'reconfigured' }, |
1044 |
{ object: arr3, name: '2', type: 'deleted', oldValue: 'goodbye' }, |
1045 |
{ object: arr3, name: '0', type: 'deleted', oldValue: 'hello' }, |
1046 |
{ object: arr3, name: 'length', type: 'updated', oldValue: 6 }, |
1047 |
{ object: arr3, name: 'length', type: 'updated', oldValue: 0 }, |
1048 |
{ object: arr3, name: 'length', type: 'updated', oldValue: 1 }, |
1049 |
{ object: arr3, name: 'length', type: 'updated', oldValue: 2 }, |
1050 |
{ object: arr3, name: 'length', type: 'updated', oldValue: 1 }, |
1051 |
{ object: arr3, name: '4', type: 'new' }, |
1052 |
{ object: arr3, name: '4', type: 'deleted', oldValue: 5 }, |
1053 |
// TODO(rafaelw): It breaks spec compliance to get two records here.
|
1054 |
// When the TODO in v8natives.js::DefineArrayProperty is addressed
|
1055 |
// which prevents DefineProperty from over-writing the magic length
|
1056 |
// property, these will collapse into a single record.
|
1057 |
{ object: arr3, name: 'length', type: 'updated', oldValue: 5 }, |
1058 |
{ object: arr3, name: 'length', type: 'reconfigured' } |
1059 |
]); |
1060 |
Object.deliverChangeRecords(observer2.callback); |
1061 |
observer2.assertCallbackRecords([ |
1062 |
{ object: arr, type: 'splice', index: 2, removed: [, 'd'], addedCount: 0 }, |
1063 |
{ object: arr, type: 'splice', index: 1, removed: ['b'], addedCount: 0 }, |
1064 |
{ object: arr, type: 'splice', index: 1, removed: [], addedCount: 9 }, |
1065 |
{ object: arr2, type: 'splice', index: 1, removed: ['beta'], addedCount: 0 }, |
1066 |
{ object: arr3, type: 'splice', index: 0, removed: ['hello',, 'goodbye',,,,], addedCount: 0 }, |
1067 |
{ object: arr3, type: 'splice', index: 0, removed: [], addedCount: 1 }, |
1068 |
{ object: arr3, type: 'splice', index: 1, removed: [], addedCount: 1 }, |
1069 |
{ object: arr3, type: 'splice', index: 1, removed: [,], addedCount: 0 }, |
1070 |
{ object: arr3, type: 'splice', index: 1, removed: [], addedCount: 4 }, |
1071 |
{ object: arr3, name: '4', type: 'new' }, |
1072 |
{ object: arr3, type: 'splice', index: 1, removed: [,,,5], addedCount: 0 } |
1073 |
]); |
1074 |
|
1075 |
|
1076 |
// Updating length on large (slow) array
|
1077 |
reset(); |
1078 |
var slow_arr = new Array(1000000000); |
1079 |
slow_arr[500000000] = 'hello'; |
1080 |
Object.observe(slow_arr, observer.callback); |
1081 |
var spliceRecords;
|
1082 |
function slowSpliceCallback(records) { |
1083 |
spliceRecords = records; |
1084 |
} |
1085 |
Array.observe(slow_arr, slowSpliceCallback); |
1086 |
slow_arr.length = 100;
|
1087 |
Object.deliverChangeRecords(observer.callback); |
1088 |
observer.assertCallbackRecords([ |
1089 |
{ object: slow_arr, name: '500000000', type: 'deleted', oldValue: 'hello' }, |
1090 |
{ object: slow_arr, name: 'length', type: 'updated', oldValue: 1000000000 }, |
1091 |
]); |
1092 |
Object.deliverChangeRecords(slowSpliceCallback); |
1093 |
assertEquals(spliceRecords.length, 1);
|
1094 |
// Have to custom assert this splice record because the removed array is huge.
|
1095 |
var splice = spliceRecords[0]; |
1096 |
assertSame(splice.object, slow_arr); |
1097 |
assertEquals(splice.type, 'splice');
|
1098 |
assertEquals(splice.index, 100);
|
1099 |
assertEquals(splice.addedCount, 0);
|
1100 |
var array_keys = %GetArrayKeys(splice.removed, splice.removed.length);
|
1101 |
assertEquals(array_keys.length, 1);
|
1102 |
assertEquals(array_keys[0], 499999900); |
1103 |
assertEquals(splice.removed[499999900], 'hello'); |
1104 |
assertEquals(splice.removed.length, 999999900);
|
1105 |
|
1106 |
|
1107 |
// Assignments in loops (checking different IC states).
|
1108 |
reset(); |
1109 |
var obj = {};
|
1110 |
Object.observe(obj, observer.callback); |
1111 |
for (var i = 0; i < 5; i++) { |
1112 |
obj["a" + i] = i;
|
1113 |
} |
1114 |
Object.deliverChangeRecords(observer.callback); |
1115 |
observer.assertCallbackRecords([ |
1116 |
{ object: obj, name: "a0", type: "new" }, |
1117 |
{ object: obj, name: "a1", type: "new" }, |
1118 |
{ object: obj, name: "a2", type: "new" }, |
1119 |
{ object: obj, name: "a3", type: "new" }, |
1120 |
{ object: obj, name: "a4", type: "new" }, |
1121 |
]); |
1122 |
|
1123 |
reset(); |
1124 |
var obj = {};
|
1125 |
Object.observe(obj, observer.callback); |
1126 |
for (var i = 0; i < 5; i++) { |
1127 |
obj[i] = i; |
1128 |
} |
1129 |
Object.deliverChangeRecords(observer.callback); |
1130 |
observer.assertCallbackRecords([ |
1131 |
{ object: obj, name: "0", type: "new" }, |
1132 |
{ object: obj, name: "1", type: "new" }, |
1133 |
{ object: obj, name: "2", type: "new" }, |
1134 |
{ object: obj, name: "3", type: "new" }, |
1135 |
{ object: obj, name: "4", type: "new" }, |
1136 |
]); |
1137 |
|
1138 |
|
1139 |
// Adding elements past the end of an array should notify on length for
|
1140 |
// Object.observe and emit "splices" for Array.observe.
|
1141 |
reset(); |
1142 |
var arr = [1, 2, 3]; |
1143 |
Object.observe(arr, observer.callback); |
1144 |
Array.observe(arr, observer2.callback); |
1145 |
arr[3] = 10; |
1146 |
arr[100] = 20; |
1147 |
Object.defineProperty(arr, '200', {value: 7}); |
1148 |
Object.defineProperty(arr, '400', {get: function(){}}); |
1149 |
arr[50] = 30; // no length change expected |
1150 |
Object.deliverChangeRecords(observer.callback); |
1151 |
observer.assertCallbackRecords([ |
1152 |
{ object: arr, name: '3', type: 'new' }, |
1153 |
{ object: arr, name: 'length', type: 'updated', oldValue: 3 }, |
1154 |
{ object: arr, name: '100', type: 'new' }, |
1155 |
{ object: arr, name: 'length', type: 'updated', oldValue: 4 }, |
1156 |
{ object: arr, name: '200', type: 'new' }, |
1157 |
{ object: arr, name: 'length', type: 'updated', oldValue: 101 }, |
1158 |
{ object: arr, name: '400', type: 'new' }, |
1159 |
{ object: arr, name: 'length', type: 'updated', oldValue: 201 }, |
1160 |
{ object: arr, name: '50', type: 'new' }, |
1161 |
]); |
1162 |
Object.deliverChangeRecords(observer2.callback); |
1163 |
observer2.assertCallbackRecords([ |
1164 |
{ object: arr, type: 'splice', index: 3, removed: [], addedCount: 1 }, |
1165 |
{ object: arr, type: 'splice', index: 4, removed: [], addedCount: 97 }, |
1166 |
{ object: arr, type: 'splice', index: 101, removed: [], addedCount: 100 }, |
1167 |
{ object: arr, type: 'splice', index: 201, removed: [], addedCount: 200 }, |
1168 |
{ object: arr, type: 'new', name: '50' }, |
1169 |
]); |
1170 |
|
1171 |
|
1172 |
// Tests for array methods, first on arrays and then on plain objects
|
1173 |
//
|
1174 |
// === ARRAYS ===
|
1175 |
//
|
1176 |
// Push
|
1177 |
reset(); |
1178 |
var array = [1, 2]; |
1179 |
Object.observe(array, observer.callback); |
1180 |
Array.observe(array, observer2.callback); |
1181 |
array.push(3, 4); |
1182 |
array.push(5);
|
1183 |
Object.deliverChangeRecords(observer.callback); |
1184 |
observer.assertCallbackRecords([ |
1185 |
{ object: array, name: '2', type: 'new' }, |
1186 |
{ object: array, name: 'length', type: 'updated', oldValue: 2 }, |
1187 |
{ object: array, name: '3', type: 'new' }, |
1188 |
{ object: array, name: 'length', type: 'updated', oldValue: 3 }, |
1189 |
{ object: array, name: '4', type: 'new' }, |
1190 |
{ object: array, name: 'length', type: 'updated', oldValue: 4 }, |
1191 |
]); |
1192 |
Object.deliverChangeRecords(observer2.callback); |
1193 |
observer2.assertCallbackRecords([ |
1194 |
{ object: array, type: 'splice', index: 2, removed: [], addedCount: 2 }, |
1195 |
{ object: array, type: 'splice', index: 4, removed: [], addedCount: 1 } |
1196 |
]); |
1197 |
|
1198 |
// Pop
|
1199 |
reset(); |
1200 |
var array = [1, 2]; |
1201 |
Object.observe(array, observer.callback); |
1202 |
array.pop(); |
1203 |
array.pop(); |
1204 |
Object.deliverChangeRecords(observer.callback); |
1205 |
observer.assertCallbackRecords([ |
1206 |
{ object: array, name: '1', type: 'deleted', oldValue: 2 }, |
1207 |
{ object: array, name: 'length', type: 'updated', oldValue: 2 }, |
1208 |
{ object: array, name: '0', type: 'deleted', oldValue: 1 }, |
1209 |
{ object: array, name: 'length', type: 'updated', oldValue: 1 }, |
1210 |
]); |
1211 |
|
1212 |
// Shift
|
1213 |
reset(); |
1214 |
var array = [1, 2]; |
1215 |
Object.observe(array, observer.callback); |
1216 |
array.shift(); |
1217 |
array.shift(); |
1218 |
Object.deliverChangeRecords(observer.callback); |
1219 |
observer.assertCallbackRecords([ |
1220 |
{ object: array, name: '0', type: 'updated', oldValue: 1 }, |
1221 |
{ object: array, name: '1', type: 'deleted', oldValue: 2 }, |
1222 |
{ object: array, name: 'length', type: 'updated', oldValue: 2 }, |
1223 |
{ object: array, name: '0', type: 'deleted', oldValue: 2 }, |
1224 |
{ object: array, name: 'length', type: 'updated', oldValue: 1 }, |
1225 |
]); |
1226 |
|
1227 |
// Unshift
|
1228 |
reset(); |
1229 |
var array = [1, 2]; |
1230 |
Object.observe(array, observer.callback); |
1231 |
array.unshift(3, 4); |
1232 |
Object.deliverChangeRecords(observer.callback); |
1233 |
observer.assertCallbackRecords([ |
1234 |
{ object: array, name: '3', type: 'new' }, |
1235 |
{ object: array, name: 'length', type: 'updated', oldValue: 2 }, |
1236 |
{ object: array, name: '2', type: 'new' }, |
1237 |
{ object: array, name: '0', type: 'updated', oldValue: 1 }, |
1238 |
{ object: array, name: '1', type: 'updated', oldValue: 2 }, |
1239 |
]); |
1240 |
|
1241 |
// Splice
|
1242 |
reset(); |
1243 |
var array = [1, 2, 3]; |
1244 |
Object.observe(array, observer.callback); |
1245 |
array.splice(1, 1, 4, 5); |
1246 |
Object.deliverChangeRecords(observer.callback); |
1247 |
observer.assertCallbackRecords([ |
1248 |
{ object: array, name: '3', type: 'new' }, |
1249 |
{ object: array, name: 'length', type: 'updated', oldValue: 3 }, |
1250 |
{ object: array, name: '1', type: 'updated', oldValue: 2 }, |
1251 |
{ object: array, name: '2', type: 'updated', oldValue: 3 }, |
1252 |
]); |
1253 |
|
1254 |
// Sort
|
1255 |
reset(); |
1256 |
var array = [3, 2, 1]; |
1257 |
Object.observe(array, observer.callback); |
1258 |
array.sort(); |
1259 |
assertEquals(1, array[0]); |
1260 |
assertEquals(2, array[1]); |
1261 |
assertEquals(3, array[2]); |
1262 |
Object.deliverChangeRecords(observer.callback); |
1263 |
observer.assertCallbackRecords([ |
1264 |
{ object: array, name: '1', type: 'updated', oldValue: 2 }, |
1265 |
{ object: array, name: '0', type: 'updated', oldValue: 3 }, |
1266 |
{ object: array, name: '2', type: 'updated', oldValue: 1 }, |
1267 |
{ object: array, name: '1', type: 'updated', oldValue: 3 }, |
1268 |
{ object: array, name: '0', type: 'updated', oldValue: 2 }, |
1269 |
]); |
1270 |
|
1271 |
// Splice emitted after Array mutation methods
|
1272 |
function MockArray(initial, observer) { |
1273 |
for (var i = 0; i < initial.length; i++) |
1274 |
this[i] = initial[i];
|
1275 |
|
1276 |
this.length_ = initial.length;
|
1277 |
this.observer = observer;
|
1278 |
} |
1279 |
MockArray.prototype = { |
1280 |
set length(length) { |
1281 |
Object.getNotifier(this).notify({ type: 'lengthChange' }); |
1282 |
this.length_ = length;
|
1283 |
Object.observe(this, this.observer.callback, ['splice']); |
1284 |
}, |
1285 |
get length() { |
1286 |
return this.length_; |
1287 |
} |
1288 |
} |
1289 |
|
1290 |
reset(); |
1291 |
var array = new MockArray([], observer); |
1292 |
Object.observe(array, observer.callback, ['lengthChange']);
|
1293 |
Array.prototype.push.call(array, 1);
|
1294 |
Object.deliverChangeRecords(observer.callback); |
1295 |
observer.assertCallbackRecords([ |
1296 |
{ object: array, type: 'lengthChange' }, |
1297 |
{ object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }, |
1298 |
]); |
1299 |
|
1300 |
reset(); |
1301 |
var array = new MockArray([1], observer); |
1302 |
Object.observe(array, observer.callback, ['lengthChange']);
|
1303 |
Array.prototype.pop.call(array); |
1304 |
Object.deliverChangeRecords(observer.callback); |
1305 |
observer.assertCallbackRecords([ |
1306 |
{ object: array, type: 'lengthChange' }, |
1307 |
{ object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }, |
1308 |
]); |
1309 |
|
1310 |
reset(); |
1311 |
var array = new MockArray([1], observer); |
1312 |
Object.observe(array, observer.callback, ['lengthChange']);
|
1313 |
Array.prototype.shift.call(array); |
1314 |
Object.deliverChangeRecords(observer.callback); |
1315 |
observer.assertCallbackRecords([ |
1316 |
{ object: array, type: 'lengthChange' }, |
1317 |
{ object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }, |
1318 |
]); |
1319 |
|
1320 |
reset(); |
1321 |
var array = new MockArray([], observer); |
1322 |
Object.observe(array, observer.callback, ['lengthChange']);
|
1323 |
Array.prototype.unshift.call(array, 1);
|
1324 |
Object.deliverChangeRecords(observer.callback); |
1325 |
observer.assertCallbackRecords([ |
1326 |
{ object: array, type: 'lengthChange' }, |
1327 |
{ object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }, |
1328 |
]); |
1329 |
|
1330 |
reset(); |
1331 |
var array = new MockArray([0, 1, 2], observer); |
1332 |
Object.observe(array, observer.callback, ['lengthChange']);
|
1333 |
Array.prototype.splice.call(array, 1, 1); |
1334 |
Object.deliverChangeRecords(observer.callback); |
1335 |
observer.assertCallbackRecords([ |
1336 |
{ object: array, type: 'lengthChange' }, |
1337 |
{ object: array, type: 'splice', index: 1, removed: [1], addedCount: 0 }, |
1338 |
]); |
1339 |
|
1340 |
//
|
1341 |
// === PLAIN OBJECTS ===
|
1342 |
//
|
1343 |
// Push
|
1344 |
reset() |
1345 |
var array = {0: 1, 1: 2, length: 2} |
1346 |
Object.observe(array, observer.callback); |
1347 |
Array.prototype.push.call(array, 3, 4); |
1348 |
Object.deliverChangeRecords(observer.callback); |
1349 |
observer.assertCallbackRecords([ |
1350 |
{ object: array, name: '2', type: 'new' }, |
1351 |
{ object: array, name: '3', type: 'new' }, |
1352 |
{ object: array, name: 'length', type: 'updated', oldValue: 2 }, |
1353 |
]); |
1354 |
|
1355 |
// Pop
|
1356 |
reset(); |
1357 |
var array = [1, 2]; |
1358 |
Object.observe(array, observer.callback); |
1359 |
Array.observe(array, observer2.callback); |
1360 |
array.pop(); |
1361 |
array.pop(); |
1362 |
array.pop(); |
1363 |
Object.deliverChangeRecords(observer.callback); |
1364 |
observer.assertCallbackRecords([ |
1365 |
{ object: array, name: '1', type: 'deleted', oldValue: 2 }, |
1366 |
{ object: array, name: 'length', type: 'updated', oldValue: 2 }, |
1367 |
{ object: array, name: '0', type: 'deleted', oldValue: 1 }, |
1368 |
{ object: array, name: 'length', type: 'updated', oldValue: 1 }, |
1369 |
]); |
1370 |
Object.deliverChangeRecords(observer2.callback); |
1371 |
observer2.assertCallbackRecords([ |
1372 |
{ object: array, type: 'splice', index: 1, removed: [2], addedCount: 0 }, |
1373 |
{ object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 } |
1374 |
]); |
1375 |
|
1376 |
// Shift
|
1377 |
reset(); |
1378 |
var array = [1, 2]; |
1379 |
Object.observe(array, observer.callback); |
1380 |
Array.observe(array, observer2.callback); |
1381 |
array.shift(); |
1382 |
array.shift(); |
1383 |
array.shift(); |
1384 |
Object.deliverChangeRecords(observer.callback); |
1385 |
observer.assertCallbackRecords([ |
1386 |
{ object: array, name: '0', type: 'updated', oldValue: 1 }, |
1387 |
{ object: array, name: '1', type: 'deleted', oldValue: 2 }, |
1388 |
{ object: array, name: 'length', type: 'updated', oldValue: 2 }, |
1389 |
{ object: array, name: '0', type: 'deleted', oldValue: 2 }, |
1390 |
{ object: array, name: 'length', type: 'updated', oldValue: 1 }, |
1391 |
]); |
1392 |
Object.deliverChangeRecords(observer2.callback); |
1393 |
observer2.assertCallbackRecords([ |
1394 |
{ object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }, |
1395 |
{ object: array, type: 'splice', index: 0, removed: [2], addedCount: 0 } |
1396 |
]); |
1397 |
|
1398 |
// Unshift
|
1399 |
reset(); |
1400 |
var array = [1, 2]; |
1401 |
Object.observe(array, observer.callback); |
1402 |
Array.observe(array, observer2.callback); |
1403 |
array.unshift(3, 4); |
1404 |
array.unshift(5);
|
1405 |
Object.deliverChangeRecords(observer.callback); |
1406 |
observer.assertCallbackRecords([ |
1407 |
{ object: array, name: '3', type: 'new' }, |
1408 |
{ object: array, name: 'length', type: 'updated', oldValue: 2 }, |
1409 |
{ object: array, name: '2', type: 'new' }, |
1410 |
{ object: array, name: '0', type: 'updated', oldValue: 1 }, |
1411 |
{ object: array, name: '1', type: 'updated', oldValue: 2 }, |
1412 |
{ object: array, name: '4', type: 'new' }, |
1413 |
{ object: array, name: 'length', type: 'updated', oldValue: 4 }, |
1414 |
{ object: array, name: '3', type: 'updated', oldValue: 2 }, |
1415 |
{ object: array, name: '2', type: 'updated', oldValue: 1 }, |
1416 |
{ object: array, name: '1', type: 'updated', oldValue: 4 }, |
1417 |
{ object: array, name: '0', type: 'updated', oldValue: 3 }, |
1418 |
]); |
1419 |
Object.deliverChangeRecords(observer2.callback); |
1420 |
observer2.assertCallbackRecords([ |
1421 |
{ object: array, type: 'splice', index: 0, removed: [], addedCount: 2 }, |
1422 |
{ object: array, type: 'splice', index: 0, removed: [], addedCount: 1 } |
1423 |
]); |
1424 |
|
1425 |
// Splice
|
1426 |
reset(); |
1427 |
var array = [1, 2, 3]; |
1428 |
Object.observe(array, observer.callback); |
1429 |
Array.observe(array, observer2.callback); |
1430 |
array.splice(1, 0, 4, 5); // 1 4 5 2 3 |
1431 |
array.splice(0, 2); // 5 2 3 |
1432 |
array.splice(1, 2, 6, 7); // 5 6 7 |
1433 |
array.splice(2, 0); |
1434 |
Object.deliverChangeRecords(observer.callback); |
1435 |
observer.assertCallbackRecords([ |
1436 |
{ object: array, name: '4', type: 'new' }, |
1437 |
{ object: array, name: 'length', type: 'updated', oldValue: 3 }, |
1438 |
{ object: array, name: '3', type: 'new' }, |
1439 |
{ object: array, name: '1', type: 'updated', oldValue: 2 }, |
1440 |
{ object: array, name: '2', type: 'updated', oldValue: 3 }, |
1441 |
|
1442 |
{ object: array, name: '0', type: 'updated', oldValue: 1 }, |
1443 |
{ object: array, name: '1', type: 'updated', oldValue: 4 }, |
1444 |
{ object: array, name: '2', type: 'updated', oldValue: 5 }, |
1445 |
{ object: array, name: '4', type: 'deleted', oldValue: 3 }, |
1446 |
{ object: array, name: '3', type: 'deleted', oldValue: 2 }, |
1447 |
{ object: array, name: 'length', type: 'updated', oldValue: 5 }, |
1448 |
|
1449 |
{ object: array, name: '1', type: 'updated', oldValue: 2 }, |
1450 |
{ object: array, name: '2', type: 'updated', oldValue: 3 }, |
1451 |
]); |
1452 |
Object.deliverChangeRecords(observer2.callback); |
1453 |
observer2.assertCallbackRecords([ |
1454 |
{ object: array, type: 'splice', index: 1, removed: [], addedCount: 2 }, |
1455 |
{ object: array, type: 'splice', index: 0, removed: [1, 4], addedCount: 0 }, |
1456 |
{ object: array, type: 'splice', index: 1, removed: [2, 3], addedCount: 2 }, |
1457 |
]); |
1458 |
|
1459 |
// Exercise StoreIC_ArrayLength
|
1460 |
reset(); |
1461 |
var dummy = {};
|
1462 |
Object.observe(dummy, observer.callback); |
1463 |
Object.unobserve(dummy, observer.callback); |
1464 |
var array = [0]; |
1465 |
Object.observe(array, observer.callback); |
1466 |
array.splice(0, 1); |
1467 |
Object.deliverChangeRecords(observer.callback); |
1468 |
observer.assertCallbackRecords([ |
1469 |
{ object: array, name: '0', type: 'deleted', oldValue: 0 }, |
1470 |
{ object: array, name: 'length', type: 'updated', oldValue: 1}, |
1471 |
]); |
1472 |
|
1473 |
|
1474 |
// __proto__
|
1475 |
reset(); |
1476 |
var obj = {};
|
1477 |
Object.observe(obj, observer.callback); |
1478 |
var p = {foo: 'yes'}; |
1479 |
var q = {bar: 'no'}; |
1480 |
obj.__proto__ = p; |
1481 |
obj.__proto__ = p; // ignored
|
1482 |
obj.__proto__ = null;
|
1483 |
obj.__proto__ = q; // the __proto__ accessor is gone
|
1484 |
// TODO(adamk): Add tests for objects with hidden prototypes
|
1485 |
// once we support observing the global object.
|
1486 |
Object.deliverChangeRecords(observer.callback); |
1487 |
observer.assertCallbackRecords([ |
1488 |
{ object: obj, name: '__proto__', type: 'prototype', |
1489 |
oldValue: Object.prototype },
|
1490 |
{ object: obj, name: '__proto__', type: 'prototype', oldValue: p }, |
1491 |
{ object: obj, name: '__proto__', type: 'new' }, |
1492 |
]); |
1493 |
|
1494 |
|
1495 |
// Function.prototype
|
1496 |
reset(); |
1497 |
var fun = function(){}; |
1498 |
Object.observe(fun, observer.callback); |
1499 |
var myproto = {foo: 'bar'}; |
1500 |
fun.prototype = myproto; |
1501 |
fun.prototype = 7;
|
1502 |
fun.prototype = 7; // ignored |
1503 |
Object.defineProperty(fun, 'prototype', {value: 8}); |
1504 |
Object.deliverChangeRecords(observer.callback); |
1505 |
observer.assertRecordCount(3);
|
1506 |
// Manually examine the first record in order to test
|
1507 |
// lazy creation of oldValue
|
1508 |
assertSame(fun, observer.records[0].object);
|
1509 |
assertEquals('prototype', observer.records[0].name); |
1510 |
assertEquals('updated', observer.records[0].type); |
1511 |
// The only existing reference to the oldValue object is in this
|
1512 |
// record, so to test that lazy creation happened correctly
|
1513 |
// we compare its constructor to our function (one of the invariants
|
1514 |
// ensured when creating an object via AllocateFunctionPrototype).
|
1515 |
assertSame(fun, observer.records[0].oldValue.constructor);
|
1516 |
observer.records.splice(0, 1); |
1517 |
observer.assertCallbackRecords([ |
1518 |
{ object: fun, name: 'prototype', type: 'updated', oldValue: myproto }, |
1519 |
{ object: fun, name: 'prototype', type: 'updated', oldValue: 7 }, |
1520 |
]); |
1521 |
|
1522 |
// Function.prototype should not be observable except on the object itself
|
1523 |
reset(); |
1524 |
var fun = function(){}; |
1525 |
var obj = { __proto__: fun }; |
1526 |
Object.observe(obj, observer.callback); |
1527 |
obj.prototype = 7;
|
1528 |
Object.deliverChangeRecords(observer.callback); |
1529 |
observer.assertNotCalled(); |
1530 |
|
1531 |
|
1532 |
// Check that changes in observation status are detected in all IC states and
|
1533 |
// in optimized code, especially in cases usually using fast elements.
|
1534 |
var mutation = [
|
1535 |
"a[i] = v",
|
1536 |
"a[i] ? ++a[i] : a[i] = v",
|
1537 |
"a[i] ? a[i]++ : a[i] = v",
|
1538 |
"a[i] ? a[i] += 1 : a[i] = v",
|
1539 |
"a[i] ? a[i] -= -1 : a[i] = v",
|
1540 |
]; |
1541 |
|
1542 |
var props = [1, "1", "a"]; |
1543 |
|
1544 |
function TestFastElements(prop, mutation, prepopulate, polymorphic, optimize) { |
1545 |
var setElement = eval(
|
1546 |
"(function setElement(a, i, v) { " + mutation + "; " + |
1547 |
"/* " + [].join.call(arguments, " ") + " */" + |
1548 |
"})"
|
1549 |
); |
1550 |
print("TestFastElements:", setElement);
|
1551 |
|
1552 |
var arr = prepopulate ? [1, 2, 3, 4, 5] : [0]; |
1553 |
if (prepopulate) arr[prop] = 2; // for non-element case |
1554 |
setElement(arr, prop, 3);
|
1555 |
setElement(arr, prop, 4);
|
1556 |
if (polymorphic) setElement(["M", "i", "l", "n", "e", "r"], 0, "m"); |
1557 |
if (optimize) %OptimizeFunctionOnNextCall(setElement);
|
1558 |
setElement(arr, prop, 5);
|
1559 |
|
1560 |
reset(); |
1561 |
Object.observe(arr, observer.callback); |
1562 |
setElement(arr, prop, 989898);
|
1563 |
Object.deliverChangeRecords(observer.callback); |
1564 |
observer.assertCallbackRecords([ |
1565 |
{ object: arr, name: "" + prop, type: 'updated', oldValue: 5 } |
1566 |
]); |
1567 |
} |
1568 |
|
1569 |
for (var b1 = 0; b1 < 2; ++b1) |
1570 |
for (var b2 = 0; b2 < 2; ++b2) |
1571 |
for (var b3 = 0; b3 < 2; ++b3) |
1572 |
for (var i in props) |
1573 |
for (var j in mutation) |
1574 |
TestFastElements(props[i], mutation[j], b1 != 0, b2 != 0, b3 != 0); |
1575 |
|
1576 |
|
1577 |
var mutation = [
|
1578 |
"a.length = v",
|
1579 |
"a.length += newSize - oldSize",
|
1580 |
"a.length -= oldSize - newSize",
|
1581 |
]; |
1582 |
|
1583 |
var mutationByIncr = [
|
1584 |
"++a.length",
|
1585 |
"a.length++",
|
1586 |
]; |
1587 |
|
1588 |
function TestFastElementsLength( |
1589 |
mutation, polymorphic, optimize, oldSize, newSize) { |
1590 |
var setLength = eval(
|
1591 |
"(function setLength(a, v) { " + mutation + "; " + |
1592 |
"/* " + [].join.call(arguments, " ") + " */" |
1593 |
+ "})"
|
1594 |
); |
1595 |
print("TestFastElementsLength:", setLength);
|
1596 |
|
1597 |
function array(n) { |
1598 |
var arr = new Array(n); |
1599 |
for (var i = 0; i < n; ++i) arr[i] = i; |
1600 |
return arr;
|
1601 |
} |
1602 |
|
1603 |
setLength(array(oldSize), newSize); |
1604 |
setLength(array(oldSize), newSize); |
1605 |
if (polymorphic) setLength(array(oldSize).map(isNaN), newSize);
|
1606 |
if (optimize) %OptimizeFunctionOnNextCall(setLength);
|
1607 |
setLength(array(oldSize), newSize); |
1608 |
|
1609 |
reset(); |
1610 |
var arr = array(oldSize);
|
1611 |
Object.observe(arr, observer.callback); |
1612 |
setLength(arr, newSize); |
1613 |
Object.deliverChangeRecords(observer.callback); |
1614 |
if (oldSize === newSize) {
|
1615 |
observer.assertNotCalled(); |
1616 |
} else {
|
1617 |
var count = oldSize > newSize ? oldSize - newSize : 0; |
1618 |
observer.assertRecordCount(count + 1);
|
1619 |
var lengthRecord = observer.records[count];
|
1620 |
assertSame(arr, lengthRecord.object); |
1621 |
assertEquals('length', lengthRecord.name);
|
1622 |
assertEquals('updated', lengthRecord.type);
|
1623 |
assertSame(oldSize, lengthRecord.oldValue); |
1624 |
} |
1625 |
} |
1626 |
|
1627 |
for (var b1 = 0; b1 < 2; ++b1) |
1628 |
for (var b2 = 0; b2 < 2; ++b2) |
1629 |
for (var n1 = 0; n1 < 3; ++n1) |
1630 |
for (var n2 = 0; n2 < 3; ++n2) |
1631 |
for (var i in mutation) |
1632 |
TestFastElementsLength(mutation[i], b1 != 0, b2 != 0, 20*n1, 20*n2); |
1633 |
|
1634 |
for (var b1 = 0; b1 < 2; ++b1) |
1635 |
for (var b2 = 0; b2 < 2; ++b2) |
1636 |
for (var n = 0; n < 3; ++n) |
1637 |
for (var i in mutationByIncr) |
1638 |
TestFastElementsLength(mutationByIncr[i], b1 != 0, b2 != 0, 7*n, 7*n+1); |