blob: ada7919d6dd215f6d49470f30dca230d14f02d83 [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
28"use strict";
29
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +000030var observationState = %GetObservationState();
31if (IS_UNDEFINED(observationState.observerInfoMap)) {
mmassi@chromium.org2f0efde2013-02-06 14:12:58 +000032 observationState.observerInfoMap = %ObservationWeakMapCreate();
33 observationState.objectInfoMap = %ObservationWeakMapCreate();
34 observationState.notifierTargetMap = %ObservationWeakMapCreate();
yangguo@chromium.orgfb377212012-11-16 14:43:43 +000035 observationState.pendingObservers = new InternalArray;
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +000036 observationState.observerPriority = 0;
danno@chromium.org72204d52012-10-31 10:02:10 +000037}
38
mmassi@chromium.org2f0efde2013-02-06 14:12:58 +000039function ObservationWeakMap(map) {
40 this.map_ = map;
danno@chromium.org72204d52012-10-31 10:02:10 +000041}
42
mmassi@chromium.org2f0efde2013-02-06 14:12:58 +000043ObservationWeakMap.prototype = {
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +000044 get: function(key) {
mmassi@chromium.org2f0efde2013-02-06 14:12:58 +000045 key = %UnwrapGlobalProxy(key);
46 if (!IS_SPEC_OBJECT(key)) return void 0;
47 return %WeakMapGet(this.map_, key);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +000048 },
49 set: function(key, value) {
mmassi@chromium.org2f0efde2013-02-06 14:12:58 +000050 key = %UnwrapGlobalProxy(key);
51 if (!IS_SPEC_OBJECT(key)) return void 0;
52 %WeakMapSet(this.map_, key, value);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +000053 },
54 has: function(key) {
mstarzinger@chromium.org32280cf2012-12-06 17:32:37 +000055 return !IS_UNDEFINED(this.get(key));
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +000056 }
57};
58
mmassi@chromium.org2f0efde2013-02-06 14:12:58 +000059var observerInfoMap =
60 new ObservationWeakMap(observationState.observerInfoMap);
61var objectInfoMap = new ObservationWeakMap(observationState.objectInfoMap);
62var notifierTargetMap =
63 new ObservationWeakMap(observationState.notifierTargetMap);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +000064
65function CreateObjectInfo(object) {
66 var info = {
67 changeObservers: new InternalArray,
68 notifier: null,
jkummerow@chromium.org4e308cf2013-05-17 13:39:16 +000069 inactiveObservers: new InternalArray,
70 performing: { __proto__: null },
71 performingCount: 0,
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +000072 };
73 objectInfoMap.set(object, info);
74 return info;
75}
danno@chromium.org72204d52012-10-31 10:02:10 +000076
jkummerow@chromium.org4e308cf2013-05-17 13:39:16 +000077var defaultAcceptTypes = {
78 __proto__: null,
79 'new': true,
80 'updated': true,
81 'deleted': true,
82 'prototype': true,
83 'reconfigured': true
84};
85
86function CreateObserver(callback, accept) {
87 var observer = {
88 __proto__: null,
89 callback: callback,
90 accept: defaultAcceptTypes
91 };
92
93 if (IS_UNDEFINED(accept))
94 return observer;
95
96 var acceptMap = { __proto__: null };
97 for (var i = 0; i < accept.length; i++)
98 acceptMap[accept[i]] = true;
99
100 observer.accept = acceptMap;
101 return observer;
102}
103
104function ObserverIsActive(observer, objectInfo) {
105 if (objectInfo.performingCount === 0)
106 return true;
107
108 var performing = objectInfo.performing;
109 for (var type in performing) {
110 if (performing[type] > 0 && observer.accept[type])
111 return false;
112 }
113
114 return true;
115}
116
117function ObserverIsInactive(observer, objectInfo) {
118 return !ObserverIsActive(observer, objectInfo);
119}
120
121function RemoveNullElements(from) {
122 var i = 0;
123 var j = 0;
124 for (; i < from.length; i++) {
125 if (from[i] === null)
126 continue;
127 if (j < i)
128 from[j] = from[i];
129 j++;
130 }
131
132 if (i !== j)
133 from.length = from.length - (i - j);
134}
135
136function RepartitionObservers(conditionFn, from, to, objectInfo) {
137 var anyRemoved = false;
138 for (var i = 0; i < from.length; i++) {
139 var observer = from[i];
140 if (conditionFn(observer, objectInfo)) {
141 anyRemoved = true;
142 from[i] = null;
143 to.push(observer);
144 }
145 }
146
147 if (anyRemoved)
148 RemoveNullElements(from);
149}
150
151function BeginPerformChange(objectInfo, type) {
152 objectInfo.performing[type] = (objectInfo.performing[type] || 0) + 1;
153 objectInfo.performingCount++;
154 RepartitionObservers(ObserverIsInactive,
155 objectInfo.changeObservers,
156 objectInfo.inactiveObservers,
157 objectInfo);
158}
159
160function EndPerformChange(objectInfo, type) {
161 objectInfo.performing[type]--;
162 objectInfo.performingCount--;
163 RepartitionObservers(ObserverIsActive,
164 objectInfo.inactiveObservers,
165 objectInfo.changeObservers,
166 objectInfo);
167}
168
169function EnsureObserverRemoved(objectInfo, callback) {
170 function remove(observerList) {
171 for (var i = 0; i < observerList.length; i++) {
172 if (observerList[i].callback === callback) {
173 observerList.splice(i, 1);
174 return true;
175 }
176 }
177 return false;
178 }
179
180 if (!remove(objectInfo.changeObservers))
181 remove(objectInfo.inactiveObservers);
182}
183
184function AcceptArgIsValid(arg) {
185 if (IS_UNDEFINED(arg))
186 return true;
187
188 if (!IS_SPEC_OBJECT(arg) ||
189 !IS_NUMBER(arg.length) ||
190 arg.length < 0)
191 return false;
192
193 var length = arg.length;
194 for (var i = 0; i < length; i++) {
195 if (!IS_STRING(arg[i]))
196 return false;
197 }
198 return true;
199}
200
201function ObjectObserve(object, callback, accept) {
danno@chromium.org72204d52012-10-31 10:02:10 +0000202 if (!IS_SPEC_OBJECT(object))
203 throw MakeTypeError("observe_non_object", ["observe"]);
204 if (!IS_SPEC_FUNCTION(callback))
205 throw MakeTypeError("observe_non_function", ["observe"]);
yangguo@chromium.orgfb377212012-11-16 14:43:43 +0000206 if (ObjectIsFrozen(callback))
danno@chromium.org72204d52012-10-31 10:02:10 +0000207 throw MakeTypeError("observe_callback_frozen");
jkummerow@chromium.org4e308cf2013-05-17 13:39:16 +0000208 if (!AcceptArgIsValid(accept))
209 throw MakeTypeError("observe_accept_invalid");
danno@chromium.org72204d52012-10-31 10:02:10 +0000210
211 if (!observerInfoMap.has(callback)) {
danno@chromium.org72204d52012-10-31 10:02:10 +0000212 observerInfoMap.set(callback, {
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000213 pendingChangeRecords: null,
214 priority: observationState.observerPriority++,
danno@chromium.org72204d52012-10-31 10:02:10 +0000215 });
216 }
217
218 var objectInfo = objectInfoMap.get(object);
yangguo@chromium.orga6bbcc82012-12-21 12:35:02 +0000219 if (IS_UNDEFINED(objectInfo)) objectInfo = CreateObjectInfo(object);
220 %SetIsObserved(object, true);
danno@chromium.org72204d52012-10-31 10:02:10 +0000221
jkummerow@chromium.org4e308cf2013-05-17 13:39:16 +0000222 EnsureObserverRemoved(objectInfo, callback);
223
224 var observer = CreateObserver(callback, accept);
225 if (ObserverIsActive(observer, objectInfo))
226 objectInfo.changeObservers.push(observer);
227 else
228 objectInfo.inactiveObservers.push(observer);
danno@chromium.org72204d52012-10-31 10:02:10 +0000229
ulan@chromium.org8e8d8822012-11-23 14:36:46 +0000230 return object;
danno@chromium.org72204d52012-10-31 10:02:10 +0000231}
232
233function ObjectUnobserve(object, callback) {
234 if (!IS_SPEC_OBJECT(object))
235 throw MakeTypeError("observe_non_object", ["unobserve"]);
yangguo@chromium.orgfb377212012-11-16 14:43:43 +0000236 if (!IS_SPEC_FUNCTION(callback))
237 throw MakeTypeError("observe_non_function", ["unobserve"]);
danno@chromium.org72204d52012-10-31 10:02:10 +0000238
239 var objectInfo = objectInfoMap.get(object);
240 if (IS_UNDEFINED(objectInfo))
ulan@chromium.org8e8d8822012-11-23 14:36:46 +0000241 return object;
danno@chromium.org72204d52012-10-31 10:02:10 +0000242
jkummerow@chromium.org4e308cf2013-05-17 13:39:16 +0000243 EnsureObserverRemoved(objectInfo, callback);
244
245 if (objectInfo.changeObservers.length === 0 &&
246 objectInfo.inactiveObservers.length === 0) {
247 %SetIsObserved(object, false);
yangguo@chromium.orga6bbcc82012-12-21 12:35:02 +0000248 }
danno@chromium.org72204d52012-10-31 10:02:10 +0000249
ulan@chromium.org8e8d8822012-11-23 14:36:46 +0000250 return object;
danno@chromium.org72204d52012-10-31 10:02:10 +0000251}
252
jkummerow@chromium.org4e308cf2013-05-17 13:39:16 +0000253function ArrayObserve(object, callback) {
254 return ObjectObserve(object, callback, ['new',
255 'updated',
256 'deleted',
257 'splice']);
258}
259
260function ArrayUnobserve(object, callback) {
261 return ObjectUnobserve(object, callback);
262}
263
danno@chromium.org72204d52012-10-31 10:02:10 +0000264function EnqueueChangeRecord(changeRecord, observers) {
mstarzinger@chromium.orgf705b502013-04-04 11:38:09 +0000265 // TODO(rossberg): adjust once there is a story for symbols vs proxies.
266 if (IS_SYMBOL(changeRecord.name)) return;
267
danno@chromium.org72204d52012-10-31 10:02:10 +0000268 for (var i = 0; i < observers.length; i++) {
269 var observer = observers[i];
jkummerow@chromium.org4e308cf2013-05-17 13:39:16 +0000270 if (IS_UNDEFINED(observer.accept[changeRecord.type]))
271 continue;
272
273 var callback = observer.callback;
274 var observerInfo = observerInfoMap.get(callback);
275 observationState.pendingObservers[observerInfo.priority] = callback;
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000276 %SetObserverDeliveryPending();
danno@chromium.org72204d52012-10-31 10:02:10 +0000277 if (IS_NULL(observerInfo.pendingChangeRecords)) {
278 observerInfo.pendingChangeRecords = new InternalArray(changeRecord);
279 } else {
280 observerInfo.pendingChangeRecords.push(changeRecord);
281 }
282 }
283}
284
jkummerow@chromium.org4e308cf2013-05-17 13:39:16 +0000285function BeginPerformSplice(array) {
286 var objectInfo = objectInfoMap.get(array);
287 if (!IS_UNDEFINED(objectInfo))
288 BeginPerformChange(objectInfo, 'splice');
289}
290
291function EndPerformSplice(array) {
292 var objectInfo = objectInfoMap.get(array);
293 if (!IS_UNDEFINED(objectInfo))
294 EndPerformChange(objectInfo, 'splice');
295}
296
danno@chromium.org1fd77d52013-06-07 16:01:45 +0000297function EnqueueSpliceRecord(array, index, removed, addedCount) {
jkummerow@chromium.org4e308cf2013-05-17 13:39:16 +0000298 var objectInfo = objectInfoMap.get(array);
299 if (IS_UNDEFINED(objectInfo) || objectInfo.changeObservers.length === 0)
300 return;
301
302 var changeRecord = {
303 type: 'splice',
304 object: array,
305 index: index,
306 removed: removed,
307 addedCount: addedCount
308 };
309
jkummerow@chromium.orgc1184022013-05-28 16:58:15 +0000310 ObjectFreeze(changeRecord);
311 ObjectFreeze(changeRecord.removed);
jkummerow@chromium.org4e308cf2013-05-17 13:39:16 +0000312 EnqueueChangeRecord(changeRecord, objectInfo.changeObservers);
313}
314
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000315function NotifyChange(type, object, name, oldValue) {
316 var objectInfo = objectInfoMap.get(object);
jkummerow@chromium.org4e308cf2013-05-17 13:39:16 +0000317 if (objectInfo.changeObservers.length === 0)
318 return;
319
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000320 var changeRecord = (arguments.length < 4) ?
321 { type: type, object: object, name: name } :
322 { type: type, object: object, name: name, oldValue: oldValue };
jkummerow@chromium.orgc1184022013-05-28 16:58:15 +0000323 ObjectFreeze(changeRecord);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000324 EnqueueChangeRecord(changeRecord, objectInfo.changeObservers);
325}
326
327var notifierPrototype = {};
328
329function ObjectNotifierNotify(changeRecord) {
330 if (!IS_SPEC_OBJECT(this))
331 throw MakeTypeError("called_on_non_object", ["notify"]);
332
333 var target = notifierTargetMap.get(this);
334 if (IS_UNDEFINED(target))
335 throw MakeTypeError("observe_notify_non_notifier");
danno@chromium.org72204d52012-10-31 10:02:10 +0000336 if (!IS_STRING(changeRecord.type))
337 throw MakeTypeError("observe_type_non_string");
338
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000339 var objectInfo = objectInfoMap.get(target);
yangguo@chromium.orga6bbcc82012-12-21 12:35:02 +0000340 if (IS_UNDEFINED(objectInfo) || objectInfo.changeObservers.length === 0)
danno@chromium.org72204d52012-10-31 10:02:10 +0000341 return;
342
yangguo@chromium.orga6bbcc82012-12-21 12:35:02 +0000343 var newRecord = { object: target };
danno@chromium.org72204d52012-10-31 10:02:10 +0000344 for (var prop in changeRecord) {
yangguo@chromium.orga6bbcc82012-12-21 12:35:02 +0000345 if (prop === 'object') continue;
yangguo@chromium.orgfb377212012-11-16 14:43:43 +0000346 %DefineOrRedefineDataProperty(newRecord, prop, changeRecord[prop],
347 READ_ONLY + DONT_DELETE);
danno@chromium.org72204d52012-10-31 10:02:10 +0000348 }
jkummerow@chromium.orgc1184022013-05-28 16:58:15 +0000349 ObjectFreeze(newRecord);
danno@chromium.org72204d52012-10-31 10:02:10 +0000350
351 EnqueueChangeRecord(newRecord, objectInfo.changeObservers);
352}
353
jkummerow@chromium.org4e308cf2013-05-17 13:39:16 +0000354function ObjectNotifierPerformChange(changeType, changeFn, receiver) {
355 if (!IS_SPEC_OBJECT(this))
356 throw MakeTypeError("called_on_non_object", ["performChange"]);
357
358 var target = notifierTargetMap.get(this);
359 if (IS_UNDEFINED(target))
360 throw MakeTypeError("observe_notify_non_notifier");
361 if (!IS_STRING(changeType))
362 throw MakeTypeError("observe_perform_non_string");
363 if (!IS_SPEC_FUNCTION(changeFn))
364 throw MakeTypeError("observe_perform_non_function");
365
366 if (IS_NULL_OR_UNDEFINED(receiver)) {
367 receiver = %GetDefaultReceiver(changeFn) || receiver;
368 } else if (!IS_SPEC_OBJECT(receiver) && %IsClassicModeFunction(changeFn)) {
369 receiver = ToObject(receiver);
370 }
371
372 var objectInfo = objectInfoMap.get(target);
373 if (IS_UNDEFINED(objectInfo))
374 return;
375
376 BeginPerformChange(objectInfo, changeType);
377 try {
378 %_CallFunction(receiver, changeFn);
379 } finally {
380 EndPerformChange(objectInfo, changeType);
381 }
382}
383
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000384function ObjectGetNotifier(object) {
385 if (!IS_SPEC_OBJECT(object))
386 throw MakeTypeError("observe_non_object", ["getNotifier"]);
danno@chromium.org72204d52012-10-31 10:02:10 +0000387
yangguo@chromium.orga6bbcc82012-12-21 12:35:02 +0000388 if (ObjectIsFrozen(object)) return null;
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000389
390 var objectInfo = objectInfoMap.get(object);
yangguo@chromium.orga6bbcc82012-12-21 12:35:02 +0000391 if (IS_UNDEFINED(objectInfo)) objectInfo = CreateObjectInfo(object);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000392
393 if (IS_NULL(objectInfo.notifier)) {
yangguo@chromium.orga6bbcc82012-12-21 12:35:02 +0000394 objectInfo.notifier = { __proto__: notifierPrototype };
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000395 notifierTargetMap.set(objectInfo.notifier, object);
396 }
397
398 return objectInfo.notifier;
399}
400
401function DeliverChangeRecordsForObserver(observer) {
402 var observerInfo = observerInfoMap.get(observer);
danno@chromium.org72204d52012-10-31 10:02:10 +0000403 if (IS_UNDEFINED(observerInfo))
yangguo@chromium.orga6bbcc82012-12-21 12:35:02 +0000404 return false;
danno@chromium.org72204d52012-10-31 10:02:10 +0000405
406 var pendingChangeRecords = observerInfo.pendingChangeRecords;
407 if (IS_NULL(pendingChangeRecords))
yangguo@chromium.orga6bbcc82012-12-21 12:35:02 +0000408 return false;
danno@chromium.org72204d52012-10-31 10:02:10 +0000409
410 observerInfo.pendingChangeRecords = null;
yangguo@chromium.orgfb377212012-11-16 14:43:43 +0000411 delete observationState.pendingObservers[observerInfo.priority];
danno@chromium.org72204d52012-10-31 10:02:10 +0000412 var delivered = [];
413 %MoveArrayContents(pendingChangeRecords, delivered);
414 try {
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000415 %Call(void 0, delivered, observer);
danno@chromium.org72204d52012-10-31 10:02:10 +0000416 } catch (ex) {}
yangguo@chromium.orga6bbcc82012-12-21 12:35:02 +0000417 return true;
danno@chromium.org72204d52012-10-31 10:02:10 +0000418}
419
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000420function ObjectDeliverChangeRecords(callback) {
421 if (!IS_SPEC_FUNCTION(callback))
422 throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]);
423
yangguo@chromium.orga6bbcc82012-12-21 12:35:02 +0000424 while (DeliverChangeRecordsForObserver(callback)) {}
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000425}
426
427function DeliverChangeRecords() {
yangguo@chromium.orgfb377212012-11-16 14:43:43 +0000428 while (observationState.pendingObservers.length) {
429 var pendingObservers = observationState.pendingObservers;
430 observationState.pendingObservers = new InternalArray;
431 for (var i in pendingObservers) {
432 DeliverChangeRecordsForObserver(pendingObservers[i]);
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000433 }
434 }
435}
436
danno@chromium.org72204d52012-10-31 10:02:10 +0000437function SetupObjectObserve() {
438 %CheckIsBootstrapping();
439 InstallFunctions($Object, DONT_ENUM, $Array(
440 "deliverChangeRecords", ObjectDeliverChangeRecords,
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000441 "getNotifier", ObjectGetNotifier,
danno@chromium.org72204d52012-10-31 10:02:10 +0000442 "observe", ObjectObserve,
443 "unobserve", ObjectUnobserve
444 ));
jkummerow@chromium.org4e308cf2013-05-17 13:39:16 +0000445 InstallFunctions($Array, DONT_ENUM, $Array(
446 "observe", ArrayObserve,
447 "unobserve", ArrayUnobserve
448 ));
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000449 InstallFunctions(notifierPrototype, DONT_ENUM, $Array(
jkummerow@chromium.org4e308cf2013-05-17 13:39:16 +0000450 "notify", ObjectNotifierNotify,
451 "performChange", ObjectNotifierPerformChange
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000452 ));
danno@chromium.org72204d52012-10-31 10:02:10 +0000453}
454
mvstanton@chromium.orge4ac3ef2012-11-12 14:53:34 +0000455SetupObjectObserve();