Upstream version 7.35.139.0
[platform/framework/web/crosswalk.git] / src / v8 / src / object-observe.js
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 "use strict";
29
30 // Overview:
31 //
32 // This file contains all of the routing and accounting for Object.observe.
33 // User code will interact with these mechanisms via the Object.observe APIs
34 // and, as a side effect of mutation objects which are observed. The V8 runtime
35 // (both C++ and JS) will interact with these mechanisms primarily by enqueuing
36 // proper change records for objects which were mutated. The Object.observe
37 // routing and accounting consists primarily of three participants
38 //
39 // 1) ObjectInfo. This represents the observed state of a given object. It
40 //    records what callbacks are observing the object, with what options, and
41 //    what "change types" are in progress on the object (i.e. via
42 //    notifier.performChange).
43 //
44 // 2) CallbackInfo. This represents a callback used for observation. It holds
45 //    the records which must be delivered to the callback, as well as the global
46 //    priority of the callback (which determines delivery order between
47 //    callbacks).
48 //
49 // 3) observationState.pendingObservers. This is the set of observers which
50 //    have change records which must be delivered. During "normal" delivery
51 //    (i.e. not Object.deliverChangeRecords), this is the mechanism by which
52 //    callbacks are invoked in the proper order until there are no more
53 //    change records pending to a callback.
54 //
55 // Note that in order to reduce allocation and processing costs, the
56 // implementation of (1) and (2) have "optimized" states which represent
57 // common cases which can be handled more efficiently.
58
59 var observationState;
60
61 function GetObservationState() {
62   if (IS_UNDEFINED(observationState))
63     observationState = %GetObservationState();
64
65   if (IS_UNDEFINED(observationState.callbackInfoMap)) {
66     observationState.callbackInfoMap = %ObservationWeakMapCreate();
67     observationState.objectInfoMap = %ObservationWeakMapCreate();
68     observationState.notifierObjectInfoMap = %ObservationWeakMapCreate();
69     observationState.pendingObservers = null;
70     observationState.nextCallbackPriority = 0;
71   }
72
73   return observationState;
74 }
75
76 function GetWeakMapWrapper() {
77   function MapWrapper(map) {
78     this.map_ = map;
79   };
80
81   MapWrapper.prototype = {
82     get: function(key) {
83       key = %UnwrapGlobalProxy(key);
84       if (!IS_SPEC_OBJECT(key)) return UNDEFINED;
85       return %WeakCollectionGet(this.map_, key);
86     },
87     set: function(key, value) {
88       key = %UnwrapGlobalProxy(key);
89       if (!IS_SPEC_OBJECT(key)) return UNDEFINED;
90       %WeakCollectionSet(this.map_, key, value);
91     },
92     has: function(key) {
93       return !IS_UNDEFINED(this.get(key));
94     }
95   };
96
97   return MapWrapper;
98 }
99
100 var contextMaps;
101
102 function GetContextMaps() {
103   if (IS_UNDEFINED(contextMaps)) {
104     var map = GetWeakMapWrapper();
105     var observationState = GetObservationState();
106     contextMaps = {
107       callbackInfoMap: new map(observationState.callbackInfoMap),
108       objectInfoMap: new map(observationState.objectInfoMap),
109       notifierObjectInfoMap: new map(observationState.notifierObjectInfoMap)
110     };
111   }
112
113   return contextMaps;
114 }
115
116 function GetCallbackInfoMap() {
117   return GetContextMaps().callbackInfoMap;
118 }
119
120 function GetObjectInfoMap() {
121   return GetContextMaps().objectInfoMap;
122 }
123
124 function GetNotifierObjectInfoMap() {
125   return GetContextMaps().notifierObjectInfoMap;
126 }
127
128 function GetPendingObservers() {
129   return GetObservationState().pendingObservers;
130 }
131
132 function SetPendingObservers(pendingObservers) {
133   GetObservationState().pendingObservers = pendingObservers;
134 }
135
136 function GetNextCallbackPriority() {
137   return GetObservationState().nextCallbackPriority++;
138 }
139
140 function nullProtoObject() {
141   return { __proto__: null };
142 }
143
144 function TypeMapCreate() {
145   return nullProtoObject();
146 }
147
148 function TypeMapAddType(typeMap, type, ignoreDuplicate) {
149   typeMap[type] = ignoreDuplicate ? 1 : (typeMap[type] || 0) + 1;
150 }
151
152 function TypeMapRemoveType(typeMap, type) {
153   typeMap[type]--;
154 }
155
156 function TypeMapCreateFromList(typeList) {
157   var typeMap = TypeMapCreate();
158   for (var i = 0; i < typeList.length; i++) {
159     TypeMapAddType(typeMap, typeList[i], true);
160   }
161   return typeMap;
162 }
163
164 function TypeMapHasType(typeMap, type) {
165   return !!typeMap[type];
166 }
167
168 function TypeMapIsDisjointFrom(typeMap1, typeMap2) {
169   if (!typeMap1 || !typeMap2)
170     return true;
171
172   for (var type in typeMap1) {
173     if (TypeMapHasType(typeMap1, type) && TypeMapHasType(typeMap2, type))
174       return false;
175   }
176
177   return true;
178 }
179
180 var defaultAcceptTypes = TypeMapCreateFromList([
181   'add',
182   'update',
183   'delete',
184   'setPrototype',
185   'reconfigure',
186   'preventExtensions'
187 ]);
188
189 // An Observer is a registration to observe an object by a callback with
190 // a given set of accept types. If the set of accept types is the default
191 // set for Object.observe, the observer is represented as a direct reference
192 // to the callback. An observer never changes its accept types and thus never
193 // needs to "normalize".
194 function ObserverCreate(callback, acceptList) {
195   if (IS_UNDEFINED(acceptList))
196     return callback;
197   var observer = nullProtoObject();
198   observer.callback = callback;
199   observer.accept = TypeMapCreateFromList(acceptList);
200   return observer;
201 }
202
203 function ObserverGetCallback(observer) {
204   return IS_SPEC_FUNCTION(observer) ? observer : observer.callback;
205 }
206
207 function ObserverGetAcceptTypes(observer) {
208   return IS_SPEC_FUNCTION(observer) ? defaultAcceptTypes : observer.accept;
209 }
210
211 function ObserverIsActive(observer, objectInfo) {
212   return TypeMapIsDisjointFrom(ObjectInfoGetPerformingTypes(objectInfo),
213                                ObserverGetAcceptTypes(observer));
214 }
215
216 function ObjectInfoGetOrCreate(object) {
217   var objectInfo = ObjectInfoGet(object);
218   if (IS_UNDEFINED(objectInfo)) {
219     if (!%IsJSProxy(object))
220       %SetIsObserved(object);
221
222     objectInfo = {
223       object: object,
224       changeObservers: null,
225       notifier: null,
226       performing: null,
227       performingCount: 0,
228     };
229     GetObjectInfoMap().set(object, objectInfo);
230   }
231   return objectInfo;
232 }
233
234 function ObjectInfoGet(object) {
235   return GetObjectInfoMap().get(object);
236 }
237
238 function ObjectInfoGetFromNotifier(notifier) {
239   return GetNotifierObjectInfoMap().get(notifier);
240 }
241
242 function ObjectInfoGetNotifier(objectInfo) {
243   if (IS_NULL(objectInfo.notifier)) {
244     objectInfo.notifier = { __proto__: notifierPrototype };
245     GetNotifierObjectInfoMap().set(objectInfo.notifier, objectInfo);
246   }
247
248   return objectInfo.notifier;
249 }
250
251 function ObjectInfoGetObject(objectInfo) {
252   return objectInfo.object;
253 }
254
255 function ChangeObserversIsOptimized(changeObservers) {
256   return typeof changeObservers === 'function' ||
257          typeof changeObservers.callback === 'function';
258 }
259
260 // The set of observers on an object is called 'changeObservers'. The first
261 // observer is referenced directly via objectInfo.changeObservers. When a second
262 // is added, changeObservers "normalizes" to become a mapping of callback
263 // priority -> observer and is then stored on objectInfo.changeObservers.
264 function ObjectInfoNormalizeChangeObservers(objectInfo) {
265   if (ChangeObserversIsOptimized(objectInfo.changeObservers)) {
266     var observer = objectInfo.changeObservers;
267     var callback = ObserverGetCallback(observer);
268     var callbackInfo = CallbackInfoGet(callback);
269     var priority = CallbackInfoGetPriority(callbackInfo);
270     objectInfo.changeObservers = nullProtoObject();
271     objectInfo.changeObservers[priority] = observer;
272   }
273 }
274
275 function ObjectInfoAddObserver(objectInfo, callback, acceptList) {
276   var callbackInfo = CallbackInfoGetOrCreate(callback);
277   var observer = ObserverCreate(callback, acceptList);
278
279   if (!objectInfo.changeObservers) {
280     objectInfo.changeObservers = observer;
281     return;
282   }
283
284   ObjectInfoNormalizeChangeObservers(objectInfo);
285   var priority = CallbackInfoGetPriority(callbackInfo);
286   objectInfo.changeObservers[priority] = observer;
287 }
288
289 function ObjectInfoRemoveObserver(objectInfo, callback) {
290   if (!objectInfo.changeObservers)
291     return;
292
293   if (ChangeObserversIsOptimized(objectInfo.changeObservers)) {
294     if (callback === ObserverGetCallback(objectInfo.changeObservers))
295       objectInfo.changeObservers = null;
296     return;
297   }
298
299   var callbackInfo = CallbackInfoGet(callback);
300   var priority = CallbackInfoGetPriority(callbackInfo);
301   objectInfo.changeObservers[priority] = null;
302 }
303
304 function ObjectInfoHasActiveObservers(objectInfo) {
305   if (IS_UNDEFINED(objectInfo) || !objectInfo.changeObservers)
306     return false;
307
308   if (ChangeObserversIsOptimized(objectInfo.changeObservers))
309     return ObserverIsActive(objectInfo.changeObservers, objectInfo);
310
311   for (var priority in objectInfo.changeObservers) {
312     var observer = objectInfo.changeObservers[priority];
313     if (!IS_NULL(observer) && ObserverIsActive(observer, objectInfo))
314       return true;
315   }
316
317   return false;
318 }
319
320 function ObjectInfoAddPerformingType(objectInfo, type) {
321   objectInfo.performing = objectInfo.performing || TypeMapCreate();
322   TypeMapAddType(objectInfo.performing, type);
323   objectInfo.performingCount++;
324 }
325
326 function ObjectInfoRemovePerformingType(objectInfo, type) {
327   objectInfo.performingCount--;
328   TypeMapRemoveType(objectInfo.performing, type);
329 }
330
331 function ObjectInfoGetPerformingTypes(objectInfo) {
332   return objectInfo.performingCount > 0 ? objectInfo.performing : null;
333 }
334
335 function AcceptArgIsValid(arg) {
336   if (IS_UNDEFINED(arg))
337     return true;
338
339   if (!IS_SPEC_OBJECT(arg) ||
340       !IS_NUMBER(arg.length) ||
341       arg.length < 0)
342     return false;
343
344   return true;
345 }
346
347 // CallbackInfo's optimized state is just a number which represents its global
348 // priority. When a change record must be enqueued for the callback, it
349 // normalizes. When delivery clears any pending change records, it re-optimizes.
350 function CallbackInfoGet(callback) {
351   return GetCallbackInfoMap().get(callback);
352 }
353
354 function CallbackInfoGetOrCreate(callback) {
355   var callbackInfo = GetCallbackInfoMap().get(callback);
356   if (!IS_UNDEFINED(callbackInfo))
357     return callbackInfo;
358
359   var priority =  GetNextCallbackPriority();
360   GetCallbackInfoMap().set(callback, priority);
361   return priority;
362 }
363
364 function CallbackInfoGetPriority(callbackInfo) {
365   if (IS_NUMBER(callbackInfo))
366     return callbackInfo;
367   else
368     return callbackInfo.priority;
369 }
370
371 function CallbackInfoNormalize(callback) {
372   var callbackInfo = GetCallbackInfoMap().get(callback);
373   if (IS_NUMBER(callbackInfo)) {
374     var priority = callbackInfo;
375     callbackInfo = new InternalArray;
376     callbackInfo.priority = priority;
377     GetCallbackInfoMap().set(callback, callbackInfo);
378   }
379   return callbackInfo;
380 }
381
382 function ObjectObserve(object, callback, acceptList) {
383   if (!IS_SPEC_OBJECT(object))
384     throw MakeTypeError("observe_non_object", ["observe"]);
385   if (!IS_SPEC_FUNCTION(callback))
386     throw MakeTypeError("observe_non_function", ["observe"]);
387   if (ObjectIsFrozen(callback))
388     throw MakeTypeError("observe_callback_frozen");
389   if (!AcceptArgIsValid(acceptList))
390     throw MakeTypeError("observe_accept_invalid");
391
392   var objectInfo = ObjectInfoGetOrCreate(object);
393   ObjectInfoAddObserver(objectInfo, callback, acceptList);
394   return object;
395 }
396
397 function ObjectUnobserve(object, callback) {
398   if (!IS_SPEC_OBJECT(object))
399     throw MakeTypeError("observe_non_object", ["unobserve"]);
400   if (!IS_SPEC_FUNCTION(callback))
401     throw MakeTypeError("observe_non_function", ["unobserve"]);
402
403   var objectInfo = ObjectInfoGet(object);
404   if (IS_UNDEFINED(objectInfo))
405     return object;
406
407   ObjectInfoRemoveObserver(objectInfo, callback);
408   return object;
409 }
410
411 function ArrayObserve(object, callback) {
412   return ObjectObserve(object, callback, ['add',
413                                           'update',
414                                           'delete',
415                                           'splice']);
416 }
417
418 function ArrayUnobserve(object, callback) {
419   return ObjectUnobserve(object, callback);
420 }
421
422 function ObserverEnqueueIfActive(observer, objectInfo, changeRecord,
423                                  needsAccessCheck) {
424   if (!ObserverIsActive(observer, objectInfo) ||
425       !TypeMapHasType(ObserverGetAcceptTypes(observer), changeRecord.type)) {
426     return;
427   }
428
429   var callback = ObserverGetCallback(observer);
430   if (needsAccessCheck &&
431       // Drop all splice records on the floor for access-checked objects
432       (changeRecord.type == 'splice' ||
433        !%IsAccessAllowedForObserver(
434            callback, changeRecord.object, changeRecord.name))) {
435     return;
436   }
437
438   var callbackInfo = CallbackInfoNormalize(callback);
439   if (IS_NULL(GetPendingObservers())) {
440     SetPendingObservers(nullProtoObject())
441     GetMicrotaskQueue().push(ObserveMicrotaskRunner);
442     %SetMicrotaskPending(true);
443   }
444   GetPendingObservers()[callbackInfo.priority] = callback;
445   callbackInfo.push(changeRecord);
446 }
447
448 function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) {
449   if (!ObjectInfoHasActiveObservers(objectInfo))
450     return;
451
452   var hasType = !IS_UNDEFINED(type);
453   var newRecord = hasType ?
454       { object: ObjectInfoGetObject(objectInfo), type: type } :
455       { object: ObjectInfoGetObject(objectInfo) };
456
457   for (var prop in changeRecord) {
458     if (prop === 'object' || (hasType && prop === 'type')) continue;
459     %DefineOrRedefineDataProperty(newRecord, prop, changeRecord[prop],
460         READ_ONLY + DONT_DELETE);
461   }
462   ObjectFreeze(newRecord);
463
464   ObjectInfoEnqueueInternalChangeRecord(objectInfo, newRecord,
465                                         true /* skip access check */);
466 }
467
468 function ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord,
469                                                skipAccessCheck) {
470   // TODO(rossberg): adjust once there is a story for symbols vs proxies.
471   if (IS_SYMBOL(changeRecord.name)) return;
472
473   var needsAccessCheck = !skipAccessCheck &&
474       %IsAccessCheckNeeded(changeRecord.object);
475
476   if (ChangeObserversIsOptimized(objectInfo.changeObservers)) {
477     var observer = objectInfo.changeObservers;
478     ObserverEnqueueIfActive(observer, objectInfo, changeRecord,
479                             needsAccessCheck);
480     return;
481   }
482
483   for (var priority in objectInfo.changeObservers) {
484     var observer = objectInfo.changeObservers[priority];
485     if (IS_NULL(observer))
486       continue;
487     ObserverEnqueueIfActive(observer, objectInfo, changeRecord,
488                             needsAccessCheck);
489   }
490 }
491
492 function BeginPerformSplice(array) {
493   var objectInfo = ObjectInfoGet(array);
494   if (!IS_UNDEFINED(objectInfo))
495     ObjectInfoAddPerformingType(objectInfo, 'splice');
496 }
497
498 function EndPerformSplice(array) {
499   var objectInfo = ObjectInfoGet(array);
500   if (!IS_UNDEFINED(objectInfo))
501     ObjectInfoRemovePerformingType(objectInfo, 'splice');
502 }
503
504 function EnqueueSpliceRecord(array, index, removed, addedCount) {
505   var objectInfo = ObjectInfoGet(array);
506   if (!ObjectInfoHasActiveObservers(objectInfo))
507     return;
508
509   var changeRecord = {
510     type: 'splice',
511     object: array,
512     index: index,
513     removed: removed,
514     addedCount: addedCount
515   };
516
517   ObjectFreeze(changeRecord);
518   ObjectFreeze(changeRecord.removed);
519   ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord);
520 }
521
522 function NotifyChange(type, object, name, oldValue) {
523   var objectInfo = ObjectInfoGet(object);
524   if (!ObjectInfoHasActiveObservers(objectInfo))
525     return;
526
527   var changeRecord;
528   if (arguments.length == 2) {
529     changeRecord = { type: type, object: object };
530   } else if (arguments.length == 3) {
531     changeRecord = { type: type, object: object, name: name };
532   } else {
533     changeRecord = {
534       type: type,
535       object: object,
536       name: name,
537       oldValue: oldValue
538     };
539   }
540
541   ObjectFreeze(changeRecord);
542   ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord);
543 }
544
545 var notifierPrototype = {};
546
547 function ObjectNotifierNotify(changeRecord) {
548   if (!IS_SPEC_OBJECT(this))
549     throw MakeTypeError("called_on_non_object", ["notify"]);
550
551   var objectInfo = ObjectInfoGetFromNotifier(this);
552   if (IS_UNDEFINED(objectInfo))
553     throw MakeTypeError("observe_notify_non_notifier");
554   if (!IS_STRING(changeRecord.type))
555     throw MakeTypeError("observe_type_non_string");
556
557   ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord);
558 }
559
560 function ObjectNotifierPerformChange(changeType, changeFn) {
561   if (!IS_SPEC_OBJECT(this))
562     throw MakeTypeError("called_on_non_object", ["performChange"]);
563
564   var objectInfo = ObjectInfoGetFromNotifier(this);
565
566   if (IS_UNDEFINED(objectInfo))
567     throw MakeTypeError("observe_notify_non_notifier");
568   if (!IS_STRING(changeType))
569     throw MakeTypeError("observe_perform_non_string");
570   if (!IS_SPEC_FUNCTION(changeFn))
571     throw MakeTypeError("observe_perform_non_function");
572
573   ObjectInfoAddPerformingType(objectInfo, changeType);
574
575   var changeRecord;
576   try {
577     changeRecord = %_CallFunction(UNDEFINED, changeFn);
578   } finally {
579     ObjectInfoRemovePerformingType(objectInfo, changeType);
580   }
581
582   if (IS_SPEC_OBJECT(changeRecord))
583     ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, changeType);
584 }
585
586 function ObjectGetNotifier(object) {
587   if (!IS_SPEC_OBJECT(object))
588     throw MakeTypeError("observe_non_object", ["getNotifier"]);
589
590   if (ObjectIsFrozen(object)) return null;
591
592   var objectInfo = ObjectInfoGetOrCreate(object);
593   return ObjectInfoGetNotifier(objectInfo);
594 }
595
596 function CallbackDeliverPending(callback) {
597   var callbackInfo = GetCallbackInfoMap().get(callback);
598   if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo))
599     return false;
600
601   // Clear the pending change records from callback and return it to its
602   // "optimized" state.
603   var priority = callbackInfo.priority;
604   GetCallbackInfoMap().set(callback, priority);
605
606   if (GetPendingObservers())
607     delete GetPendingObservers()[priority];
608
609   var delivered = [];
610   %MoveArrayContents(callbackInfo, delivered);
611
612   try {
613     %_CallFunction(UNDEFINED, delivered, callback);
614   } catch (ex) {}  // TODO(rossberg): perhaps log uncaught exceptions.
615   return true;
616 }
617
618 function ObjectDeliverChangeRecords(callback) {
619   if (!IS_SPEC_FUNCTION(callback))
620     throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]);
621
622   while (CallbackDeliverPending(callback)) {}
623 }
624
625 function ObserveMicrotaskRunner() {
626   var pendingObservers = GetPendingObservers();
627   if (pendingObservers) {
628     SetPendingObservers(null);
629     for (var i in pendingObservers) {
630       CallbackDeliverPending(pendingObservers[i]);
631     }
632   }
633 }
634
635 function SetupObjectObserve() {
636   %CheckIsBootstrapping();
637   InstallFunctions($Object, DONT_ENUM, $Array(
638     "deliverChangeRecords", ObjectDeliverChangeRecords,
639     "getNotifier", ObjectGetNotifier,
640     "observe", ObjectObserve,
641     "unobserve", ObjectUnobserve
642   ));
643   InstallFunctions($Array, DONT_ENUM, $Array(
644     "observe", ArrayObserve,
645     "unobserve", ArrayUnobserve
646   ));
647   InstallFunctions(notifierPrototype, DONT_ENUM, $Array(
648     "notify", ObjectNotifierNotify,
649     "performChange", ObjectNotifierPerformChange
650   ));
651 }
652
653 // Disable Object.observe API for M35.
654 // SetupObjectObserve();