OLD | NEW |
1 // Copyright 2012 the V8 project authors. All rights reserved. | 1 // Copyright 2012 the V8 project authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 "use strict"; | 5 "use strict"; |
6 | 6 |
7 // Overview: | 7 // Overview: |
8 // | 8 // |
9 // This file contains all of the routing and accounting for Object.observe. | 9 // This file contains all of the routing and accounting for Object.observe. |
10 // User code will interact with these mechanisms via the Object.observe APIs | 10 // User code will interact with these mechanisms via the Object.observe APIs |
(...skipping 17 matching lines...) Expand all Loading... |
28 // (i.e. not Object.deliverChangeRecords), this is the mechanism by which | 28 // (i.e. not Object.deliverChangeRecords), this is the mechanism by which |
29 // callbacks are invoked in the proper order until there are no more | 29 // callbacks are invoked in the proper order until there are no more |
30 // change records pending to a callback. | 30 // change records pending to a callback. |
31 // | 31 // |
32 // Note that in order to reduce allocation and processing costs, the | 32 // Note that in order to reduce allocation and processing costs, the |
33 // implementation of (1) and (2) have "optimized" states which represent | 33 // implementation of (1) and (2) have "optimized" states which represent |
34 // common cases which can be handled more efficiently. | 34 // common cases which can be handled more efficiently. |
35 | 35 |
36 var observationState; | 36 var observationState; |
37 | 37 |
| 38 // We have to wait until after bootstrapping to grab a reference to the |
| 39 // observationState object, since it's not possible to serialize that |
| 40 // reference into the snapshot. |
38 function GetObservationStateJS() { | 41 function GetObservationStateJS() { |
39 if (IS_UNDEFINED(observationState)) | 42 if (IS_UNDEFINED(observationState)) { |
40 observationState = %GetObservationState(); | 43 observationState = %GetObservationState(); |
| 44 } |
41 | 45 |
| 46 // TODO(adamk): Consider moving this code into heap.cc |
42 if (IS_UNDEFINED(observationState.callbackInfoMap)) { | 47 if (IS_UNDEFINED(observationState.callbackInfoMap)) { |
43 observationState.callbackInfoMap = %ObservationWeakMapCreate(); | 48 observationState.callbackInfoMap = %ObservationWeakMapCreate(); |
44 observationState.objectInfoMap = %ObservationWeakMapCreate(); | 49 observationState.objectInfoMap = %ObservationWeakMapCreate(); |
45 observationState.notifierObjectInfoMap = %ObservationWeakMapCreate(); | 50 observationState.notifierObjectInfoMap = %ObservationWeakMapCreate(); |
46 observationState.pendingObservers = null; | 51 observationState.pendingObservers = null; |
47 observationState.nextCallbackPriority = 0; | 52 observationState.nextCallbackPriority = 0; |
48 observationState.lastMicrotaskId = 0; | 53 observationState.lastMicrotaskId = 0; |
49 } | 54 } |
50 | 55 |
51 return observationState; | 56 return observationState; |
52 } | 57 } |
53 | 58 |
54 function GetWeakMapWrapper() { | |
55 function MapWrapper(map) { | |
56 this.map_ = map; | |
57 }; | |
58 | |
59 MapWrapper.prototype = { | |
60 __proto__: null, | |
61 get: function(key) { | |
62 return %WeakCollectionGet(this.map_, key); | |
63 }, | |
64 set: function(key, value) { | |
65 %WeakCollectionSet(this.map_, key, value); | |
66 }, | |
67 has: function(key) { | |
68 return !IS_UNDEFINED(this.get(key)); | |
69 } | |
70 }; | |
71 | |
72 return MapWrapper; | |
73 } | |
74 | |
75 var contextMaps; | |
76 | |
77 function GetContextMaps() { | |
78 if (IS_UNDEFINED(contextMaps)) { | |
79 var map = GetWeakMapWrapper(); | |
80 var observationState = GetObservationStateJS(); | |
81 contextMaps = { | |
82 callbackInfoMap: new map(observationState.callbackInfoMap), | |
83 objectInfoMap: new map(observationState.objectInfoMap), | |
84 notifierObjectInfoMap: new map(observationState.notifierObjectInfoMap) | |
85 }; | |
86 } | |
87 | |
88 return contextMaps; | |
89 } | |
90 | |
91 function GetCallbackInfoMap() { | |
92 return GetContextMaps().callbackInfoMap; | |
93 } | |
94 | |
95 function GetObjectInfoMap() { | |
96 return GetContextMaps().objectInfoMap; | |
97 } | |
98 | |
99 function GetNotifierObjectInfoMap() { | |
100 return GetContextMaps().notifierObjectInfoMap; | |
101 } | |
102 | |
103 function GetPendingObservers() { | 59 function GetPendingObservers() { |
104 return GetObservationStateJS().pendingObservers; | 60 return GetObservationStateJS().pendingObservers; |
105 } | 61 } |
106 | 62 |
107 function SetPendingObservers(pendingObservers) { | 63 function SetPendingObservers(pendingObservers) { |
108 GetObservationStateJS().pendingObservers = pendingObservers; | 64 GetObservationStateJS().pendingObservers = pendingObservers; |
109 } | 65 } |
110 | 66 |
111 function GetNextCallbackPriority() { | 67 function GetNextCallbackPriority() { |
112 return GetObservationStateJS().nextCallbackPriority++; | 68 return GetObservationStateJS().nextCallbackPriority++; |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
197 if (!%_IsJSProxy(object)) { | 153 if (!%_IsJSProxy(object)) { |
198 %SetIsObserved(object); | 154 %SetIsObserved(object); |
199 } | 155 } |
200 objectInfo = { | 156 objectInfo = { |
201 object: object, | 157 object: object, |
202 changeObservers: null, | 158 changeObservers: null, |
203 notifier: null, | 159 notifier: null, |
204 performing: null, | 160 performing: null, |
205 performingCount: 0, | 161 performingCount: 0, |
206 }; | 162 }; |
207 GetObjectInfoMap().set(object, objectInfo); | 163 %WeakCollectionSet(GetObservationStateJS().objectInfoMap, |
| 164 object, objectInfo); |
208 } | 165 } |
209 return objectInfo; | 166 return objectInfo; |
210 } | 167 } |
211 | 168 |
212 function ObjectInfoGet(object) { | 169 function ObjectInfoGet(object) { |
213 return GetObjectInfoMap().get(object); | 170 return %WeakCollectionGet(GetObservationStateJS().objectInfoMap, object); |
214 } | 171 } |
215 | 172 |
216 function ObjectInfoGetFromNotifier(notifier) { | 173 function ObjectInfoGetFromNotifier(notifier) { |
217 return GetNotifierObjectInfoMap().get(notifier); | 174 return %WeakCollectionGet(GetObservationStateJS().notifierObjectInfoMap, |
| 175 notifier); |
218 } | 176 } |
219 | 177 |
220 function ObjectInfoGetNotifier(objectInfo) { | 178 function ObjectInfoGetNotifier(objectInfo) { |
221 if (IS_NULL(objectInfo.notifier)) { | 179 if (IS_NULL(objectInfo.notifier)) { |
222 objectInfo.notifier = { __proto__: notifierPrototype }; | 180 objectInfo.notifier = { __proto__: notifierPrototype }; |
223 GetNotifierObjectInfoMap().set(objectInfo.notifier, objectInfo); | 181 %WeakCollectionSet(GetObservationStateJS().notifierObjectInfoMap, |
| 182 objectInfo.notifier, objectInfo); |
224 } | 183 } |
225 | 184 |
226 return objectInfo.notifier; | 185 return objectInfo.notifier; |
227 } | 186 } |
228 | 187 |
229 function ObjectInfoGetObject(objectInfo) { | |
230 return objectInfo.object; | |
231 } | |
232 | |
233 function ChangeObserversIsOptimized(changeObservers) { | 188 function ChangeObserversIsOptimized(changeObservers) { |
234 return typeof changeObservers === 'function' || | 189 return IS_SPEC_FUNCTION(changeObservers) || |
235 typeof changeObservers.callback === 'function'; | 190 IS_SPEC_FUNCTION(changeObservers.callback); |
236 } | 191 } |
237 | 192 |
238 // The set of observers on an object is called 'changeObservers'. The first | 193 // The set of observers on an object is called 'changeObservers'. The first |
239 // observer is referenced directly via objectInfo.changeObservers. When a second | 194 // observer is referenced directly via objectInfo.changeObservers. When a second |
240 // is added, changeObservers "normalizes" to become a mapping of callback | 195 // is added, changeObservers "normalizes" to become a mapping of callback |
241 // priority -> observer and is then stored on objectInfo.changeObservers. | 196 // priority -> observer and is then stored on objectInfo.changeObservers. |
242 function ObjectInfoNormalizeChangeObservers(objectInfo) { | 197 function ObjectInfoNormalizeChangeObservers(objectInfo) { |
243 if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { | 198 if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { |
244 var observer = objectInfo.changeObservers; | 199 var observer = objectInfo.changeObservers; |
245 var callback = ObserverGetCallback(observer); | 200 var callback = ObserverGetCallback(observer); |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
321 var len = ToInteger(arg.length); | 276 var len = ToInteger(arg.length); |
322 if (len < 0) len = 0; | 277 if (len < 0) len = 0; |
323 | 278 |
324 return TypeMapCreateFromList(arg, len); | 279 return TypeMapCreateFromList(arg, len); |
325 } | 280 } |
326 | 281 |
327 // CallbackInfo's optimized state is just a number which represents its global | 282 // CallbackInfo's optimized state is just a number which represents its global |
328 // priority. When a change record must be enqueued for the callback, it | 283 // priority. When a change record must be enqueued for the callback, it |
329 // normalizes. When delivery clears any pending change records, it re-optimizes. | 284 // normalizes. When delivery clears any pending change records, it re-optimizes. |
330 function CallbackInfoGet(callback) { | 285 function CallbackInfoGet(callback) { |
331 return GetCallbackInfoMap().get(callback); | 286 return %WeakCollectionGet(GetObservationStateJS().callbackInfoMap, callback); |
| 287 } |
| 288 |
| 289 function CallbackInfoSet(callback, callbackInfo) { |
| 290 %WeakCollectionSet(GetObservationStateJS().callbackInfoMap, |
| 291 callback, callbackInfo); |
332 } | 292 } |
333 | 293 |
334 function CallbackInfoGetOrCreate(callback) { | 294 function CallbackInfoGetOrCreate(callback) { |
335 var callbackInfo = GetCallbackInfoMap().get(callback); | 295 var callbackInfo = CallbackInfoGet(callback); |
336 if (!IS_UNDEFINED(callbackInfo)) | 296 if (!IS_UNDEFINED(callbackInfo)) |
337 return callbackInfo; | 297 return callbackInfo; |
338 | 298 |
339 var priority = GetNextCallbackPriority(); | 299 var priority = GetNextCallbackPriority(); |
340 GetCallbackInfoMap().set(callback, priority); | 300 CallbackInfoSet(callback, priority); |
341 return priority; | 301 return priority; |
342 } | 302 } |
343 | 303 |
344 function CallbackInfoGetPriority(callbackInfo) { | 304 function CallbackInfoGetPriority(callbackInfo) { |
345 if (IS_NUMBER(callbackInfo)) | 305 if (IS_NUMBER(callbackInfo)) |
346 return callbackInfo; | 306 return callbackInfo; |
347 else | 307 else |
348 return callbackInfo.priority; | 308 return callbackInfo.priority; |
349 } | 309 } |
350 | 310 |
351 function CallbackInfoNormalize(callback) { | 311 function CallbackInfoNormalize(callback) { |
352 var callbackInfo = GetCallbackInfoMap().get(callback); | 312 var callbackInfo = CallbackInfoGet(callback); |
353 if (IS_NUMBER(callbackInfo)) { | 313 if (IS_NUMBER(callbackInfo)) { |
354 var priority = callbackInfo; | 314 var priority = callbackInfo; |
355 callbackInfo = new InternalArray; | 315 callbackInfo = new InternalArray; |
356 callbackInfo.priority = priority; | 316 callbackInfo.priority = priority; |
357 GetCallbackInfoMap().set(callback, callbackInfo); | 317 CallbackInfoSet(callback, callbackInfo); |
358 } | 318 } |
359 return callbackInfo; | 319 return callbackInfo; |
360 } | 320 } |
361 | 321 |
362 function ObjectObserve(object, callback, acceptList) { | 322 function ObjectObserve(object, callback, acceptList) { |
363 if (!IS_SPEC_OBJECT(object)) | 323 if (!IS_SPEC_OBJECT(object)) |
364 throw MakeTypeError("observe_non_object", ["observe"]); | 324 throw MakeTypeError("observe_non_object", ["observe"]); |
365 if (%IsJSGlobalProxy(object)) | 325 if (%IsJSGlobalProxy(object)) |
366 throw MakeTypeError("observe_global_proxy", ["observe"]); | 326 throw MakeTypeError("observe_global_proxy", ["observe"]); |
367 if (!IS_SPEC_FUNCTION(callback)) | 327 if (!IS_SPEC_FUNCTION(callback)) |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
438 GetPendingObservers()[callbackInfo.priority] = callback; | 398 GetPendingObservers()[callbackInfo.priority] = callback; |
439 callbackInfo.push(changeRecord); | 399 callbackInfo.push(changeRecord); |
440 } | 400 } |
441 | 401 |
442 function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) { | 402 function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) { |
443 if (!ObjectInfoHasActiveObservers(objectInfo)) | 403 if (!ObjectInfoHasActiveObservers(objectInfo)) |
444 return; | 404 return; |
445 | 405 |
446 var hasType = !IS_UNDEFINED(type); | 406 var hasType = !IS_UNDEFINED(type); |
447 var newRecord = hasType ? | 407 var newRecord = hasType ? |
448 { object: ObjectInfoGetObject(objectInfo), type: type } : | 408 { object: objectInfo.object, type: type } : |
449 { object: ObjectInfoGetObject(objectInfo) }; | 409 { object: objectInfo.object }; |
450 | 410 |
451 for (var prop in changeRecord) { | 411 for (var prop in changeRecord) { |
452 if (prop === 'object' || (hasType && prop === 'type')) continue; | 412 if (prop === 'object' || (hasType && prop === 'type')) continue; |
453 %DefineDataPropertyUnchecked( | 413 %DefineDataPropertyUnchecked( |
454 newRecord, prop, changeRecord[prop], READ_ONLY + DONT_DELETE); | 414 newRecord, prop, changeRecord[prop], READ_ONLY + DONT_DELETE); |
455 } | 415 } |
456 ObjectFreezeJS(newRecord); | 416 ObjectFreezeJS(newRecord); |
457 | 417 |
458 ObjectInfoEnqueueInternalChangeRecord(objectInfo, newRecord); | 418 ObjectInfoEnqueueInternalChangeRecord(objectInfo, newRecord); |
459 } | 419 } |
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
587 var getNotifierFn = %GetObjectContextObjectGetNotifier(object); | 547 var getNotifierFn = %GetObjectContextObjectGetNotifier(object); |
588 return getNotifierFn(object); | 548 return getNotifierFn(object); |
589 } | 549 } |
590 | 550 |
591 function NativeObjectGetNotifier(object) { | 551 function NativeObjectGetNotifier(object) { |
592 var objectInfo = ObjectInfoGetOrCreate(object); | 552 var objectInfo = ObjectInfoGetOrCreate(object); |
593 return ObjectInfoGetNotifier(objectInfo); | 553 return ObjectInfoGetNotifier(objectInfo); |
594 } | 554 } |
595 | 555 |
596 function CallbackDeliverPending(callback) { | 556 function CallbackDeliverPending(callback) { |
597 var callbackInfo = GetCallbackInfoMap().get(callback); | 557 var callbackInfo = CallbackInfoGet(callback); |
598 if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) | 558 if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) |
599 return false; | 559 return false; |
600 | 560 |
601 // Clear the pending change records from callback and return it to its | 561 // Clear the pending change records from callback and return it to its |
602 // "optimized" state. | 562 // "optimized" state. |
603 var priority = callbackInfo.priority; | 563 var priority = callbackInfo.priority; |
604 GetCallbackInfoMap().set(callback, priority); | 564 CallbackInfoSet(callback, priority); |
605 | 565 |
606 if (GetPendingObservers()) | 566 var pendingObservers = GetPendingObservers(); |
607 delete GetPendingObservers()[priority]; | 567 if (!IS_NULL(pendingObservers)) |
| 568 delete pendingObservers[priority]; |
608 | 569 |
609 var delivered = []; | 570 var delivered = []; |
610 %MoveArrayContents(callbackInfo, delivered); | 571 %MoveArrayContents(callbackInfo, delivered); |
611 | 572 |
612 try { | 573 try { |
613 %_CallFunction(UNDEFINED, delivered, callback); | 574 %_CallFunction(UNDEFINED, delivered, callback); |
614 } catch (ex) {} // TODO(rossberg): perhaps log uncaught exceptions. | 575 } catch (ex) {} // TODO(rossberg): perhaps log uncaught exceptions. |
615 return true; | 576 return true; |
616 } | 577 } |
617 | 578 |
618 function ObjectDeliverChangeRecords(callback) { | 579 function ObjectDeliverChangeRecords(callback) { |
619 if (!IS_SPEC_FUNCTION(callback)) | 580 if (!IS_SPEC_FUNCTION(callback)) |
620 throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); | 581 throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); |
621 | 582 |
622 while (CallbackDeliverPending(callback)) {} | 583 while (CallbackDeliverPending(callback)) {} |
623 } | 584 } |
624 | 585 |
625 function ObserveMicrotaskRunner() { | 586 function ObserveMicrotaskRunner() { |
626 var pendingObservers = GetPendingObservers(); | 587 var pendingObservers = GetPendingObservers(); |
627 if (pendingObservers) { | 588 if (!IS_NULL(pendingObservers)) { |
628 SetPendingObservers(null); | 589 SetPendingObservers(null); |
629 for (var i in pendingObservers) { | 590 for (var i in pendingObservers) { |
630 CallbackDeliverPending(pendingObservers[i]); | 591 CallbackDeliverPending(pendingObservers[i]); |
631 } | 592 } |
632 } | 593 } |
633 } | 594 } |
634 | 595 |
635 function SetupObjectObserve() { | 596 function SetupObjectObserve() { |
636 %CheckIsBootstrapping(); | 597 %CheckIsBootstrapping(); |
637 InstallFunctions($Object, DONT_ENUM, $Array( | 598 InstallFunctions($Object, DONT_ENUM, $Array( |
638 "deliverChangeRecords", ObjectDeliverChangeRecords, | 599 "deliverChangeRecords", ObjectDeliverChangeRecords, |
639 "getNotifier", ObjectGetNotifier, | 600 "getNotifier", ObjectGetNotifier, |
640 "observe", ObjectObserve, | 601 "observe", ObjectObserve, |
641 "unobserve", ObjectUnobserve | 602 "unobserve", ObjectUnobserve |
642 )); | 603 )); |
643 InstallFunctions($Array, DONT_ENUM, $Array( | 604 InstallFunctions($Array, DONT_ENUM, $Array( |
644 "observe", ArrayObserve, | 605 "observe", ArrayObserve, |
645 "unobserve", ArrayUnobserve | 606 "unobserve", ArrayUnobserve |
646 )); | 607 )); |
647 InstallFunctions(notifierPrototype, DONT_ENUM, $Array( | 608 InstallFunctions(notifierPrototype, DONT_ENUM, $Array( |
648 "notify", ObjectNotifierNotify, | 609 "notify", ObjectNotifierNotify, |
649 "performChange", ObjectNotifierPerformChange | 610 "performChange", ObjectNotifierPerformChange |
650 )); | 611 )); |
651 } | 612 } |
652 | 613 |
653 SetupObjectObserve(); | 614 SetupObjectObserve(); |
OLD | NEW |