blob: d88c24eac5bcd87c98ecf0e7b6899ada76c0e050 [file] [log] [blame]
danno@chromium.org72204d52012-10-31 10:02:10 +00001// 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
ulan@chromium.org8e8d8822012-11-23 14:36:46 +000028// Flags: --harmony-observation --harmony-proxies --harmony-collections
danno@chromium.org72204d52012-10-31 10:02:10 +000029
30var allObservers = [];
31function reset() {
32 allObservers.forEach(function(observer) { observer.reset(); });
33}
34
35function createObserver() {
36 "use strict"; // So that |this| in callback can be undefined.
37
38 var observer = {
39 records: undefined,
40 callbackCount: 0,
41 reset: function() {
42 this.records = undefined;
43 this.callbackCount = 0;
44 },
45 assertNotCalled: function() {
46 assertEquals(undefined, this.records);
47 assertEquals(0, this.callbackCount);
48 },
49 assertCalled: function() {
50 assertEquals(1, this.callbackCount);
51 },
52 assertRecordCount: function(count) {
53 this.assertCalled();
54 assertEquals(count, this.records.length);
55 },
56 assertCallbackRecords: function(recs) {
57 this.assertRecordCount(recs.length);
58 for (var i = 0; i < recs.length; i++) {
ulan@chromium.org8e8d8822012-11-23 14:36:46 +000059 if ('name' in recs[i])
60 recs[i].name = String(recs[i].name);
61 print(i, JSON.stringify(this.records[i]), JSON.stringify(recs[i]));
danno@chromium.org72204d52012-10-31 10:02:10 +000062 assertSame(this.records[i].object, recs[i].object);
63 assertEquals('string', typeof recs[i].type);
64 assertPropertiesEqual(this.records[i], recs[i]);
65 }
66 }
67 };
68
69 observer.callback = function(r) {
70 assertEquals(undefined, this);
71 assertEquals('object', typeof r);
72 assertTrue(r instanceof Array)
73 observer.records = r;
74 observer.callbackCount++;
75 };
76
77 observer.reset();
78 allObservers.push(observer);
79 return observer;
80}
81
82var observer = createObserver();
83assertEquals("function", typeof observer.callback);
84var obj = {};
85
86function frozenFunction() {}
87Object.freeze(frozenFunction);
88var nonFunction = {};
89var changeRecordWithAccessor = { type: 'foo' };
90var recordCreated = false;
91Object.defineProperty(changeRecordWithAccessor, 'name', {
92 get: function() {
93 recordCreated = true;
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +000094 return "bar";
danno@chromium.org72204d52012-10-31 10:02:10 +000095 },
96 enumerable: true
97})
98
99// Object.observe
100assertThrows(function() { Object.observe("non-object", observer.callback); }, TypeError);
101assertThrows(function() { Object.observe(obj, nonFunction); }, TypeError);
102assertThrows(function() { Object.observe(obj, frozenFunction); }, TypeError);
ulan@chromium.org8e8d8822012-11-23 14:36:46 +0000103assertEquals(obj, Object.observe(obj, observer.callback));
danno@chromium.org72204d52012-10-31 10:02:10 +0000104
105// Object.unobserve
106assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError);
yangguo@chromium.orgfb377212012-11-16 14:43:43 +0000107assertThrows(function() { Object.unobserve(obj, nonFunction); }, TypeError);
ulan@chromium.org8e8d8822012-11-23 14:36:46 +0000108assertEquals(obj, Object.unobserve(obj, observer.callback));
danno@chromium.org72204d52012-10-31 10:02:10 +0000109
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000110// Object.getNotifier
111var notifier = Object.getNotifier(obj);
112assertSame(notifier, Object.getNotifier(obj));
113assertEquals(null, Object.getNotifier(Object.freeze({})));
114assertFalse(notifier.hasOwnProperty('notify'));
115assertEquals([], Object.keys(notifier));
116var notifyDesc = Object.getOwnPropertyDescriptor(notifier.__proto__, 'notify');
117assertTrue(notifyDesc.configurable);
118assertTrue(notifyDesc.writable);
119assertFalse(notifyDesc.enumerable);
120assertThrows(function() { notifier.notify({}); }, TypeError);
121assertThrows(function() { notifier.notify({ type: 4 }); }, TypeError);
122var notify = notifier.notify;
123assertThrows(function() { notify.call(undefined, { type: 'a' }); }, TypeError);
124assertThrows(function() { notify.call(null, { type: 'a' }); }, TypeError);
125assertThrows(function() { notify.call(5, { type: 'a' }); }, TypeError);
126assertThrows(function() { notify.call('hello', { type: 'a' }); }, TypeError);
127assertThrows(function() { notify.call(false, { type: 'a' }); }, TypeError);
128assertThrows(function() { notify.call({}, { type: 'a' }); }, TypeError);
danno@chromium.org72204d52012-10-31 10:02:10 +0000129assertFalse(recordCreated);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000130notifier.notify(changeRecordWithAccessor);
131assertFalse(recordCreated); // not observed yet
danno@chromium.org72204d52012-10-31 10:02:10 +0000132
133// Object.deliverChangeRecords
134assertThrows(function() { Object.deliverChangeRecords(nonFunction); }, TypeError);
135
danno@chromium.org72204d52012-10-31 10:02:10 +0000136Object.observe(obj, observer.callback);
yangguo@chromium.orgfb377212012-11-16 14:43:43 +0000137
138// notify uses to [[CreateOwnProperty]] to create changeRecord;
139reset();
140var protoExpandoAccessed = false;
141Object.defineProperty(Object.prototype, 'protoExpando',
142 {
143 configurable: true,
144 set: function() { protoExpandoAccessed = true; }
145 }
146);
147notifier.notify({ type: 'foo', protoExpando: 'val'});
148assertFalse(protoExpandoAccessed);
149delete Object.prototype.protoExpando;
150Object.deliverChangeRecords(observer.callback);
151
152// Multiple records are delivered.
153reset();
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000154notifier.notify({
danno@chromium.org72204d52012-10-31 10:02:10 +0000155 type: 'updated',
156 name: 'foo',
157 expando: 1
158});
159
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000160notifier.notify({
161 object: notifier, // object property is ignored
danno@chromium.org72204d52012-10-31 10:02:10 +0000162 type: 'deleted',
163 name: 'bar',
164 expando2: 'str'
165});
166Object.deliverChangeRecords(observer.callback);
167observer.assertCallbackRecords([
168 { object: obj, name: 'foo', type: 'updated', expando: 1 },
169 { object: obj, name: 'bar', type: 'deleted', expando2: 'str' }
170]);
171
172// No delivery takes place if no records are pending
173reset();
174Object.deliverChangeRecords(observer.callback);
175observer.assertNotCalled();
176
177// Multiple observation has no effect.
178reset();
179Object.observe(obj, observer.callback);
180Object.observe(obj, observer.callback);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000181Object.getNotifier(obj).notify({
danno@chromium.org72204d52012-10-31 10:02:10 +0000182 type: 'foo',
183});
184Object.deliverChangeRecords(observer.callback);
185observer.assertCalled();
186
187// Observation can be stopped.
188reset();
189Object.unobserve(obj, observer.callback);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000190Object.getNotifier(obj).notify({
danno@chromium.org72204d52012-10-31 10:02:10 +0000191 type: 'foo',
192});
193Object.deliverChangeRecords(observer.callback);
194observer.assertNotCalled();
195
196// Multiple unobservation has no effect
197reset();
198Object.unobserve(obj, observer.callback);
199Object.unobserve(obj, observer.callback);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000200Object.getNotifier(obj).notify({
danno@chromium.org72204d52012-10-31 10:02:10 +0000201 type: 'foo',
202});
203Object.deliverChangeRecords(observer.callback);
204observer.assertNotCalled();
205
206// Re-observation works and only includes changeRecords after of call.
207reset();
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000208Object.getNotifier(obj).notify({
danno@chromium.org72204d52012-10-31 10:02:10 +0000209 type: 'foo',
210});
211Object.observe(obj, observer.callback);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000212Object.getNotifier(obj).notify({
danno@chromium.org72204d52012-10-31 10:02:10 +0000213 type: 'foo',
214});
215records = undefined;
216Object.deliverChangeRecords(observer.callback);
217observer.assertRecordCount(1);
218
219// Observing a continuous stream of changes, while itermittantly unobserving.
220reset();
221Object.observe(obj, observer.callback);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000222Object.getNotifier(obj).notify({
danno@chromium.org72204d52012-10-31 10:02:10 +0000223 type: 'foo',
224 val: 1
225});
226
227Object.unobserve(obj, observer.callback);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000228Object.getNotifier(obj).notify({
danno@chromium.org72204d52012-10-31 10:02:10 +0000229 type: 'foo',
230 val: 2
231});
232
233Object.observe(obj, observer.callback);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000234Object.getNotifier(obj).notify({
danno@chromium.org72204d52012-10-31 10:02:10 +0000235 type: 'foo',
236 val: 3
237});
238
239Object.unobserve(obj, observer.callback);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000240Object.getNotifier(obj).notify({
danno@chromium.org72204d52012-10-31 10:02:10 +0000241 type: 'foo',
242 val: 4
243});
244
245Object.observe(obj, observer.callback);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000246Object.getNotifier(obj).notify({
danno@chromium.org72204d52012-10-31 10:02:10 +0000247 type: 'foo',
248 val: 5
249});
250
251Object.unobserve(obj, observer.callback);
252Object.deliverChangeRecords(observer.callback);
253observer.assertCallbackRecords([
254 { object: obj, type: 'foo', val: 1 },
255 { object: obj, type: 'foo', val: 3 },
256 { object: obj, type: 'foo', val: 5 }
257]);
258
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000259// Observing multiple objects; records appear in order.
danno@chromium.org72204d52012-10-31 10:02:10 +0000260reset();
261var obj2 = {};
262var obj3 = {}
263Object.observe(obj, observer.callback);
264Object.observe(obj3, observer.callback);
265Object.observe(obj2, observer.callback);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000266Object.getNotifier(obj).notify({
267 type: 'foo1',
danno@chromium.org72204d52012-10-31 10:02:10 +0000268});
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000269Object.getNotifier(obj2).notify({
270 type: 'foo2',
danno@chromium.org72204d52012-10-31 10:02:10 +0000271});
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000272Object.getNotifier(obj3).notify({
273 type: 'foo3',
danno@chromium.org72204d52012-10-31 10:02:10 +0000274});
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000275Object.observe(obj3, observer.callback);
danno@chromium.org72204d52012-10-31 10:02:10 +0000276Object.deliverChangeRecords(observer.callback);
277observer.assertCallbackRecords([
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000278 { object: obj, type: 'foo1' },
279 { object: obj2, type: 'foo2' },
280 { object: obj3, type: 'foo3' }
281]);
282
283// Observing named properties.
284reset();
285var obj = {a: 1}
286Object.observe(obj, observer.callback);
287obj.a = 2;
288obj["a"] = 3;
289delete obj.a;
290obj.a = 4;
291obj.a = 4; // ignored
292obj.a = 5;
293Object.defineProperty(obj, "a", {value: 6});
294Object.defineProperty(obj, "a", {writable: false});
295obj.a = 7; // ignored
296Object.defineProperty(obj, "a", {value: 8});
297Object.defineProperty(obj, "a", {value: 7, writable: true});
298Object.defineProperty(obj, "a", {get: function() {}});
ulan@chromium.org8e8d8822012-11-23 14:36:46 +0000299Object.defineProperty(obj, "a", {get: frozenFunction});
300Object.defineProperty(obj, "a", {get: frozenFunction}); // ignored
301Object.defineProperty(obj, "a", {get: frozenFunction, set: frozenFunction});
302Object.defineProperty(obj, "a", {set: frozenFunction}); // ignored
303Object.defineProperty(obj, "a", {get: undefined, set: frozenFunction});
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000304delete obj.a;
305delete obj.a;
306Object.defineProperty(obj, "a", {get: function() {}, configurable: true});
307Object.defineProperty(obj, "a", {value: 9, writable: true});
308obj.a = 10;
309delete obj.a;
310Object.defineProperty(obj, "a", {value: 11, configurable: true});
311Object.deliverChangeRecords(observer.callback);
312observer.assertCallbackRecords([
313 { object: obj, name: "a", type: "updated", oldValue: 1 },
314 { object: obj, name: "a", type: "updated", oldValue: 2 },
315 { object: obj, name: "a", type: "deleted", oldValue: 3 },
316 { object: obj, name: "a", type: "new" },
317 { object: obj, name: "a", type: "updated", oldValue: 4 },
318 { object: obj, name: "a", type: "updated", oldValue: 5 },
319 { object: obj, name: "a", type: "reconfigured", oldValue: 6 },
320 { object: obj, name: "a", type: "updated", oldValue: 6 },
321 { object: obj, name: "a", type: "reconfigured", oldValue: 8 },
322 { object: obj, name: "a", type: "reconfigured", oldValue: 7 },
323 { object: obj, name: "a", type: "reconfigured" },
ulan@chromium.org8e8d8822012-11-23 14:36:46 +0000324 { object: obj, name: "a", type: "reconfigured" },
325 { object: obj, name: "a", type: "reconfigured" },
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000326 { object: obj, name: "a", type: "deleted" },
327 { object: obj, name: "a", type: "new" },
328 { object: obj, name: "a", type: "reconfigured" },
329 { object: obj, name: "a", type: "updated", oldValue: 9 },
330 { object: obj, name: "a", type: "deleted", oldValue: 10 },
331 { object: obj, name: "a", type: "new" },
332]);
333
334// Observing indexed properties.
335reset();
336var obj = {'1': 1}
337Object.observe(obj, observer.callback);
338obj[1] = 2;
339obj[1] = 3;
340delete obj[1];
341obj[1] = 4;
342obj[1] = 4; // ignored
343obj[1] = 5;
344Object.defineProperty(obj, "1", {value: 6});
345Object.defineProperty(obj, "1", {writable: false});
346obj[1] = 7; // ignored
347Object.defineProperty(obj, "1", {value: 8});
348Object.defineProperty(obj, "1", {value: 7, writable: true});
349Object.defineProperty(obj, "1", {get: function() {}});
ulan@chromium.org8e8d8822012-11-23 14:36:46 +0000350Object.defineProperty(obj, "1", {get: frozenFunction});
351Object.defineProperty(obj, "1", {get: frozenFunction}); // ignored
352Object.defineProperty(obj, "1", {get: frozenFunction, set: frozenFunction});
353Object.defineProperty(obj, "1", {set: frozenFunction}); // ignored
354Object.defineProperty(obj, "1", {get: undefined, set: frozenFunction});
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000355delete obj[1];
356delete obj[1];
357Object.defineProperty(obj, "1", {get: function() {}, configurable: true});
358Object.defineProperty(obj, "1", {value: 9, writable: true});
359obj[1] = 10;
360delete obj[1];
361Object.defineProperty(obj, "1", {value: 11, configurable: true});
362Object.deliverChangeRecords(observer.callback);
363observer.assertCallbackRecords([
364 { object: obj, name: "1", type: "updated", oldValue: 1 },
365 { object: obj, name: "1", type: "updated", oldValue: 2 },
366 { object: obj, name: "1", type: "deleted", oldValue: 3 },
367 { object: obj, name: "1", type: "new" },
368 { object: obj, name: "1", type: "updated", oldValue: 4 },
369 { object: obj, name: "1", type: "updated", oldValue: 5 },
370 { object: obj, name: "1", type: "reconfigured", oldValue: 6 },
371 { object: obj, name: "1", type: "updated", oldValue: 6 },
372 { object: obj, name: "1", type: "reconfigured", oldValue: 8 },
373 { object: obj, name: "1", type: "reconfigured", oldValue: 7 },
yangguo@chromium.orgfb377212012-11-16 14:43:43 +0000374 { object: obj, name: "1", type: "reconfigured" },
ulan@chromium.org8e8d8822012-11-23 14:36:46 +0000375 { object: obj, name: "1", type: "reconfigured" },
376 { object: obj, name: "1", type: "reconfigured" },
yangguo@chromium.orgfb377212012-11-16 14:43:43 +0000377 { object: obj, name: "1", type: "deleted" },
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000378 { object: obj, name: "1", type: "new" },
yangguo@chromium.orgfb377212012-11-16 14:43:43 +0000379 { object: obj, name: "1", type: "reconfigured" },
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000380 { object: obj, name: "1", type: "updated", oldValue: 9 },
381 { object: obj, name: "1", type: "deleted", oldValue: 10 },
382 { object: obj, name: "1", type: "new" },
383]);
384
ulan@chromium.org8e8d8822012-11-23 14:36:46 +0000385
386// Test all kinds of objects generically.
387function TestObserveConfigurable(obj, prop) {
388 reset();
389 obj[prop] = 1;
390 Object.observe(obj, observer.callback);
391 obj[prop] = 2;
392 obj[prop] = 3;
393 delete obj[prop];
394 obj[prop] = 4;
395 obj[prop] = 4; // ignored
396 obj[prop] = 5;
397 Object.defineProperty(obj, prop, {value: 6});
398 Object.defineProperty(obj, prop, {writable: false});
399 obj[prop] = 7; // ignored
400 Object.defineProperty(obj, prop, {value: 8});
401 Object.defineProperty(obj, prop, {value: 7, writable: true});
402 Object.defineProperty(obj, prop, {get: function() {}});
403 Object.defineProperty(obj, prop, {get: frozenFunction});
404 Object.defineProperty(obj, prop, {get: frozenFunction}); // ignored
405 Object.defineProperty(obj, prop, {get: frozenFunction, set: frozenFunction});
406 Object.defineProperty(obj, prop, {set: frozenFunction}); // ignored
407 Object.defineProperty(obj, prop, {get: undefined, set: frozenFunction});
danno@chromium.org1f34ad32012-11-26 14:53:56 +0000408 obj.__defineSetter__(prop, frozenFunction); // ignored
409 obj.__defineSetter__(prop, function() {});
410 obj.__defineGetter__(prop, function() {});
ulan@chromium.org8e8d8822012-11-23 14:36:46 +0000411 delete obj[prop];
danno@chromium.org1f34ad32012-11-26 14:53:56 +0000412 delete obj[prop]; // ignored
413 obj.__defineGetter__(prop, function() {});
ulan@chromium.org8e8d8822012-11-23 14:36:46 +0000414 delete obj[prop];
415 Object.defineProperty(obj, prop, {get: function() {}, configurable: true});
416 Object.defineProperty(obj, prop, {value: 9, writable: true});
417 obj[prop] = 10;
418 delete obj[prop];
419 Object.defineProperty(obj, prop, {value: 11, configurable: true});
420 Object.deliverChangeRecords(observer.callback);
421 observer.assertCallbackRecords([
422 { object: obj, name: prop, type: "updated", oldValue: 1 },
423 { object: obj, name: prop, type: "updated", oldValue: 2 },
424 { object: obj, name: prop, type: "deleted", oldValue: 3 },
425 { object: obj, name: prop, type: "new" },
426 { object: obj, name: prop, type: "updated", oldValue: 4 },
427 { object: obj, name: prop, type: "updated", oldValue: 5 },
428 { object: obj, name: prop, type: "reconfigured", oldValue: 6 },
429 { object: obj, name: prop, type: "updated", oldValue: 6 },
430 { object: obj, name: prop, type: "reconfigured", oldValue: 8 },
431 { object: obj, name: prop, type: "reconfigured", oldValue: 7 },
432 { object: obj, name: prop, type: "reconfigured" },
433 { object: obj, name: prop, type: "reconfigured" },
434 { object: obj, name: prop, type: "reconfigured" },
danno@chromium.org1f34ad32012-11-26 14:53:56 +0000435 { object: obj, name: prop, type: "reconfigured" },
436 { object: obj, name: prop, type: "reconfigured" },
437 { object: obj, name: prop, type: "deleted" },
438 { object: obj, name: prop, type: "new" },
ulan@chromium.org8e8d8822012-11-23 14:36:46 +0000439 { object: obj, name: prop, type: "deleted" },
440 { object: obj, name: prop, type: "new" },
441 { object: obj, name: prop, type: "reconfigured" },
442 { object: obj, name: prop, type: "updated", oldValue: 9 },
443 { object: obj, name: prop, type: "deleted", oldValue: 10 },
444 { object: obj, name: prop, type: "new" },
445 ]);
446 Object.unobserve(obj, observer.callback);
447 delete obj[prop];
448}
449
450function TestObserveNonConfigurable(obj, prop) {
451 reset();
452 obj[prop] = 1;
453 Object.observe(obj, observer.callback);
454 obj[prop] = 4;
455 obj[prop] = 4; // ignored
456 obj[prop] = 5;
457 Object.defineProperty(obj, prop, {value: 6});
458 Object.defineProperty(obj, prop, {value: 6}); // ignored
459 Object.defineProperty(obj, prop, {value: 7});
460 Object.defineProperty(obj, prop, {enumerable: true}); // ignored
461 Object.defineProperty(obj, prop, {writable: false});
462 obj[prop] = 7; // ignored
463 Object.defineProperty(obj, prop, {get: function() {}}); // ignored
464 Object.deliverChangeRecords(observer.callback);
465 observer.assertCallbackRecords([
466 { object: obj, name: prop, type: "updated", oldValue: 1 },
467 { object: obj, name: prop, type: "updated", oldValue: 4 },
468 { object: obj, name: prop, type: "updated", oldValue: 5 },
469 { object: obj, name: prop, type: "updated", oldValue: 6 },
470 { object: obj, name: prop, type: "reconfigured", oldValue: 7 },
471 ]);
472 Object.unobserve(obj, observer.callback);
473}
474
475function createProxy(create, x) {
476 var handler = {
477 getPropertyDescriptor: function(k) {
danno@chromium.org1f34ad32012-11-26 14:53:56 +0000478 for (var o = this.target; o; o = Object.getPrototypeOf(o)) {
479 var desc = Object.getOwnPropertyDescriptor(o, k);
480 if (desc) return desc;
481 }
482 return undefined;
ulan@chromium.org8e8d8822012-11-23 14:36:46 +0000483 },
484 getOwnPropertyDescriptor: function(k) {
485 return Object.getOwnPropertyDescriptor(this.target, k);
486 },
487 defineProperty: function(k, desc) {
488 var x = Object.defineProperty(this.target, k, desc);
489 Object.deliverChangeRecords(this.callback);
490 return x;
491 },
492 delete: function(k) {
493 var x = delete this.target[k];
494 Object.deliverChangeRecords(this.callback);
495 return x;
496 },
497 getPropertyNames: function() {
498 return Object.getOwnPropertyNames(this.target);
499 },
500 target: {isProxy: true},
501 callback: function(changeRecords) {
502 print("callback", JSON.stringify(handler.proxy), JSON.stringify(got));
503 for (var i in changeRecords) {
504 var got = changeRecords[i];
505 var change = {object: handler.proxy, name: got.name, type: got.type};
506 if ("oldValue" in got) change.oldValue = got.oldValue;
507 Object.getNotifier(handler.proxy).notify(change);
508 }
509 },
510 };
511 Object.observe(handler.target, handler.callback);
512 return handler.proxy = create(handler, x);
513}
514
515var objects = [
516 {},
517 [],
518 this, // global object
519 function(){},
520 (function(){ return arguments })(),
521 (function(){ "use strict"; return arguments })(),
522 Object(1), Object(true), Object("bla"),
523 new Date(),
524 Object, Function, Date, RegExp,
525 new Set, new Map, new WeakMap,
526 new ArrayBuffer(10), new Int32Array(5),
527 createProxy(Proxy.create, null),
528 createProxy(Proxy.createFunction, function(){}),
529];
530var properties = ["a", "1", 1, "length", "prototype"];
531
532// Cases that yield non-standard results.
533// TODO(observe): ...or don't work yet.
534function blacklisted(obj, prop) {
535 return (obj instanceof Int32Array && prop == 1) ||
536 (obj instanceof Int32Array && prop === "length") ||
537 (obj instanceof ArrayBuffer && prop == 1) ||
538 // TODO(observe): oldValue when reconfiguring array length
539 (obj instanceof Array && prop === "length") ||
540 // TODO(observe): prototype property on functions
541 (obj instanceof Function && prop === "prototype") ||
542 // TODO(observe): global object
543 obj === this;
544}
545
546for (var i in objects) for (var j in properties) {
547 var obj = objects[i];
548 var prop = properties[j];
549 if (blacklisted(obj, prop)) continue;
550 var desc = Object.getOwnPropertyDescriptor(obj, prop);
551 print("***", typeof obj, JSON.stringify(obj), prop);
552 if (!desc || desc.configurable)
553 TestObserveConfigurable(obj, prop);
554 else if (desc.writable)
555 TestObserveNonConfigurable(obj, prop);
556}
557
558
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000559// Observing array length (including truncation)
560reset();
561var arr = ['a', 'b', 'c', 'd'];
562var arr2 = ['alpha', 'beta'];
563var arr3 = ['hello'];
564arr3[2] = 'goodbye';
565arr3.length = 6;
566// TODO(adamk): Enable this test case when it can run in a reasonable
567// amount of time.
568//var slow_arr = new Array(1000000000);
569//slow_arr[500000000] = 'hello';
570Object.defineProperty(arr, '0', {configurable: false});
571Object.defineProperty(arr, '2', {get: function(){}});
572Object.defineProperty(arr2, '0', {get: function(){}, configurable: false});
573Object.observe(arr, observer.callback);
574Object.observe(arr2, observer.callback);
575Object.observe(arr3, observer.callback);
576arr.length = 2;
577arr.length = 0;
578arr.length = 10;
579arr2.length = 0;
580arr2.length = 1; // no change expected
581arr3.length = 0;
582Object.defineProperty(arr3, 'length', {value: 5});
583Object.defineProperty(arr3, 'length', {value: 10, writable: false});
584Object.deliverChangeRecords(observer.callback);
585observer.assertCallbackRecords([
586 { object: arr, name: '3', type: 'deleted', oldValue: 'd' },
mmassi@chromium.org49a44672012-12-04 13:52:03 +0000587 { object: arr, name: '2', type: 'deleted' },
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000588 { object: arr, name: 'length', type: 'updated', oldValue: 4 },
589 { object: arr, name: '1', type: 'deleted', oldValue: 'b' },
590 { object: arr, name: 'length', type: 'updated', oldValue: 2 },
591 { object: arr, name: 'length', type: 'updated', oldValue: 1 },
592 { object: arr2, name: '1', type: 'deleted', oldValue: 'beta' },
593 { object: arr2, name: 'length', type: 'updated', oldValue: 2 },
594 { object: arr3, name: '2', type: 'deleted', oldValue: 'goodbye' },
595 { object: arr3, name: '0', type: 'deleted', oldValue: 'hello' },
596 { object: arr3, name: 'length', type: 'updated', oldValue: 6 },
597 { object: arr3, name: 'length', type: 'updated', oldValue: 0 },
598 { object: arr3, name: 'length', type: 'updated', oldValue: 5 },
599 // TODO(adamk): This record should be merged with the above
600 { object: arr3, name: 'length', type: 'reconfigured' },
601]);
602
603// Assignments in loops (checking different IC states).
604reset();
605var obj = {};
606Object.observe(obj, observer.callback);
607for (var i = 0; i < 5; i++) {
608 obj["a" + i] = i;
609}
610Object.deliverChangeRecords(observer.callback);
611observer.assertCallbackRecords([
612 { object: obj, name: "a0", type: "new" },
613 { object: obj, name: "a1", type: "new" },
614 { object: obj, name: "a2", type: "new" },
615 { object: obj, name: "a3", type: "new" },
616 { object: obj, name: "a4", type: "new" },
617]);
618
619reset();
620var obj = {};
621Object.observe(obj, observer.callback);
622for (var i = 0; i < 5; i++) {
623 obj[i] = i;
624}
625Object.deliverChangeRecords(observer.callback);
626observer.assertCallbackRecords([
627 { object: obj, name: "0", type: "new" },
628 { object: obj, name: "1", type: "new" },
629 { object: obj, name: "2", type: "new" },
630 { object: obj, name: "3", type: "new" },
631 { object: obj, name: "4", type: "new" },
632]);
633
634// Adding elements past the end of an array should notify on length
635reset();
636var arr = [1, 2, 3];
637Object.observe(arr, observer.callback);
638arr[3] = 10;
639arr[100] = 20;
640Object.defineProperty(arr, '200', {value: 7});
641Object.defineProperty(arr, '400', {get: function(){}});
642arr[50] = 30; // no length change expected
643Object.deliverChangeRecords(observer.callback);
644observer.assertCallbackRecords([
645 { object: arr, name: '3', type: 'new' },
646 { object: arr, name: 'length', type: 'updated', oldValue: 3 },
647 { object: arr, name: '100', type: 'new' },
648 { object: arr, name: 'length', type: 'updated', oldValue: 4 },
649 { object: arr, name: '200', type: 'new' },
650 { object: arr, name: 'length', type: 'updated', oldValue: 101 },
651 { object: arr, name: '400', type: 'new' },
652 { object: arr, name: 'length', type: 'updated', oldValue: 201 },
653 { object: arr, name: '50', type: 'new' },
654]);
655
656// Tests for array methods, first on arrays and then on plain objects
657//
658// === ARRAYS ===
659//
660// Push
661reset();
662var array = [1, 2];
663Object.observe(array, observer.callback);
664array.push(3, 4);
665Object.deliverChangeRecords(observer.callback);
666observer.assertCallbackRecords([
667 { object: array, name: '2', type: 'new' },
668 { object: array, name: 'length', type: 'updated', oldValue: 2 },
669 { object: array, name: '3', type: 'new' },
670 { object: array, name: 'length', type: 'updated', oldValue: 3 },
671]);
672
673// Pop
674reset();
675var array = [1, 2];
676Object.observe(array, observer.callback);
677array.pop();
678array.pop();
679Object.deliverChangeRecords(observer.callback);
680observer.assertCallbackRecords([
681 { object: array, name: '1', type: 'deleted', oldValue: 2 },
682 { object: array, name: 'length', type: 'updated', oldValue: 2 },
683 { object: array, name: '0', type: 'deleted', oldValue: 1 },
684 { object: array, name: 'length', type: 'updated', oldValue: 1 },
685]);
686
687// Shift
688reset();
689var array = [1, 2];
690Object.observe(array, observer.callback);
691array.shift();
692array.shift();
693Object.deliverChangeRecords(observer.callback);
694observer.assertCallbackRecords([
695 { object: array, name: '0', type: 'updated', oldValue: 1 },
696 { object: array, name: '1', type: 'deleted', oldValue: 2 },
697 { object: array, name: 'length', type: 'updated', oldValue: 2 },
698 { object: array, name: '0', type: 'deleted', oldValue: 2 },
699 { object: array, name: 'length', type: 'updated', oldValue: 1 },
700]);
701
702// Unshift
703reset();
704var array = [1, 2];
705Object.observe(array, observer.callback);
706array.unshift(3, 4);
707Object.deliverChangeRecords(observer.callback);
708observer.assertCallbackRecords([
709 { object: array, name: '3', type: 'new' },
710 { object: array, name: 'length', type: 'updated', oldValue: 2 },
711 { object: array, name: '2', type: 'new' },
712 { object: array, name: '0', type: 'updated', oldValue: 1 },
713 { object: array, name: '1', type: 'updated', oldValue: 2 },
714]);
715
716// Splice
717reset();
718var array = [1, 2, 3];
719Object.observe(array, observer.callback);
720array.splice(1, 1, 4, 5);
721Object.deliverChangeRecords(observer.callback);
722observer.assertCallbackRecords([
723 { object: array, name: '3', type: 'new' },
724 { object: array, name: 'length', type: 'updated', oldValue: 3 },
725 { object: array, name: '1', type: 'updated', oldValue: 2 },
726 { object: array, name: '2', type: 'updated', oldValue: 3 },
727]);
728
729//
730// === PLAIN OBJECTS ===
731//
732// Push
733reset()
734var array = {0: 1, 1: 2, length: 2}
735Object.observe(array, observer.callback);
736Array.prototype.push.call(array, 3, 4);
737Object.deliverChangeRecords(observer.callback);
738observer.assertCallbackRecords([
739 { object: array, name: '2', type: 'new' },
740 { object: array, name: '3', type: 'new' },
741 { object: array, name: 'length', type: 'updated', oldValue: 2 },
742]);
743
744// Pop
745reset()
746var array = {0: 1, 1: 2, length: 2};
747Object.observe(array, observer.callback);
748Array.prototype.pop.call(array);
749Array.prototype.pop.call(array);
750Object.deliverChangeRecords(observer.callback);
751observer.assertCallbackRecords([
752 { object: array, name: '1', type: 'deleted', oldValue: 2 },
753 { object: array, name: 'length', type: 'updated', oldValue: 2 },
754 { object: array, name: '0', type: 'deleted', oldValue: 1 },
755 { object: array, name: 'length', type: 'updated', oldValue: 1 },
756]);
757
758// Shift
759reset()
760var array = {0: 1, 1: 2, length: 2};
761Object.observe(array, observer.callback);
762Array.prototype.shift.call(array);
763Array.prototype.shift.call(array);
764Object.deliverChangeRecords(observer.callback);
765observer.assertCallbackRecords([
766 { object: array, name: '0', type: 'updated', oldValue: 1 },
767 { object: array, name: '1', type: 'deleted', oldValue: 2 },
768 { object: array, name: 'length', type: 'updated', oldValue: 2 },
769 { object: array, name: '0', type: 'deleted', oldValue: 2 },
770 { object: array, name: 'length', type: 'updated', oldValue: 1 },
771]);
772
773// Unshift
774reset()
775var array = {0: 1, 1: 2, length: 2};
776Object.observe(array, observer.callback);
777Array.prototype.unshift.call(array, 3, 4);
778Object.deliverChangeRecords(observer.callback);
779observer.assertCallbackRecords([
780 { object: array, name: '3', type: 'new' },
781 { object: array, name: '2', type: 'new' },
782 { object: array, name: '0', type: 'updated', oldValue: 1 },
783 { object: array, name: '1', type: 'updated', oldValue: 2 },
784 { object: array, name: 'length', type: 'updated', oldValue: 2 },
785]);
786
787// Splice
788reset()
789var array = {0: 1, 1: 2, 2: 3, length: 3};
790Object.observe(array, observer.callback);
791Array.prototype.splice.call(array, 1, 1, 4, 5);
792Object.deliverChangeRecords(observer.callback);
793observer.assertCallbackRecords([
794 { object: array, name: '3', type: 'new' },
795 { object: array, name: '1', type: 'updated', oldValue: 2 },
796 { object: array, name: '2', type: 'updated', oldValue: 3 },
797 { object: array, name: 'length', type: 'updated', oldValue: 3 },
798]);
yangguo@chromium.orgfb377212012-11-16 14:43:43 +0000799
800// Exercise StoreIC_ArrayLength
801reset();
802var dummy = {};
803Object.observe(dummy, observer.callback);
804Object.unobserve(dummy, observer.callback);
805var array = [0];
806Object.observe(array, observer.callback);
807array.splice(0, 1);
808Object.deliverChangeRecords(observer.callback);
809observer.assertCallbackRecords([
810 { object: array, name: '0', type: 'deleted', oldValue: 0 },
811 { object: array, name: 'length', type: 'updated', oldValue: 1},
812]);