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