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