| 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 |