OLD | NEW |
1 // Copyright 2012 the V8 project authors. All rights reserved. | 1 // Copyright 2012 the V8 project authors. All rights reserved. |
2 // Redistribution and use in source and binary forms, with or without | 2 // Redistribution and use in source and binary forms, with or without |
3 // modification, are permitted provided that the following conditions are | 3 // modification, are permitted provided that the following conditions are |
4 // met: | 4 // met: |
5 // | 5 // |
6 // * Redistributions of source code must retain the above copyright | 6 // * Redistributions of source code must retain the above copyright |
7 // notice, this list of conditions and the following disclaimer. | 7 // notice, this list of conditions and the following disclaimer. |
8 // * Redistributions in binary form must reproduce the above | 8 // * Redistributions in binary form must reproduce the above |
9 // copyright notice, this list of conditions and the following | 9 // copyright notice, this list of conditions and the following |
10 // disclaimer in the documentation and/or other materials provided | 10 // disclaimer in the documentation and/or other materials provided |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
49 // 3) observationState.pendingObservers. This is the set of observers which | 49 // 3) observationState.pendingObservers. This is the set of observers which |
50 // have change records which must be delivered. During "normal" delivery | 50 // have change records which must be delivered. During "normal" delivery |
51 // (i.e. not Object.deliverChangeRecords), this is the mechanism by which | 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 | 52 // callbacks are invoked in the proper order until there are no more |
53 // change records pending to a callback. | 53 // change records pending to a callback. |
54 // | 54 // |
55 // Note that in order to reduce allocation and processing costs, the | 55 // Note that in order to reduce allocation and processing costs, the |
56 // implementation of (1) and (2) have "optimized" states which represent | 56 // implementation of (1) and (2) have "optimized" states which represent |
57 // common cases which can be handled more efficiently. | 57 // common cases which can be handled more efficiently. |
58 | 58 |
59 var observationState = %GetObservationState(); | 59 var observationState; |
60 if (IS_UNDEFINED(observationState.callbackInfoMap)) { | 60 |
61 observationState.callbackInfoMap = %ObservationWeakMapCreate(); | 61 function GetObservationState() { |
62 observationState.objectInfoMap = %ObservationWeakMapCreate(); | 62 if (IS_UNDEFINED(observationState)) |
63 observationState.notifierObjectInfoMap = %ObservationWeakMapCreate(); | 63 observationState = %GetObservationState(); |
64 observationState.pendingObservers = null; | 64 |
65 observationState.nextCallbackPriority = 0; | 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; |
66 } | 74 } |
67 | 75 |
68 function ObservationWeakMap(map) { | 76 function GetWeakMapWrapper() { |
69 this.map_ = map; | 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; |
70 } | 98 } |
71 | 99 |
72 ObservationWeakMap.prototype = { | 100 var contextMaps; |
73 get: function(key) { | 101 |
74 key = %UnwrapGlobalProxy(key); | 102 function GetContextMaps() { |
75 if (!IS_SPEC_OBJECT(key)) return UNDEFINED; | 103 if (IS_UNDEFINED(contextMaps)) { |
76 return %WeakCollectionGet(this.map_, key); | 104 var map = GetWeakMapWrapper(); |
77 }, | 105 var observationState = GetObservationState(); |
78 set: function(key, value) { | 106 contextMaps = { |
79 key = %UnwrapGlobalProxy(key); | 107 callbackInfoMap: new map(observationState.callbackInfoMap), |
80 if (!IS_SPEC_OBJECT(key)) return UNDEFINED; | 108 objectInfoMap: new map(observationState.objectInfoMap), |
81 %WeakCollectionSet(this.map_, key, value); | 109 notifierObjectInfoMap: new map(observationState.notifierObjectInfoMap) |
82 }, | 110 }; |
83 has: function(key) { | |
84 return !IS_UNDEFINED(this.get(key)); | |
85 } | 111 } |
86 }; | |
87 | 112 |
88 var callbackInfoMap = | 113 return contextMaps; |
89 new ObservationWeakMap(observationState.callbackInfoMap); | 114 } |
90 var objectInfoMap = new ObservationWeakMap(observationState.objectInfoMap); | 115 |
91 var notifierObjectInfoMap = | 116 function GetCallbackInfoMap() { |
92 new ObservationWeakMap(observationState.notifierObjectInfoMap); | 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 } |
93 | 139 |
94 function nullProtoObject() { | 140 function nullProtoObject() { |
95 return { __proto__: null }; | 141 return { __proto__: null }; |
96 } | 142 } |
97 | 143 |
98 function TypeMapCreate() { | 144 function TypeMapCreate() { |
99 return nullProtoObject(); | 145 return nullProtoObject(); |
100 } | 146 } |
101 | 147 |
102 function TypeMapAddType(typeMap, type, ignoreDuplicate) { | 148 function TypeMapAddType(typeMap, type, ignoreDuplicate) { |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
173 if (!%IsJSProxy(object)) | 219 if (!%IsJSProxy(object)) |
174 %SetIsObserved(object); | 220 %SetIsObserved(object); |
175 | 221 |
176 objectInfo = { | 222 objectInfo = { |
177 object: object, | 223 object: object, |
178 changeObservers: null, | 224 changeObservers: null, |
179 notifier: null, | 225 notifier: null, |
180 performing: null, | 226 performing: null, |
181 performingCount: 0, | 227 performingCount: 0, |
182 }; | 228 }; |
183 objectInfoMap.set(object, objectInfo); | 229 GetObjectInfoMap().set(object, objectInfo); |
184 } | 230 } |
185 return objectInfo; | 231 return objectInfo; |
186 } | 232 } |
187 | 233 |
188 function ObjectInfoGet(object) { | 234 function ObjectInfoGet(object) { |
189 return objectInfoMap.get(object); | 235 return GetObjectInfoMap().get(object); |
190 } | 236 } |
191 | 237 |
192 function ObjectInfoGetFromNotifier(notifier) { | 238 function ObjectInfoGetFromNotifier(notifier) { |
193 return notifierObjectInfoMap.get(notifier); | 239 return GetNotifierObjectInfoMap().get(notifier); |
194 } | 240 } |
195 | 241 |
196 function ObjectInfoGetNotifier(objectInfo) { | 242 function ObjectInfoGetNotifier(objectInfo) { |
197 if (IS_NULL(objectInfo.notifier)) { | 243 if (IS_NULL(objectInfo.notifier)) { |
198 objectInfo.notifier = { __proto__: notifierPrototype }; | 244 objectInfo.notifier = { __proto__: notifierPrototype }; |
199 notifierObjectInfoMap.set(objectInfo.notifier, objectInfo); | 245 GetNotifierObjectInfoMap().set(objectInfo.notifier, objectInfo); |
200 } | 246 } |
201 | 247 |
202 return objectInfo.notifier; | 248 return objectInfo.notifier; |
203 } | 249 } |
204 | 250 |
205 function ObjectInfoGetObject(objectInfo) { | 251 function ObjectInfoGetObject(objectInfo) { |
206 return objectInfo.object; | 252 return objectInfo.object; |
207 } | 253 } |
208 | 254 |
209 function ChangeObserversIsOptimized(changeObservers) { | 255 function ChangeObserversIsOptimized(changeObservers) { |
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
295 arg.length < 0) | 341 arg.length < 0) |
296 return false; | 342 return false; |
297 | 343 |
298 return true; | 344 return true; |
299 } | 345 } |
300 | 346 |
301 // CallbackInfo's optimized state is just a number which represents its global | 347 // CallbackInfo's optimized state is just a number which represents its global |
302 // priority. When a change record must be enqueued for the callback, it | 348 // priority. When a change record must be enqueued for the callback, it |
303 // normalizes. When delivery clears any pending change records, it re-optimizes. | 349 // normalizes. When delivery clears any pending change records, it re-optimizes. |
304 function CallbackInfoGet(callback) { | 350 function CallbackInfoGet(callback) { |
305 return callbackInfoMap.get(callback); | 351 return GetCallbackInfoMap().get(callback); |
306 } | 352 } |
307 | 353 |
308 function CallbackInfoGetOrCreate(callback) { | 354 function CallbackInfoGetOrCreate(callback) { |
309 var callbackInfo = callbackInfoMap.get(callback); | 355 var callbackInfo = GetCallbackInfoMap().get(callback); |
310 if (!IS_UNDEFINED(callbackInfo)) | 356 if (!IS_UNDEFINED(callbackInfo)) |
311 return callbackInfo; | 357 return callbackInfo; |
312 | 358 |
313 var priority = observationState.nextCallbackPriority++ | 359 var priority = GetNextCallbackPriority(); |
314 callbackInfoMap.set(callback, priority); | 360 GetCallbackInfoMap().set(callback, priority); |
315 return priority; | 361 return priority; |
316 } | 362 } |
317 | 363 |
318 function CallbackInfoGetPriority(callbackInfo) { | 364 function CallbackInfoGetPriority(callbackInfo) { |
319 if (IS_NUMBER(callbackInfo)) | 365 if (IS_NUMBER(callbackInfo)) |
320 return callbackInfo; | 366 return callbackInfo; |
321 else | 367 else |
322 return callbackInfo.priority; | 368 return callbackInfo.priority; |
323 } | 369 } |
324 | 370 |
325 function CallbackInfoNormalize(callback) { | 371 function CallbackInfoNormalize(callback) { |
326 var callbackInfo = callbackInfoMap.get(callback); | 372 var callbackInfo = GetCallbackInfoMap().get(callback); |
327 if (IS_NUMBER(callbackInfo)) { | 373 if (IS_NUMBER(callbackInfo)) { |
328 var priority = callbackInfo; | 374 var priority = callbackInfo; |
329 callbackInfo = new InternalArray; | 375 callbackInfo = new InternalArray; |
330 callbackInfo.priority = priority; | 376 callbackInfo.priority = priority; |
331 callbackInfoMap.set(callback, callbackInfo); | 377 GetCallbackInfoMap().set(callback, callbackInfo); |
332 } | 378 } |
333 return callbackInfo; | 379 return callbackInfo; |
334 } | 380 } |
335 | 381 |
336 function ObjectObserve(object, callback, acceptList) { | 382 function ObjectObserve(object, callback, acceptList) { |
337 if (!IS_SPEC_OBJECT(object)) | 383 if (!IS_SPEC_OBJECT(object)) |
338 throw MakeTypeError("observe_non_object", ["observe"]); | 384 throw MakeTypeError("observe_non_object", ["observe"]); |
339 if (!IS_SPEC_FUNCTION(callback)) | 385 if (!IS_SPEC_FUNCTION(callback)) |
340 throw MakeTypeError("observe_non_function", ["observe"]); | 386 throw MakeTypeError("observe_non_function", ["observe"]); |
341 if (ObjectIsFrozen(callback)) | 387 if (ObjectIsFrozen(callback)) |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
383 var callback = ObserverGetCallback(observer); | 429 var callback = ObserverGetCallback(observer); |
384 if (needsAccessCheck && | 430 if (needsAccessCheck && |
385 // Drop all splice records on the floor for access-checked objects | 431 // Drop all splice records on the floor for access-checked objects |
386 (changeRecord.type == 'splice' || | 432 (changeRecord.type == 'splice' || |
387 !%IsAccessAllowedForObserver( | 433 !%IsAccessAllowedForObserver( |
388 callback, changeRecord.object, changeRecord.name))) { | 434 callback, changeRecord.object, changeRecord.name))) { |
389 return; | 435 return; |
390 } | 436 } |
391 | 437 |
392 var callbackInfo = CallbackInfoNormalize(callback); | 438 var callbackInfo = CallbackInfoNormalize(callback); |
393 if (IS_NULL(observationState.pendingObservers)) { | 439 if (IS_NULL(GetPendingObservers())) { |
394 observationState.pendingObservers = nullProtoObject(); | 440 SetPendingObservers(nullProtoObject()) |
395 GetMicrotaskQueue().push(ObserveMicrotaskRunner); | 441 GetMicrotaskQueue().push(ObserveMicrotaskRunner); |
396 %SetMicrotaskPending(true); | 442 %SetMicrotaskPending(true); |
397 } | 443 } |
398 observationState.pendingObservers[callbackInfo.priority] = callback; | 444 GetPendingObservers()[callbackInfo.priority] = callback; |
399 callbackInfo.push(changeRecord); | 445 callbackInfo.push(changeRecord); |
400 } | 446 } |
401 | 447 |
402 function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) { | 448 function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) { |
403 if (!ObjectInfoHasActiveObservers(objectInfo)) | 449 if (!ObjectInfoHasActiveObservers(objectInfo)) |
404 return; | 450 return; |
405 | 451 |
406 var hasType = !IS_UNDEFINED(type); | 452 var hasType = !IS_UNDEFINED(type); |
407 var newRecord = hasType ? | 453 var newRecord = hasType ? |
408 { object: ObjectInfoGetObject(objectInfo), type: type } : | 454 { object: ObjectInfoGetObject(objectInfo), type: type } : |
(...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
541 if (!IS_SPEC_OBJECT(object)) | 587 if (!IS_SPEC_OBJECT(object)) |
542 throw MakeTypeError("observe_non_object", ["getNotifier"]); | 588 throw MakeTypeError("observe_non_object", ["getNotifier"]); |
543 | 589 |
544 if (ObjectIsFrozen(object)) return null; | 590 if (ObjectIsFrozen(object)) return null; |
545 | 591 |
546 var objectInfo = ObjectInfoGetOrCreate(object); | 592 var objectInfo = ObjectInfoGetOrCreate(object); |
547 return ObjectInfoGetNotifier(objectInfo); | 593 return ObjectInfoGetNotifier(objectInfo); |
548 } | 594 } |
549 | 595 |
550 function CallbackDeliverPending(callback) { | 596 function CallbackDeliverPending(callback) { |
551 var callbackInfo = callbackInfoMap.get(callback); | 597 var callbackInfo = GetCallbackInfoMap().get(callback); |
552 if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) | 598 if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) |
553 return false; | 599 return false; |
554 | 600 |
555 // 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 |
556 // "optimized" state. | 602 // "optimized" state. |
557 var priority = callbackInfo.priority; | 603 var priority = callbackInfo.priority; |
558 callbackInfoMap.set(callback, priority); | 604 GetCallbackInfoMap().set(callback, priority); |
559 | 605 |
560 if (observationState.pendingObservers) | 606 if (GetPendingObservers()) |
561 delete observationState.pendingObservers[priority]; | 607 delete GetPendingObservers()[priority]; |
562 | 608 |
563 var delivered = []; | 609 var delivered = []; |
564 %MoveArrayContents(callbackInfo, delivered); | 610 %MoveArrayContents(callbackInfo, delivered); |
565 | 611 |
566 try { | 612 try { |
567 %_CallFunction(UNDEFINED, delivered, callback); | 613 %_CallFunction(UNDEFINED, delivered, callback); |
568 } catch (ex) {} // TODO(rossberg): perhaps log uncaught exceptions. | 614 } catch (ex) {} // TODO(rossberg): perhaps log uncaught exceptions. |
569 return true; | 615 return true; |
570 } | 616 } |
571 | 617 |
572 function ObjectDeliverChangeRecords(callback) { | 618 function ObjectDeliverChangeRecords(callback) { |
573 if (!IS_SPEC_FUNCTION(callback)) | 619 if (!IS_SPEC_FUNCTION(callback)) |
574 throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); | 620 throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); |
575 | 621 |
576 while (CallbackDeliverPending(callback)) {} | 622 while (CallbackDeliverPending(callback)) {} |
577 } | 623 } |
578 | 624 |
579 function ObserveMicrotaskRunner() { | 625 function ObserveMicrotaskRunner() { |
580 var pendingObservers = observationState.pendingObservers; | 626 var pendingObservers = GetPendingObservers(); |
581 if (pendingObservers) { | 627 if (pendingObservers) { |
582 observationState.pendingObservers = null; | 628 SetPendingObservers(null); |
583 for (var i in pendingObservers) { | 629 for (var i in pendingObservers) { |
584 CallbackDeliverPending(pendingObservers[i]); | 630 CallbackDeliverPending(pendingObservers[i]); |
585 } | 631 } |
586 } | 632 } |
587 } | 633 } |
588 | 634 |
589 function SetupObjectObserve() { | 635 function SetupObjectObserve() { |
590 %CheckIsBootstrapping(); | 636 %CheckIsBootstrapping(); |
591 InstallFunctions($Object, DONT_ENUM, $Array( | 637 InstallFunctions($Object, DONT_ENUM, $Array( |
592 "deliverChangeRecords", ObjectDeliverChangeRecords, | 638 "deliverChangeRecords", ObjectDeliverChangeRecords, |
593 "getNotifier", ObjectGetNotifier, | 639 "getNotifier", ObjectGetNotifier, |
594 "observe", ObjectObserve, | 640 "observe", ObjectObserve, |
595 "unobserve", ObjectUnobserve | 641 "unobserve", ObjectUnobserve |
596 )); | 642 )); |
597 InstallFunctions($Array, DONT_ENUM, $Array( | 643 InstallFunctions($Array, DONT_ENUM, $Array( |
598 "observe", ArrayObserve, | 644 "observe", ArrayObserve, |
599 "unobserve", ArrayUnobserve | 645 "unobserve", ArrayUnobserve |
600 )); | 646 )); |
601 InstallFunctions(notifierPrototype, DONT_ENUM, $Array( | 647 InstallFunctions(notifierPrototype, DONT_ENUM, $Array( |
602 "notify", ObjectNotifierNotify, | 648 "notify", ObjectNotifierNotify, |
603 "performChange", ObjectNotifierPerformChange | 649 "performChange", ObjectNotifierPerformChange |
604 )); | 650 )); |
605 } | 651 } |
606 | 652 |
607 SetupObjectObserve(); | 653 SetupObjectObserve(); |
OLD | NEW |