Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 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 |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | "use strict"; |
| 6 | |
| 7 | // Overview: |
| 8 | // |
| 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 |
| 11 | // and, as a side effect of mutation objects which are observed. The V8 runtime |
| 12 | // (both C++ and JS) will interact with these mechanisms primarily by enqueuing |
| 13 | // proper change records for objects which were mutated. The Object.observe |
| 14 | // routing and accounting consists primarily of three participants |
| 15 | // |
| 16 | // 1) ObjectInfo. This represents the observed state of a given object. It |
| 17 | // records what callbacks are observing the object, with what options, and |
| 18 | // what "change types" are in progress on the object (i.e. via |
| 19 | // notifier.performChange). |
| 20 | // |
| 21 | // 2) CallbackInfo. This represents a callback used for observation. It holds |
| 22 | // the records which must be delivered to the callback, as well as the global |
| 23 | // priority of the callback (which determines delivery order between |
| 24 | // callbacks). |
| 25 | // |
| 26 | // 3) observationState.pendingObservers. This is the set of observers which |
| 27 | // have change records which must be delivered. During "normal" delivery |
| 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 |
| 30 | // change records pending to a callback. |
| 31 | // |
| 32 | // Note that in order to reduce allocation and processing costs, the |
| 33 | // implementation of (1) and (2) have "optimized" states which represent |
| 34 | // common cases which can be handled more efficiently. |
| 35 | |
| 36 | var observationState; |
| 37 | |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 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. |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 41 | function GetObservationStateJS() { |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 42 | if (IS_UNDEFINED(observationState)) { |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 43 | observationState = %GetObservationState(); |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 44 | } |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 45 | |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 46 | // TODO(adamk): Consider moving this code into heap.cc |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 47 | if (IS_UNDEFINED(observationState.callbackInfoMap)) { |
| 48 | observationState.callbackInfoMap = %ObservationWeakMapCreate(); |
| 49 | observationState.objectInfoMap = %ObservationWeakMapCreate(); |
| 50 | observationState.notifierObjectInfoMap = %ObservationWeakMapCreate(); |
| 51 | observationState.pendingObservers = null; |
| 52 | observationState.nextCallbackPriority = 0; |
| 53 | observationState.lastMicrotaskId = 0; |
| 54 | } |
| 55 | |
| 56 | return observationState; |
| 57 | } |
| 58 | |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 59 | function GetPendingObservers() { |
| 60 | return GetObservationStateJS().pendingObservers; |
| 61 | } |
| 62 | |
| 63 | function SetPendingObservers(pendingObservers) { |
| 64 | GetObservationStateJS().pendingObservers = pendingObservers; |
| 65 | } |
| 66 | |
| 67 | function GetNextCallbackPriority() { |
| 68 | return GetObservationStateJS().nextCallbackPriority++; |
| 69 | } |
| 70 | |
| 71 | function nullProtoObject() { |
| 72 | return { __proto__: null }; |
| 73 | } |
| 74 | |
| 75 | function TypeMapCreate() { |
| 76 | return nullProtoObject(); |
| 77 | } |
| 78 | |
| 79 | function TypeMapAddType(typeMap, type, ignoreDuplicate) { |
| 80 | typeMap[type] = ignoreDuplicate ? 1 : (typeMap[type] || 0) + 1; |
| 81 | } |
| 82 | |
| 83 | function TypeMapRemoveType(typeMap, type) { |
| 84 | typeMap[type]--; |
| 85 | } |
| 86 | |
| 87 | function TypeMapCreateFromList(typeList, length) { |
| 88 | var typeMap = TypeMapCreate(); |
| 89 | for (var i = 0; i < length; i++) { |
| 90 | TypeMapAddType(typeMap, typeList[i], true); |
| 91 | } |
| 92 | return typeMap; |
| 93 | } |
| 94 | |
| 95 | function TypeMapHasType(typeMap, type) { |
| 96 | return !!typeMap[type]; |
| 97 | } |
| 98 | |
| 99 | function TypeMapIsDisjointFrom(typeMap1, typeMap2) { |
| 100 | if (!typeMap1 || !typeMap2) |
| 101 | return true; |
| 102 | |
| 103 | for (var type in typeMap1) { |
| 104 | if (TypeMapHasType(typeMap1, type) && TypeMapHasType(typeMap2, type)) |
| 105 | return false; |
| 106 | } |
| 107 | |
| 108 | return true; |
| 109 | } |
| 110 | |
| 111 | var defaultAcceptTypes = (function() { |
| 112 | var defaultTypes = [ |
| 113 | 'add', |
| 114 | 'update', |
| 115 | 'delete', |
| 116 | 'setPrototype', |
| 117 | 'reconfigure', |
| 118 | 'preventExtensions' |
| 119 | ]; |
| 120 | return TypeMapCreateFromList(defaultTypes, defaultTypes.length); |
| 121 | })(); |
| 122 | |
| 123 | // An Observer is a registration to observe an object by a callback with |
| 124 | // a given set of accept types. If the set of accept types is the default |
| 125 | // set for Object.observe, the observer is represented as a direct reference |
| 126 | // to the callback. An observer never changes its accept types and thus never |
| 127 | // needs to "normalize". |
| 128 | function ObserverCreate(callback, acceptList) { |
| 129 | if (IS_UNDEFINED(acceptList)) |
| 130 | return callback; |
| 131 | var observer = nullProtoObject(); |
| 132 | observer.callback = callback; |
| 133 | observer.accept = acceptList; |
| 134 | return observer; |
| 135 | } |
| 136 | |
| 137 | function ObserverGetCallback(observer) { |
| 138 | return IS_SPEC_FUNCTION(observer) ? observer : observer.callback; |
| 139 | } |
| 140 | |
| 141 | function ObserverGetAcceptTypes(observer) { |
| 142 | return IS_SPEC_FUNCTION(observer) ? defaultAcceptTypes : observer.accept; |
| 143 | } |
| 144 | |
| 145 | function ObserverIsActive(observer, objectInfo) { |
| 146 | return TypeMapIsDisjointFrom(ObjectInfoGetPerformingTypes(objectInfo), |
| 147 | ObserverGetAcceptTypes(observer)); |
| 148 | } |
| 149 | |
| 150 | function ObjectInfoGetOrCreate(object) { |
| 151 | var objectInfo = ObjectInfoGet(object); |
| 152 | if (IS_UNDEFINED(objectInfo)) { |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 153 | if (!%_IsJSProxy(object)) { |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 154 | %SetIsObserved(object); |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 155 | } |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 156 | objectInfo = { |
| 157 | object: object, |
| 158 | changeObservers: null, |
| 159 | notifier: null, |
| 160 | performing: null, |
| 161 | performingCount: 0, |
| 162 | }; |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 163 | %WeakCollectionSet(GetObservationStateJS().objectInfoMap, |
| 164 | object, objectInfo); |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 165 | } |
| 166 | return objectInfo; |
| 167 | } |
| 168 | |
| 169 | function ObjectInfoGet(object) { |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 170 | return %WeakCollectionGet(GetObservationStateJS().objectInfoMap, object); |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 171 | } |
| 172 | |
| 173 | function ObjectInfoGetFromNotifier(notifier) { |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 174 | return %WeakCollectionGet(GetObservationStateJS().notifierObjectInfoMap, |
| 175 | notifier); |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 176 | } |
| 177 | |
| 178 | function ObjectInfoGetNotifier(objectInfo) { |
| 179 | if (IS_NULL(objectInfo.notifier)) { |
| 180 | objectInfo.notifier = { __proto__: notifierPrototype }; |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 181 | %WeakCollectionSet(GetObservationStateJS().notifierObjectInfoMap, |
| 182 | objectInfo.notifier, objectInfo); |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 183 | } |
| 184 | |
| 185 | return objectInfo.notifier; |
| 186 | } |
| 187 | |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 188 | function ChangeObserversIsOptimized(changeObservers) { |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 189 | return IS_SPEC_FUNCTION(changeObservers) || |
| 190 | IS_SPEC_FUNCTION(changeObservers.callback); |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 191 | } |
| 192 | |
| 193 | // The set of observers on an object is called 'changeObservers'. The first |
| 194 | // observer is referenced directly via objectInfo.changeObservers. When a second |
| 195 | // is added, changeObservers "normalizes" to become a mapping of callback |
| 196 | // priority -> observer and is then stored on objectInfo.changeObservers. |
| 197 | function ObjectInfoNormalizeChangeObservers(objectInfo) { |
| 198 | if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { |
| 199 | var observer = objectInfo.changeObservers; |
| 200 | var callback = ObserverGetCallback(observer); |
| 201 | var callbackInfo = CallbackInfoGet(callback); |
| 202 | var priority = CallbackInfoGetPriority(callbackInfo); |
| 203 | objectInfo.changeObservers = nullProtoObject(); |
| 204 | objectInfo.changeObservers[priority] = observer; |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | function ObjectInfoAddObserver(objectInfo, callback, acceptList) { |
| 209 | var callbackInfo = CallbackInfoGetOrCreate(callback); |
| 210 | var observer = ObserverCreate(callback, acceptList); |
| 211 | |
| 212 | if (!objectInfo.changeObservers) { |
| 213 | objectInfo.changeObservers = observer; |
| 214 | return; |
| 215 | } |
| 216 | |
| 217 | ObjectInfoNormalizeChangeObservers(objectInfo); |
| 218 | var priority = CallbackInfoGetPriority(callbackInfo); |
| 219 | objectInfo.changeObservers[priority] = observer; |
| 220 | } |
| 221 | |
| 222 | function ObjectInfoRemoveObserver(objectInfo, callback) { |
| 223 | if (!objectInfo.changeObservers) |
| 224 | return; |
| 225 | |
| 226 | if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { |
| 227 | if (callback === ObserverGetCallback(objectInfo.changeObservers)) |
| 228 | objectInfo.changeObservers = null; |
| 229 | return; |
| 230 | } |
| 231 | |
| 232 | var callbackInfo = CallbackInfoGet(callback); |
| 233 | var priority = CallbackInfoGetPriority(callbackInfo); |
| 234 | objectInfo.changeObservers[priority] = null; |
| 235 | } |
| 236 | |
| 237 | function ObjectInfoHasActiveObservers(objectInfo) { |
| 238 | if (IS_UNDEFINED(objectInfo) || !objectInfo.changeObservers) |
| 239 | return false; |
| 240 | |
| 241 | if (ChangeObserversIsOptimized(objectInfo.changeObservers)) |
| 242 | return ObserverIsActive(objectInfo.changeObservers, objectInfo); |
| 243 | |
| 244 | for (var priority in objectInfo.changeObservers) { |
| 245 | var observer = objectInfo.changeObservers[priority]; |
| 246 | if (!IS_NULL(observer) && ObserverIsActive(observer, objectInfo)) |
| 247 | return true; |
| 248 | } |
| 249 | |
| 250 | return false; |
| 251 | } |
| 252 | |
| 253 | function ObjectInfoAddPerformingType(objectInfo, type) { |
| 254 | objectInfo.performing = objectInfo.performing || TypeMapCreate(); |
| 255 | TypeMapAddType(objectInfo.performing, type); |
| 256 | objectInfo.performingCount++; |
| 257 | } |
| 258 | |
| 259 | function ObjectInfoRemovePerformingType(objectInfo, type) { |
| 260 | objectInfo.performingCount--; |
| 261 | TypeMapRemoveType(objectInfo.performing, type); |
| 262 | } |
| 263 | |
| 264 | function ObjectInfoGetPerformingTypes(objectInfo) { |
| 265 | return objectInfo.performingCount > 0 ? objectInfo.performing : null; |
| 266 | } |
| 267 | |
| 268 | function ConvertAcceptListToTypeMap(arg) { |
| 269 | // We use undefined as a sentinel for the default accept list. |
| 270 | if (IS_UNDEFINED(arg)) |
| 271 | return arg; |
| 272 | |
| 273 | if (!IS_SPEC_OBJECT(arg)) |
| 274 | throw MakeTypeError("observe_accept_invalid"); |
| 275 | |
| 276 | var len = ToInteger(arg.length); |
| 277 | if (len < 0) len = 0; |
| 278 | |
| 279 | return TypeMapCreateFromList(arg, len); |
| 280 | } |
| 281 | |
| 282 | // CallbackInfo's optimized state is just a number which represents its global |
| 283 | // priority. When a change record must be enqueued for the callback, it |
| 284 | // normalizes. When delivery clears any pending change records, it re-optimizes. |
| 285 | function CallbackInfoGet(callback) { |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 286 | return %WeakCollectionGet(GetObservationStateJS().callbackInfoMap, callback); |
| 287 | } |
| 288 | |
| 289 | function CallbackInfoSet(callback, callbackInfo) { |
| 290 | %WeakCollectionSet(GetObservationStateJS().callbackInfoMap, |
| 291 | callback, callbackInfo); |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 292 | } |
| 293 | |
| 294 | function CallbackInfoGetOrCreate(callback) { |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 295 | var callbackInfo = CallbackInfoGet(callback); |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 296 | if (!IS_UNDEFINED(callbackInfo)) |
| 297 | return callbackInfo; |
| 298 | |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 299 | var priority = GetNextCallbackPriority(); |
| 300 | CallbackInfoSet(callback, priority); |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 301 | return priority; |
| 302 | } |
| 303 | |
| 304 | function CallbackInfoGetPriority(callbackInfo) { |
| 305 | if (IS_NUMBER(callbackInfo)) |
| 306 | return callbackInfo; |
| 307 | else |
| 308 | return callbackInfo.priority; |
| 309 | } |
| 310 | |
| 311 | function CallbackInfoNormalize(callback) { |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 312 | var callbackInfo = CallbackInfoGet(callback); |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 313 | if (IS_NUMBER(callbackInfo)) { |
| 314 | var priority = callbackInfo; |
| 315 | callbackInfo = new InternalArray; |
| 316 | callbackInfo.priority = priority; |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 317 | CallbackInfoSet(callback, callbackInfo); |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 318 | } |
| 319 | return callbackInfo; |
| 320 | } |
| 321 | |
| 322 | function ObjectObserve(object, callback, acceptList) { |
| 323 | if (!IS_SPEC_OBJECT(object)) |
| 324 | throw MakeTypeError("observe_non_object", ["observe"]); |
| 325 | if (%IsJSGlobalProxy(object)) |
| 326 | throw MakeTypeError("observe_global_proxy", ["observe"]); |
| 327 | if (!IS_SPEC_FUNCTION(callback)) |
| 328 | throw MakeTypeError("observe_non_function", ["observe"]); |
| 329 | if (ObjectIsFrozen(callback)) |
| 330 | throw MakeTypeError("observe_callback_frozen"); |
| 331 | |
| 332 | var objectObserveFn = %GetObjectContextObjectObserve(object); |
| 333 | return objectObserveFn(object, callback, acceptList); |
| 334 | } |
| 335 | |
| 336 | function NativeObjectObserve(object, callback, acceptList) { |
| 337 | var objectInfo = ObjectInfoGetOrCreate(object); |
| 338 | var typeList = ConvertAcceptListToTypeMap(acceptList); |
| 339 | ObjectInfoAddObserver(objectInfo, callback, typeList); |
| 340 | return object; |
| 341 | } |
| 342 | |
| 343 | function ObjectUnobserve(object, callback) { |
| 344 | if (!IS_SPEC_OBJECT(object)) |
| 345 | throw MakeTypeError("observe_non_object", ["unobserve"]); |
| 346 | if (%IsJSGlobalProxy(object)) |
| 347 | throw MakeTypeError("observe_global_proxy", ["unobserve"]); |
| 348 | if (!IS_SPEC_FUNCTION(callback)) |
| 349 | throw MakeTypeError("observe_non_function", ["unobserve"]); |
| 350 | |
| 351 | var objectInfo = ObjectInfoGet(object); |
| 352 | if (IS_UNDEFINED(objectInfo)) |
| 353 | return object; |
| 354 | |
| 355 | ObjectInfoRemoveObserver(objectInfo, callback); |
| 356 | return object; |
| 357 | } |
| 358 | |
| 359 | function ArrayObserve(object, callback) { |
| 360 | return ObjectObserve(object, callback, ['add', |
| 361 | 'update', |
| 362 | 'delete', |
| 363 | 'splice']); |
| 364 | } |
| 365 | |
| 366 | function ArrayUnobserve(object, callback) { |
| 367 | return ObjectUnobserve(object, callback); |
| 368 | } |
| 369 | |
| 370 | function ObserverEnqueueIfActive(observer, objectInfo, changeRecord) { |
| 371 | if (!ObserverIsActive(observer, objectInfo) || |
| 372 | !TypeMapHasType(ObserverGetAcceptTypes(observer), changeRecord.type)) { |
| 373 | return; |
| 374 | } |
| 375 | |
| 376 | var callback = ObserverGetCallback(observer); |
| 377 | if (!%ObserverObjectAndRecordHaveSameOrigin(callback, changeRecord.object, |
| 378 | changeRecord)) { |
| 379 | return; |
| 380 | } |
| 381 | |
| 382 | var callbackInfo = CallbackInfoNormalize(callback); |
| 383 | if (IS_NULL(GetPendingObservers())) { |
| 384 | SetPendingObservers(nullProtoObject()); |
| 385 | if (DEBUG_IS_ACTIVE) { |
| 386 | var id = ++GetObservationStateJS().lastMicrotaskId; |
| 387 | var name = "Object.observe"; |
| 388 | %EnqueueMicrotask(function() { |
| 389 | %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name }); |
| 390 | ObserveMicrotaskRunner(); |
| 391 | %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name }); |
| 392 | }); |
| 393 | %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name }); |
| 394 | } else { |
| 395 | %EnqueueMicrotask(ObserveMicrotaskRunner); |
| 396 | } |
| 397 | } |
| 398 | GetPendingObservers()[callbackInfo.priority] = callback; |
| 399 | callbackInfo.push(changeRecord); |
| 400 | } |
| 401 | |
| 402 | function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) { |
| 403 | if (!ObjectInfoHasActiveObservers(objectInfo)) |
| 404 | return; |
| 405 | |
| 406 | var hasType = !IS_UNDEFINED(type); |
| 407 | var newRecord = hasType ? |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 408 | { object: objectInfo.object, type: type } : |
| 409 | { object: objectInfo.object }; |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 410 | |
| 411 | for (var prop in changeRecord) { |
| 412 | if (prop === 'object' || (hasType && prop === 'type')) continue; |
| 413 | %DefineDataPropertyUnchecked( |
| 414 | newRecord, prop, changeRecord[prop], READ_ONLY + DONT_DELETE); |
| 415 | } |
| 416 | ObjectFreezeJS(newRecord); |
| 417 | |
| 418 | ObjectInfoEnqueueInternalChangeRecord(objectInfo, newRecord); |
| 419 | } |
| 420 | |
| 421 | function ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord) { |
| 422 | // TODO(rossberg): adjust once there is a story for symbols vs proxies. |
| 423 | if (IS_SYMBOL(changeRecord.name)) return; |
| 424 | |
| 425 | if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { |
| 426 | var observer = objectInfo.changeObservers; |
| 427 | ObserverEnqueueIfActive(observer, objectInfo, changeRecord); |
| 428 | return; |
| 429 | } |
| 430 | |
| 431 | for (var priority in objectInfo.changeObservers) { |
| 432 | var observer = objectInfo.changeObservers[priority]; |
| 433 | if (IS_NULL(observer)) |
| 434 | continue; |
| 435 | ObserverEnqueueIfActive(observer, objectInfo, changeRecord); |
| 436 | } |
| 437 | } |
| 438 | |
| 439 | function BeginPerformSplice(array) { |
| 440 | var objectInfo = ObjectInfoGet(array); |
| 441 | if (!IS_UNDEFINED(objectInfo)) |
| 442 | ObjectInfoAddPerformingType(objectInfo, 'splice'); |
| 443 | } |
| 444 | |
| 445 | function EndPerformSplice(array) { |
| 446 | var objectInfo = ObjectInfoGet(array); |
| 447 | if (!IS_UNDEFINED(objectInfo)) |
| 448 | ObjectInfoRemovePerformingType(objectInfo, 'splice'); |
| 449 | } |
| 450 | |
| 451 | function EnqueueSpliceRecord(array, index, removed, addedCount) { |
| 452 | var objectInfo = ObjectInfoGet(array); |
| 453 | if (!ObjectInfoHasActiveObservers(objectInfo)) |
| 454 | return; |
| 455 | |
| 456 | var changeRecord = { |
| 457 | type: 'splice', |
| 458 | object: array, |
| 459 | index: index, |
| 460 | removed: removed, |
| 461 | addedCount: addedCount |
| 462 | }; |
| 463 | |
| 464 | ObjectFreezeJS(changeRecord); |
| 465 | ObjectFreezeJS(changeRecord.removed); |
| 466 | ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord); |
| 467 | } |
| 468 | |
| 469 | function NotifyChange(type, object, name, oldValue) { |
| 470 | var objectInfo = ObjectInfoGet(object); |
| 471 | if (!ObjectInfoHasActiveObservers(objectInfo)) |
| 472 | return; |
| 473 | |
| 474 | var changeRecord; |
| 475 | if (arguments.length == 2) { |
| 476 | changeRecord = { type: type, object: object }; |
| 477 | } else if (arguments.length == 3) { |
| 478 | changeRecord = { type: type, object: object, name: name }; |
| 479 | } else { |
| 480 | changeRecord = { |
| 481 | type: type, |
| 482 | object: object, |
| 483 | name: name, |
| 484 | oldValue: oldValue |
| 485 | }; |
| 486 | } |
| 487 | |
| 488 | ObjectFreezeJS(changeRecord); |
| 489 | ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord); |
| 490 | } |
| 491 | |
| 492 | var notifierPrototype = {}; |
| 493 | |
| 494 | function ObjectNotifierNotify(changeRecord) { |
| 495 | if (!IS_SPEC_OBJECT(this)) |
| 496 | throw MakeTypeError("called_on_non_object", ["notify"]); |
| 497 | |
| 498 | var objectInfo = ObjectInfoGetFromNotifier(this); |
| 499 | if (IS_UNDEFINED(objectInfo)) |
| 500 | throw MakeTypeError("observe_notify_non_notifier"); |
| 501 | if (!IS_STRING(changeRecord.type)) |
| 502 | throw MakeTypeError("observe_type_non_string"); |
| 503 | |
| 504 | ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord); |
| 505 | } |
| 506 | |
| 507 | function ObjectNotifierPerformChange(changeType, changeFn) { |
| 508 | if (!IS_SPEC_OBJECT(this)) |
| 509 | throw MakeTypeError("called_on_non_object", ["performChange"]); |
| 510 | |
| 511 | var objectInfo = ObjectInfoGetFromNotifier(this); |
| 512 | if (IS_UNDEFINED(objectInfo)) |
| 513 | throw MakeTypeError("observe_notify_non_notifier"); |
| 514 | if (!IS_STRING(changeType)) |
| 515 | throw MakeTypeError("observe_perform_non_string"); |
| 516 | if (!IS_SPEC_FUNCTION(changeFn)) |
| 517 | throw MakeTypeError("observe_perform_non_function"); |
| 518 | |
| 519 | var performChangeFn = %GetObjectContextNotifierPerformChange(objectInfo); |
| 520 | performChangeFn(objectInfo, changeType, changeFn); |
| 521 | } |
| 522 | |
| 523 | function NativeObjectNotifierPerformChange(objectInfo, changeType, changeFn) { |
| 524 | ObjectInfoAddPerformingType(objectInfo, changeType); |
| 525 | |
| 526 | var changeRecord; |
| 527 | try { |
| 528 | changeRecord = %_CallFunction(UNDEFINED, changeFn); |
| 529 | } finally { |
| 530 | ObjectInfoRemovePerformingType(objectInfo, changeType); |
| 531 | } |
| 532 | |
| 533 | if (IS_SPEC_OBJECT(changeRecord)) |
| 534 | ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, changeType); |
| 535 | } |
| 536 | |
| 537 | function ObjectGetNotifier(object) { |
| 538 | if (!IS_SPEC_OBJECT(object)) |
| 539 | throw MakeTypeError("observe_non_object", ["getNotifier"]); |
| 540 | if (%IsJSGlobalProxy(object)) |
| 541 | throw MakeTypeError("observe_global_proxy", ["getNotifier"]); |
| 542 | |
| 543 | if (ObjectIsFrozen(object)) return null; |
| 544 | |
| 545 | if (!%ObjectWasCreatedInCurrentOrigin(object)) return null; |
| 546 | |
| 547 | var getNotifierFn = %GetObjectContextObjectGetNotifier(object); |
| 548 | return getNotifierFn(object); |
| 549 | } |
| 550 | |
| 551 | function NativeObjectGetNotifier(object) { |
| 552 | var objectInfo = ObjectInfoGetOrCreate(object); |
| 553 | return ObjectInfoGetNotifier(objectInfo); |
| 554 | } |
| 555 | |
| 556 | function CallbackDeliverPending(callback) { |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 557 | var callbackInfo = CallbackInfoGet(callback); |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 558 | if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) |
| 559 | return false; |
| 560 | |
| 561 | // Clear the pending change records from callback and return it to its |
| 562 | // "optimized" state. |
| 563 | var priority = callbackInfo.priority; |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 564 | CallbackInfoSet(callback, priority); |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 565 | |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 566 | var pendingObservers = GetPendingObservers(); |
| 567 | if (!IS_NULL(pendingObservers)) |
| 568 | delete pendingObservers[priority]; |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 569 | |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 570 | // TODO: combine the following runtime calls for perf optimization. |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 571 | var delivered = []; |
| 572 | %MoveArrayContents(callbackInfo, delivered); |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 573 | %DeliverObservationChangeRecords(callback, delivered); |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 574 | |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 575 | return true; |
| 576 | } |
| 577 | |
| 578 | function ObjectDeliverChangeRecords(callback) { |
| 579 | if (!IS_SPEC_FUNCTION(callback)) |
| 580 | throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); |
| 581 | |
| 582 | while (CallbackDeliverPending(callback)) {} |
| 583 | } |
| 584 | |
| 585 | function ObserveMicrotaskRunner() { |
| 586 | var pendingObservers = GetPendingObservers(); |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame^] | 587 | if (!IS_NULL(pendingObservers)) { |
Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 588 | SetPendingObservers(null); |
| 589 | for (var i in pendingObservers) { |
| 590 | CallbackDeliverPending(pendingObservers[i]); |
| 591 | } |
| 592 | } |
| 593 | } |
| 594 | |
| 595 | function SetupObjectObserve() { |
| 596 | %CheckIsBootstrapping(); |
| 597 | InstallFunctions($Object, DONT_ENUM, $Array( |
| 598 | "deliverChangeRecords", ObjectDeliverChangeRecords, |
| 599 | "getNotifier", ObjectGetNotifier, |
| 600 | "observe", ObjectObserve, |
| 601 | "unobserve", ObjectUnobserve |
| 602 | )); |
| 603 | InstallFunctions($Array, DONT_ENUM, $Array( |
| 604 | "observe", ArrayObserve, |
| 605 | "unobserve", ArrayUnobserve |
| 606 | )); |
| 607 | InstallFunctions(notifierPrototype, DONT_ENUM, $Array( |
| 608 | "notify", ObjectNotifierNotify, |
| 609 | "performChange", ObjectNotifierPerformChange |
| 610 | )); |
| 611 | } |
| 612 | |
| 613 | SetupObjectObserve(); |