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