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 15 matching lines...) Expand all Loading... |
26 // 3) observationState.pendingObservers. This is the set of observers which | 26 // 3) observationState.pendingObservers. This is the set of observers which |
27 // have change records which must be delivered. During "normal" delivery | 27 // have change records which must be delivered. During "normal" delivery |
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 = %GetObservationState(); |
37 | 37 |
38 function GetObservationStateJS() { | 38 // This is run during the first context creation in an isolate. |
39 if (IS_UNDEFINED(observationState)) | 39 if (IS_UNDEFINED(observationState.callbackInfoMap)) { |
40 observationState = %GetObservationState(); | 40 observationState.callbackInfoMap = %ObservationWeakMapCreate(); |
41 | 41 observationState.objectInfoMap = %ObservationWeakMapCreate(); |
42 if (IS_UNDEFINED(observationState.callbackInfoMap)) { | 42 observationState.notifierObjectInfoMap = %ObservationWeakMapCreate(); |
43 observationState.callbackInfoMap = %ObservationWeakMapCreate(); | 43 observationState.pendingObservers = null; |
44 observationState.objectInfoMap = %ObservationWeakMapCreate(); | 44 observationState.nextCallbackPriority = 0; |
45 observationState.notifierObjectInfoMap = %ObservationWeakMapCreate(); | 45 observationState.lastMicrotaskId = 0; |
46 observationState.pendingObservers = null; | |
47 observationState.nextCallbackPriority = 0; | |
48 observationState.lastMicrotaskId = 0; | |
49 } | |
50 | |
51 return observationState; | |
52 } | |
53 | |
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 } | 46 } |
102 | 47 |
103 function GetPendingObservers() { | 48 function GetPendingObservers() { |
104 return GetObservationStateJS().pendingObservers; | 49 return observationState.pendingObservers; |
105 } | 50 } |
106 | 51 |
107 function SetPendingObservers(pendingObservers) { | 52 function SetPendingObservers(pendingObservers) { |
108 GetObservationStateJS().pendingObservers = pendingObservers; | 53 observationState.pendingObservers = pendingObservers; |
109 } | 54 } |
110 | 55 |
111 function GetNextCallbackPriority() { | 56 function GetNextCallbackPriority() { |
112 return GetObservationStateJS().nextCallbackPriority++; | 57 return observationState.nextCallbackPriority++; |
113 } | 58 } |
114 | 59 |
115 function nullProtoObject() { | 60 function nullProtoObject() { |
116 return { __proto__: null }; | 61 return { __proto__: null }; |
117 } | 62 } |
118 | 63 |
119 function TypeMapCreate() { | 64 function TypeMapCreate() { |
120 return nullProtoObject(); | 65 return nullProtoObject(); |
121 } | 66 } |
122 | 67 |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
197 if (!%_IsJSProxy(object)) { | 142 if (!%_IsJSProxy(object)) { |
198 %SetIsObserved(object); | 143 %SetIsObserved(object); |
199 } | 144 } |
200 objectInfo = { | 145 objectInfo = { |
201 object: object, | 146 object: object, |
202 changeObservers: null, | 147 changeObservers: null, |
203 notifier: null, | 148 notifier: null, |
204 performing: null, | 149 performing: null, |
205 performingCount: 0, | 150 performingCount: 0, |
206 }; | 151 }; |
207 GetObjectInfoMap().set(object, objectInfo); | 152 %WeakCollectionSet(observationState.objectInfoMap, object, objectInfo); |
208 } | 153 } |
209 return objectInfo; | 154 return objectInfo; |
210 } | 155 } |
211 | 156 |
212 function ObjectInfoGet(object) { | 157 function ObjectInfoGet(object) { |
213 return GetObjectInfoMap().get(object); | 158 return %WeakCollectionGet(observationState.objectInfoMap, object); |
214 } | 159 } |
215 | 160 |
216 function ObjectInfoGetFromNotifier(notifier) { | 161 function ObjectInfoGetFromNotifier(notifier) { |
217 return GetNotifierObjectInfoMap().get(notifier); | 162 return %WeakCollectionGet(observationState.notifierObjectInfoMap, notifier); |
218 } | 163 } |
219 | 164 |
220 function ObjectInfoGetNotifier(objectInfo) { | 165 function ObjectInfoGetNotifier(objectInfo) { |
221 if (IS_NULL(objectInfo.notifier)) { | 166 if (IS_NULL(objectInfo.notifier)) { |
222 objectInfo.notifier = { __proto__: notifierPrototype }; | 167 objectInfo.notifier = { __proto__: notifierPrototype }; |
223 GetNotifierObjectInfoMap().set(objectInfo.notifier, objectInfo); | 168 %WeakCollectionSet(observationState.notifierObjectInfoMap, |
| 169 objectInfo.notifier, objectInfo); |
224 } | 170 } |
225 | 171 |
226 return objectInfo.notifier; | 172 return objectInfo.notifier; |
227 } | 173 } |
228 | 174 |
229 function ObjectInfoGetObject(objectInfo) { | |
230 return objectInfo.object; | |
231 } | |
232 | |
233 function ChangeObserversIsOptimized(changeObservers) { | 175 function ChangeObserversIsOptimized(changeObservers) { |
234 return typeof changeObservers === 'function' || | 176 return IS_SPEC_FUNCTION(changeObservers) || |
235 typeof changeObservers.callback === 'function'; | 177 IS_SPEC_FUNCTION(changeObservers.callback); |
236 } | 178 } |
237 | 179 |
238 // The set of observers on an object is called 'changeObservers'. The first | 180 // The set of observers on an object is called 'changeObservers'. The first |
239 // observer is referenced directly via objectInfo.changeObservers. When a second | 181 // observer is referenced directly via objectInfo.changeObservers. When a second |
240 // is added, changeObservers "normalizes" to become a mapping of callback | 182 // is added, changeObservers "normalizes" to become a mapping of callback |
241 // priority -> observer and is then stored on objectInfo.changeObservers. | 183 // priority -> observer and is then stored on objectInfo.changeObservers. |
242 function ObjectInfoNormalizeChangeObservers(objectInfo) { | 184 function ObjectInfoNormalizeChangeObservers(objectInfo) { |
243 if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { | 185 if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { |
244 var observer = objectInfo.changeObservers; | 186 var observer = objectInfo.changeObservers; |
245 var callback = ObserverGetCallback(observer); | 187 var callback = ObserverGetCallback(observer); |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
321 var len = ToInteger(arg.length); | 263 var len = ToInteger(arg.length); |
322 if (len < 0) len = 0; | 264 if (len < 0) len = 0; |
323 | 265 |
324 return TypeMapCreateFromList(arg, len); | 266 return TypeMapCreateFromList(arg, len); |
325 } | 267 } |
326 | 268 |
327 // CallbackInfo's optimized state is just a number which represents its global | 269 // 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 | 270 // priority. When a change record must be enqueued for the callback, it |
329 // normalizes. When delivery clears any pending change records, it re-optimizes. | 271 // normalizes. When delivery clears any pending change records, it re-optimizes. |
330 function CallbackInfoGet(callback) { | 272 function CallbackInfoGet(callback) { |
331 return GetCallbackInfoMap().get(callback); | 273 return %WeakCollectionGet(observationState.callbackInfoMap, callback); |
| 274 } |
| 275 |
| 276 function CallbackInfoSet(callback, callbackInfo) { |
| 277 %WeakCollectionSet(observationState.callbackInfoMap, callback, callbackInfo); |
332 } | 278 } |
333 | 279 |
334 function CallbackInfoGetOrCreate(callback) { | 280 function CallbackInfoGetOrCreate(callback) { |
335 var callbackInfo = GetCallbackInfoMap().get(callback); | 281 var callbackInfo = CallbackInfoGet(callback); |
336 if (!IS_UNDEFINED(callbackInfo)) | 282 if (!IS_UNDEFINED(callbackInfo)) |
337 return callbackInfo; | 283 return callbackInfo; |
338 | 284 |
339 var priority = GetNextCallbackPriority(); | 285 var priority = GetNextCallbackPriority(); |
340 GetCallbackInfoMap().set(callback, priority); | 286 CallbackInfoSet(callback, priority); |
341 return priority; | 287 return priority; |
342 } | 288 } |
343 | 289 |
344 function CallbackInfoGetPriority(callbackInfo) { | 290 function CallbackInfoGetPriority(callbackInfo) { |
345 if (IS_NUMBER(callbackInfo)) | 291 if (IS_NUMBER(callbackInfo)) |
346 return callbackInfo; | 292 return callbackInfo; |
347 else | 293 else |
348 return callbackInfo.priority; | 294 return callbackInfo.priority; |
349 } | 295 } |
350 | 296 |
351 function CallbackInfoNormalize(callback) { | 297 function CallbackInfoNormalize(callback) { |
352 var callbackInfo = GetCallbackInfoMap().get(callback); | 298 var callbackInfo = CallbackInfoGet(callback); |
353 if (IS_NUMBER(callbackInfo)) { | 299 if (IS_NUMBER(callbackInfo)) { |
354 var priority = callbackInfo; | 300 var priority = callbackInfo; |
355 callbackInfo = new InternalArray; | 301 callbackInfo = new InternalArray; |
356 callbackInfo.priority = priority; | 302 callbackInfo.priority = priority; |
357 GetCallbackInfoMap().set(callback, callbackInfo); | 303 CallbackInfoSet(callback, callbackInfo); |
358 } | 304 } |
359 return callbackInfo; | 305 return callbackInfo; |
360 } | 306 } |
361 | 307 |
362 function ObjectObserve(object, callback, acceptList) { | 308 function ObjectObserve(object, callback, acceptList) { |
363 if (!IS_SPEC_OBJECT(object)) | 309 if (!IS_SPEC_OBJECT(object)) |
364 throw MakeTypeError("observe_non_object", ["observe"]); | 310 throw MakeTypeError("observe_non_object", ["observe"]); |
365 if (%IsJSGlobalProxy(object)) | 311 if (%IsJSGlobalProxy(object)) |
366 throw MakeTypeError("observe_global_proxy", ["observe"]); | 312 throw MakeTypeError("observe_global_proxy", ["observe"]); |
367 if (!IS_SPEC_FUNCTION(callback)) | 313 if (!IS_SPEC_FUNCTION(callback)) |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
416 var callback = ObserverGetCallback(observer); | 362 var callback = ObserverGetCallback(observer); |
417 if (!%ObserverObjectAndRecordHaveSameOrigin(callback, changeRecord.object, | 363 if (!%ObserverObjectAndRecordHaveSameOrigin(callback, changeRecord.object, |
418 changeRecord)) { | 364 changeRecord)) { |
419 return; | 365 return; |
420 } | 366 } |
421 | 367 |
422 var callbackInfo = CallbackInfoNormalize(callback); | 368 var callbackInfo = CallbackInfoNormalize(callback); |
423 if (IS_NULL(GetPendingObservers())) { | 369 if (IS_NULL(GetPendingObservers())) { |
424 SetPendingObservers(nullProtoObject()); | 370 SetPendingObservers(nullProtoObject()); |
425 if (DEBUG_IS_ACTIVE) { | 371 if (DEBUG_IS_ACTIVE) { |
426 var id = ++GetObservationStateJS().lastMicrotaskId; | 372 var id = ++observationState.lastMicrotaskId; |
427 var name = "Object.observe"; | 373 var name = "Object.observe"; |
428 %EnqueueMicrotask(function() { | 374 %EnqueueMicrotask(function() { |
429 %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name }); | 375 %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name }); |
430 ObserveMicrotaskRunner(); | 376 ObserveMicrotaskRunner(); |
431 %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name }); | 377 %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name }); |
432 }); | 378 }); |
433 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name }); | 379 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name }); |
434 } else { | 380 } else { |
435 %EnqueueMicrotask(ObserveMicrotaskRunner); | 381 %EnqueueMicrotask(ObserveMicrotaskRunner); |
436 } | 382 } |
437 } | 383 } |
438 GetPendingObservers()[callbackInfo.priority] = callback; | 384 GetPendingObservers()[callbackInfo.priority] = callback; |
439 callbackInfo.push(changeRecord); | 385 callbackInfo.push(changeRecord); |
440 } | 386 } |
441 | 387 |
442 function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) { | 388 function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) { |
443 if (!ObjectInfoHasActiveObservers(objectInfo)) | 389 if (!ObjectInfoHasActiveObservers(objectInfo)) |
444 return; | 390 return; |
445 | 391 |
446 var hasType = !IS_UNDEFINED(type); | 392 var hasType = !IS_UNDEFINED(type); |
447 var newRecord = hasType ? | 393 var newRecord = hasType ? |
448 { object: ObjectInfoGetObject(objectInfo), type: type } : | 394 { object: objectInfo.object, type: type } : |
449 { object: ObjectInfoGetObject(objectInfo) }; | 395 { object: objectInfo.object }; |
450 | 396 |
451 for (var prop in changeRecord) { | 397 for (var prop in changeRecord) { |
452 if (prop === 'object' || (hasType && prop === 'type')) continue; | 398 if (prop === 'object' || (hasType && prop === 'type')) continue; |
453 %DefineDataPropertyUnchecked( | 399 %DefineDataPropertyUnchecked( |
454 newRecord, prop, changeRecord[prop], READ_ONLY + DONT_DELETE); | 400 newRecord, prop, changeRecord[prop], READ_ONLY + DONT_DELETE); |
455 } | 401 } |
456 ObjectFreezeJS(newRecord); | 402 ObjectFreezeJS(newRecord); |
457 | 403 |
458 ObjectInfoEnqueueInternalChangeRecord(objectInfo, newRecord); | 404 ObjectInfoEnqueueInternalChangeRecord(objectInfo, newRecord); |
459 } | 405 } |
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
587 var getNotifierFn = %GetObjectContextObjectGetNotifier(object); | 533 var getNotifierFn = %GetObjectContextObjectGetNotifier(object); |
588 return getNotifierFn(object); | 534 return getNotifierFn(object); |
589 } | 535 } |
590 | 536 |
591 function NativeObjectGetNotifier(object) { | 537 function NativeObjectGetNotifier(object) { |
592 var objectInfo = ObjectInfoGetOrCreate(object); | 538 var objectInfo = ObjectInfoGetOrCreate(object); |
593 return ObjectInfoGetNotifier(objectInfo); | 539 return ObjectInfoGetNotifier(objectInfo); |
594 } | 540 } |
595 | 541 |
596 function CallbackDeliverPending(callback) { | 542 function CallbackDeliverPending(callback) { |
597 var callbackInfo = GetCallbackInfoMap().get(callback); | 543 var callbackInfo = CallbackInfoGet(callback); |
598 if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) | 544 if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) |
599 return false; | 545 return false; |
600 | 546 |
601 // Clear the pending change records from callback and return it to its | 547 // Clear the pending change records from callback and return it to its |
602 // "optimized" state. | 548 // "optimized" state. |
603 var priority = callbackInfo.priority; | 549 var priority = callbackInfo.priority; |
604 GetCallbackInfoMap().set(callback, priority); | 550 CallbackInfoSet(callback, priority); |
605 | 551 |
606 if (GetPendingObservers()) | 552 var pendingObservers = GetPendingObservers(); |
607 delete GetPendingObservers()[priority]; | 553 if (!IS_NULL(pendingObservers)) |
| 554 delete pendingObservers[priority]; |
608 | 555 |
609 var delivered = []; | 556 var delivered = []; |
610 %MoveArrayContents(callbackInfo, delivered); | 557 %MoveArrayContents(callbackInfo, delivered); |
611 | 558 |
612 try { | 559 try { |
613 %_CallFunction(UNDEFINED, delivered, callback); | 560 %_CallFunction(UNDEFINED, delivered, callback); |
614 } catch (ex) {} // TODO(rossberg): perhaps log uncaught exceptions. | 561 } catch (ex) {} // TODO(rossberg): perhaps log uncaught exceptions. |
615 return true; | 562 return true; |
616 } | 563 } |
617 | 564 |
618 function ObjectDeliverChangeRecords(callback) { | 565 function ObjectDeliverChangeRecords(callback) { |
619 if (!IS_SPEC_FUNCTION(callback)) | 566 if (!IS_SPEC_FUNCTION(callback)) |
620 throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); | 567 throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); |
621 | 568 |
622 while (CallbackDeliverPending(callback)) {} | 569 while (CallbackDeliverPending(callback)) {} |
623 } | 570 } |
624 | 571 |
625 function ObserveMicrotaskRunner() { | 572 function ObserveMicrotaskRunner() { |
626 var pendingObservers = GetPendingObservers(); | 573 var pendingObservers = GetPendingObservers(); |
627 if (pendingObservers) { | 574 if (!IS_NULL(pendingObservers)) { |
628 SetPendingObservers(null); | 575 SetPendingObservers(null); |
629 for (var i in pendingObservers) { | 576 for (var i in pendingObservers) { |
630 CallbackDeliverPending(pendingObservers[i]); | 577 CallbackDeliverPending(pendingObservers[i]); |
631 } | 578 } |
632 } | 579 } |
633 } | 580 } |
634 | 581 |
635 function SetupObjectObserve() { | 582 function SetupObjectObserve() { |
636 %CheckIsBootstrapping(); | 583 %CheckIsBootstrapping(); |
637 InstallFunctions($Object, DONT_ENUM, $Array( | 584 InstallFunctions($Object, DONT_ENUM, $Array( |
638 "deliverChangeRecords", ObjectDeliverChangeRecords, | 585 "deliverChangeRecords", ObjectDeliverChangeRecords, |
639 "getNotifier", ObjectGetNotifier, | 586 "getNotifier", ObjectGetNotifier, |
640 "observe", ObjectObserve, | 587 "observe", ObjectObserve, |
641 "unobserve", ObjectUnobserve | 588 "unobserve", ObjectUnobserve |
642 )); | 589 )); |
643 InstallFunctions($Array, DONT_ENUM, $Array( | 590 InstallFunctions($Array, DONT_ENUM, $Array( |
644 "observe", ArrayObserve, | 591 "observe", ArrayObserve, |
645 "unobserve", ArrayUnobserve | 592 "unobserve", ArrayUnobserve |
646 )); | 593 )); |
647 InstallFunctions(notifierPrototype, DONT_ENUM, $Array( | 594 InstallFunctions(notifierPrototype, DONT_ENUM, $Array( |
648 "notify", ObjectNotifierNotify, | 595 "notify", ObjectNotifierNotify, |
649 "performChange", ObjectNotifierPerformChange | 596 "performChange", ObjectNotifierPerformChange |
650 )); | 597 )); |
651 } | 598 } |
652 | 599 |
653 SetupObjectObserve(); | 600 SetupObjectObserve(); |
OLD | NEW |