danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 1 | // Copyright 2012 the V8 project authors. All rights reserved. |
| 2 | // Redistribution and use in source and binary forms, with or without |
| 3 | // modification, are permitted provided that the following conditions are |
| 4 | // met: |
| 5 | // |
| 6 | // * Redistributions of source code must retain the above copyright |
| 7 | // notice, this list of conditions and the following disclaimer. |
| 8 | // * Redistributions in binary form must reproduce the above |
| 9 | // copyright notice, this list of conditions and the following |
| 10 | // disclaimer in the documentation and/or other materials provided |
| 11 | // with the distribution. |
| 12 | // * Neither the name of Google Inc. nor the names of its |
| 13 | // contributors may be used to endorse or promote products derived |
| 14 | // from this software without specific prior written permission. |
| 15 | // |
| 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 17 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 18 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 19 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 20 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 21 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 22 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 23 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 24 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 25 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 27 | |
| 28 | "use strict"; |
| 29 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 30 | // Overview: |
| 31 | // |
| 32 | // This file contains all of the routing and accounting for Object.observe. |
| 33 | // User code will interact with these mechanisms via the Object.observe APIs |
| 34 | // and, as a side effect of mutation objects which are observed. The V8 runtime |
| 35 | // (both C++ and JS) will interact with these mechanisms primarily by enqueuing |
| 36 | // proper change records for objects which were mutated. The Object.observe |
| 37 | // routing and accounting consists primarily of three participants |
| 38 | // |
| 39 | // 1) ObjectInfo. This represents the observed state of a given object. It |
| 40 | // records what callbacks are observing the object, with what options, and |
| 41 | // what "change types" are in progress on the object (i.e. via |
| 42 | // notifier.performChange). |
| 43 | // |
| 44 | // 2) CallbackInfo. This represents a callback used for observation. It holds |
| 45 | // the records which must be delivered to the callback, as well as the global |
| 46 | // priority of the callback (which determines delivery order between |
| 47 | // callbacks). |
| 48 | // |
| 49 | // 3) observationState.pendingObservers. This is the set of observers which |
| 50 | // have change records which must be delivered. During "normal" delivery |
| 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 |
| 53 | // change records pending to a callback. |
| 54 | // |
| 55 | // Note that in order to reduce allocation and processing costs, the |
| 56 | // implementation of (1) and (2) have "optimized" states which represent |
| 57 | // common cases which can be handled more efficiently. |
| 58 | |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 59 | var observationState = %GetObservationState(); |
jkummerow@chromium.org | 1048047 | 2013-07-17 08:22:15 +0000 | [diff] [blame] | 60 | if (IS_UNDEFINED(observationState.callbackInfoMap)) { |
| 61 | observationState.callbackInfoMap = %ObservationWeakMapCreate(); |
mmassi@chromium.org | 2f0efde | 2013-02-06 14:12:58 +0000 | [diff] [blame] | 62 | observationState.objectInfoMap = %ObservationWeakMapCreate(); |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 63 | observationState.notifierObjectInfoMap = %ObservationWeakMapCreate(); |
| 64 | observationState.pendingObservers = null; |
jkummerow@chromium.org | 1048047 | 2013-07-17 08:22:15 +0000 | [diff] [blame] | 65 | observationState.nextCallbackPriority = 0; |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 66 | } |
| 67 | |
mmassi@chromium.org | 2f0efde | 2013-02-06 14:12:58 +0000 | [diff] [blame] | 68 | function ObservationWeakMap(map) { |
| 69 | this.map_ = map; |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 70 | } |
| 71 | |
mmassi@chromium.org | 2f0efde | 2013-02-06 14:12:58 +0000 | [diff] [blame] | 72 | ObservationWeakMap.prototype = { |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 73 | get: function(key) { |
mmassi@chromium.org | 2f0efde | 2013-02-06 14:12:58 +0000 | [diff] [blame] | 74 | key = %UnwrapGlobalProxy(key); |
| 75 | if (!IS_SPEC_OBJECT(key)) return void 0; |
jkummerow@chromium.org | ba72ec8 | 2013-07-22 09:21:20 +0000 | [diff] [blame] | 76 | return %WeakCollectionGet(this.map_, key); |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 77 | }, |
| 78 | set: function(key, value) { |
mmassi@chromium.org | 2f0efde | 2013-02-06 14:12:58 +0000 | [diff] [blame] | 79 | key = %UnwrapGlobalProxy(key); |
| 80 | if (!IS_SPEC_OBJECT(key)) return void 0; |
jkummerow@chromium.org | ba72ec8 | 2013-07-22 09:21:20 +0000 | [diff] [blame] | 81 | %WeakCollectionSet(this.map_, key, value); |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 82 | }, |
| 83 | has: function(key) { |
mstarzinger@chromium.org | 32280cf | 2012-12-06 17:32:37 +0000 | [diff] [blame] | 84 | return !IS_UNDEFINED(this.get(key)); |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 85 | } |
| 86 | }; |
| 87 | |
jkummerow@chromium.org | 1048047 | 2013-07-17 08:22:15 +0000 | [diff] [blame] | 88 | var callbackInfoMap = |
| 89 | new ObservationWeakMap(observationState.callbackInfoMap); |
mmassi@chromium.org | 2f0efde | 2013-02-06 14:12:58 +0000 | [diff] [blame] | 90 | var objectInfoMap = new ObservationWeakMap(observationState.objectInfoMap); |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 91 | var notifierObjectInfoMap = |
| 92 | new ObservationWeakMap(observationState.notifierObjectInfoMap); |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 93 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 94 | function TypeMapCreate() { |
| 95 | return { __proto__: null }; |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 96 | } |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 97 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 98 | function TypeMapAddType(typeMap, type, ignoreDuplicate) { |
| 99 | typeMap[type] = ignoreDuplicate ? 1 : (typeMap[type] || 0) + 1; |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 100 | } |
| 101 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 102 | function TypeMapRemoveType(typeMap, type) { |
| 103 | typeMap[type]--; |
| 104 | } |
| 105 | |
| 106 | function TypeMapCreateFromList(typeList) { |
| 107 | var typeMap = TypeMapCreate(); |
| 108 | for (var i = 0; i < typeList.length; i++) { |
| 109 | TypeMapAddType(typeMap, typeList[i], true); |
| 110 | } |
| 111 | return typeMap; |
| 112 | } |
| 113 | |
| 114 | function TypeMapHasType(typeMap, type) { |
| 115 | return !!typeMap[type]; |
| 116 | } |
| 117 | |
| 118 | function TypeMapIsDisjointFrom(typeMap1, typeMap2) { |
| 119 | if (!typeMap1 || !typeMap2) |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 120 | return true; |
| 121 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 122 | for (var type in typeMap1) { |
| 123 | if (TypeMapHasType(typeMap1, type) && TypeMapHasType(typeMap2, type)) |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 124 | return false; |
| 125 | } |
| 126 | |
| 127 | return true; |
| 128 | } |
| 129 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 130 | var defaultAcceptTypes = TypeMapCreateFromList([ |
| 131 | 'new', |
| 132 | 'updated', |
| 133 | 'deleted', |
| 134 | 'prototype', |
| 135 | 'reconfigured' |
| 136 | ]); |
| 137 | |
| 138 | // An Observer is a registration to observe an object by a callback with |
| 139 | // a given set of accept types. If the set of accept types is the default |
| 140 | // set for Object.observe, the observer is represented as a direct reference |
| 141 | // to the callback. An observer never changes its accept types and thus never |
| 142 | // needs to "normalize". |
| 143 | function ObserverCreate(callback, acceptList) { |
| 144 | return IS_UNDEFINED(acceptList) ? callback : { |
| 145 | __proto__: null, |
| 146 | callback: callback, |
| 147 | accept: TypeMapCreateFromList(acceptList) |
| 148 | }; |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 149 | } |
| 150 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 151 | function ObserverGetCallback(observer) { |
| 152 | return IS_SPEC_FUNCTION(observer) ? observer : observer.callback; |
| 153 | } |
| 154 | |
| 155 | function ObserverGetAcceptTypes(observer) { |
| 156 | return IS_SPEC_FUNCTION(observer) ? defaultAcceptTypes : observer.accept; |
| 157 | } |
| 158 | |
| 159 | function ObserverIsActive(observer, objectInfo) { |
| 160 | return TypeMapIsDisjointFrom(ObjectInfoGetPerformingTypes(objectInfo), |
| 161 | ObserverGetAcceptTypes(observer)); |
| 162 | } |
| 163 | |
| 164 | function ObjectInfoGet(object) { |
| 165 | var objectInfo = objectInfoMap.get(object); |
| 166 | if (IS_UNDEFINED(objectInfo)) { |
| 167 | if (!%IsJSProxy(object)) |
| 168 | %SetIsObserved(object); |
| 169 | |
| 170 | objectInfo = { |
| 171 | object: object, |
| 172 | changeObservers: null, |
| 173 | notifier: null, |
| 174 | performing: null, |
| 175 | performingCount: 0, |
| 176 | }; |
| 177 | objectInfoMap.set(object, objectInfo); |
| 178 | } |
| 179 | return objectInfo; |
| 180 | } |
| 181 | |
| 182 | function ObjectInfoGetFromNotifier(notifier) { |
| 183 | return notifierObjectInfoMap.get(notifier); |
| 184 | } |
| 185 | |
| 186 | function ObjectInfoGetNotifier(objectInfo) { |
| 187 | if (IS_NULL(objectInfo.notifier)) { |
| 188 | objectInfo.notifier = { __proto__: notifierPrototype }; |
| 189 | notifierObjectInfoMap.set(objectInfo.notifier, objectInfo); |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 190 | } |
| 191 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 192 | return objectInfo.notifier; |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 193 | } |
| 194 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 195 | function ObjectInfoGetObject(objectInfo) { |
| 196 | return objectInfo.object; |
| 197 | } |
| 198 | |
| 199 | function ChangeObserversIsOptimized(changeObservers) { |
| 200 | return typeof changeObservers === 'function' || |
| 201 | typeof changeObservers.callback === 'function'; |
| 202 | } |
| 203 | |
| 204 | // The set of observers on an object is called 'changeObservers'. The first |
| 205 | // observer is referenced directly via objectInfo.changeObservers. When a second |
| 206 | // is added, changeObservers "normalizes" to become a mapping of callback |
| 207 | // priority -> observer and is then stored on objectInfo.changeObservers. |
| 208 | function ObjectInfoNormalizeChangeObservers(objectInfo) { |
| 209 | if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { |
| 210 | var observer = objectInfo.changeObservers; |
| 211 | var callback = ObserverGetCallback(observer); |
| 212 | var callbackInfo = CallbackInfoGet(callback); |
| 213 | var priority = CallbackInfoGetPriority(callbackInfo); |
| 214 | objectInfo.changeObservers = { __proto__: null }; |
| 215 | objectInfo.changeObservers[priority] = observer; |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | function ObjectInfoAddObserver(objectInfo, callback, acceptList) { |
| 220 | var callbackInfo = CallbackInfoGetOrCreate(callback); |
| 221 | var observer = ObserverCreate(callback, acceptList); |
| 222 | |
| 223 | if (!objectInfo.changeObservers) { |
| 224 | objectInfo.changeObservers = observer; |
| 225 | return; |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 226 | } |
| 227 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 228 | ObjectInfoNormalizeChangeObservers(objectInfo); |
| 229 | var priority = CallbackInfoGetPriority(callbackInfo); |
| 230 | objectInfo.changeObservers[priority] = observer; |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 231 | } |
| 232 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 233 | function ObjectInfoRemoveObserver(objectInfo, callback) { |
| 234 | if (!objectInfo.changeObservers) |
| 235 | return; |
| 236 | |
| 237 | if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { |
| 238 | if (callback === ObserverGetCallback(objectInfo.changeObservers)) |
| 239 | objectInfo.changeObservers = null; |
| 240 | return; |
| 241 | } |
| 242 | |
| 243 | var callbackInfo = CallbackInfoGet(callback); |
| 244 | var priority = CallbackInfoGetPriority(callbackInfo); |
| 245 | delete objectInfo.changeObservers[priority]; |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 246 | } |
| 247 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 248 | function ObjectInfoHasActiveObservers(objectInfo) { |
| 249 | if (IS_UNDEFINED(objectInfo) || !objectInfo.changeObservers) |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 250 | return false; |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 251 | |
| 252 | if (ChangeObserversIsOptimized(objectInfo.changeObservers)) |
| 253 | return ObserverIsActive(objectInfo.changeObservers, objectInfo); |
| 254 | |
| 255 | for (var priority in objectInfo.changeObservers) { |
| 256 | if (ObserverIsActive(objectInfo.changeObservers[priority], objectInfo)) |
| 257 | return true; |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 258 | } |
| 259 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 260 | return false; |
| 261 | } |
| 262 | |
| 263 | function ObjectInfoAddPerformingType(objectInfo, type) { |
| 264 | objectInfo.performing = objectInfo.performing || TypeMapCreate(); |
| 265 | TypeMapAddType(objectInfo.performing, type); |
| 266 | objectInfo.performingCount++; |
| 267 | } |
| 268 | |
| 269 | function ObjectInfoRemovePerformingType(objectInfo, type) { |
| 270 | objectInfo.performingCount--; |
| 271 | TypeMapRemoveType(objectInfo.performing, type); |
| 272 | } |
| 273 | |
| 274 | function ObjectInfoGetPerformingTypes(objectInfo) { |
| 275 | return objectInfo.performingCount > 0 ? objectInfo.performing : null; |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 276 | } |
| 277 | |
| 278 | function AcceptArgIsValid(arg) { |
| 279 | if (IS_UNDEFINED(arg)) |
| 280 | return true; |
| 281 | |
| 282 | if (!IS_SPEC_OBJECT(arg) || |
| 283 | !IS_NUMBER(arg.length) || |
| 284 | arg.length < 0) |
| 285 | return false; |
| 286 | |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 287 | return true; |
| 288 | } |
| 289 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 290 | // CallbackInfo's optimized state is just a number which represents its global |
| 291 | // priority. When a change record must be enqueued for the callback, it |
| 292 | // normalizes. When delivery clears any pending change records, it re-optimizes. |
| 293 | function CallbackInfoGet(callback) { |
| 294 | return callbackInfoMap.get(callback); |
jkummerow@chromium.org | 1048047 | 2013-07-17 08:22:15 +0000 | [diff] [blame] | 295 | } |
| 296 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 297 | function CallbackInfoGetOrCreate(callback) { |
| 298 | var callbackInfo = callbackInfoMap.get(callback); |
| 299 | if (!IS_UNDEFINED(callbackInfo)) |
| 300 | return callbackInfo; |
| 301 | |
| 302 | var priority = observationState.nextCallbackPriority++ |
| 303 | callbackInfoMap.set(callback, priority); |
| 304 | return priority; |
| 305 | } |
| 306 | |
| 307 | function CallbackInfoGetPriority(callbackInfo) { |
| 308 | if (IS_NUMBER(callbackInfo)) |
| 309 | return callbackInfo; |
| 310 | else |
| 311 | return callbackInfo.priority; |
| 312 | } |
| 313 | |
| 314 | function CallbackInfoNormalize(callback) { |
jkummerow@chromium.org | 1048047 | 2013-07-17 08:22:15 +0000 | [diff] [blame] | 315 | var callbackInfo = callbackInfoMap.get(callback); |
| 316 | if (IS_NUMBER(callbackInfo)) { |
| 317 | var priority = callbackInfo; |
| 318 | callbackInfo = new InternalArray; |
| 319 | callbackInfo.priority = priority; |
| 320 | callbackInfoMap.set(callback, callbackInfo); |
| 321 | } |
| 322 | return callbackInfo; |
| 323 | } |
| 324 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 325 | function ObjectObserve(object, callback, acceptList) { |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 326 | if (!IS_SPEC_OBJECT(object)) |
| 327 | throw MakeTypeError("observe_non_object", ["observe"]); |
| 328 | if (!IS_SPEC_FUNCTION(callback)) |
| 329 | throw MakeTypeError("observe_non_function", ["observe"]); |
yangguo@chromium.org | fb37721 | 2012-11-16 14:43:43 +0000 | [diff] [blame] | 330 | if (ObjectIsFrozen(callback)) |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 331 | throw MakeTypeError("observe_callback_frozen"); |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 332 | if (!AcceptArgIsValid(acceptList)) |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 333 | throw MakeTypeError("observe_accept_invalid"); |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 334 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 335 | var objectInfo = ObjectInfoGet(object); |
| 336 | ObjectInfoAddObserver(objectInfo, callback, acceptList); |
ulan@chromium.org | 8e8d882 | 2012-11-23 14:36:46 +0000 | [diff] [blame] | 337 | return object; |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 338 | } |
| 339 | |
| 340 | function ObjectUnobserve(object, callback) { |
| 341 | if (!IS_SPEC_OBJECT(object)) |
| 342 | throw MakeTypeError("observe_non_object", ["unobserve"]); |
yangguo@chromium.org | fb37721 | 2012-11-16 14:43:43 +0000 | [diff] [blame] | 343 | if (!IS_SPEC_FUNCTION(callback)) |
| 344 | throw MakeTypeError("observe_non_function", ["unobserve"]); |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 345 | |
| 346 | var objectInfo = objectInfoMap.get(object); |
| 347 | if (IS_UNDEFINED(objectInfo)) |
ulan@chromium.org | 8e8d882 | 2012-11-23 14:36:46 +0000 | [diff] [blame] | 348 | return object; |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 349 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 350 | ObjectInfoRemoveObserver(objectInfo, callback); |
ulan@chromium.org | 8e8d882 | 2012-11-23 14:36:46 +0000 | [diff] [blame] | 351 | return object; |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 352 | } |
| 353 | |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 354 | function ArrayObserve(object, callback) { |
| 355 | return ObjectObserve(object, callback, ['new', |
| 356 | 'updated', |
| 357 | 'deleted', |
| 358 | 'splice']); |
| 359 | } |
| 360 | |
| 361 | function ArrayUnobserve(object, callback) { |
| 362 | return ObjectUnobserve(object, callback); |
| 363 | } |
| 364 | |
dslomov@chromium.org | e97852d | 2013-09-12 09:02:59 +0000 | [diff] [blame] | 365 | function ObserverEnqueueIfActive(observer, objectInfo, changeRecord, |
| 366 | needsAccessCheck) { |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 367 | if (!ObserverIsActive(observer, objectInfo) || |
| 368 | !TypeMapHasType(ObserverGetAcceptTypes(observer), changeRecord.type)) { |
| 369 | return; |
| 370 | } |
| 371 | |
| 372 | var callback = ObserverGetCallback(observer); |
dslomov@chromium.org | e97852d | 2013-09-12 09:02:59 +0000 | [diff] [blame] | 373 | if (needsAccessCheck && |
| 374 | // Drop all splice records on the floor for access-checked objects |
| 375 | (changeRecord.type == 'splice' || |
| 376 | !%IsAccessAllowedForObserver( |
| 377 | callback, changeRecord.object, changeRecord.name))) { |
| 378 | return; |
| 379 | } |
| 380 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 381 | var callbackInfo = CallbackInfoNormalize(callback); |
| 382 | if (!observationState.pendingObservers) |
| 383 | observationState.pendingObservers = { __proto__: null }; |
jkummerow@chromium.org | 1048047 | 2013-07-17 08:22:15 +0000 | [diff] [blame] | 384 | observationState.pendingObservers[callbackInfo.priority] = callback; |
| 385 | callbackInfo.push(changeRecord); |
| 386 | %SetObserverDeliveryPending(); |
| 387 | } |
| 388 | |
dslomov@chromium.org | e97852d | 2013-09-12 09:02:59 +0000 | [diff] [blame] | 389 | function ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord, |
| 390 | skipAccessCheck) { |
mstarzinger@chromium.org | f705b50 | 2013-04-04 11:38:09 +0000 | [diff] [blame] | 391 | // TODO(rossberg): adjust once there is a story for symbols vs proxies. |
| 392 | if (IS_SYMBOL(changeRecord.name)) return; |
| 393 | |
dslomov@chromium.org | e97852d | 2013-09-12 09:02:59 +0000 | [diff] [blame] | 394 | var needsAccessCheck = !skipAccessCheck && |
| 395 | %IsAccessCheckNeeded(changeRecord.object); |
| 396 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 397 | if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { |
| 398 | var observer = objectInfo.changeObservers; |
dslomov@chromium.org | e97852d | 2013-09-12 09:02:59 +0000 | [diff] [blame] | 399 | ObserverEnqueueIfActive(observer, objectInfo, changeRecord, |
| 400 | needsAccessCheck); |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 401 | return; |
| 402 | } |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 403 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 404 | for (var priority in objectInfo.changeObservers) { |
| 405 | var observer = objectInfo.changeObservers[priority]; |
dslomov@chromium.org | e97852d | 2013-09-12 09:02:59 +0000 | [diff] [blame] | 406 | ObserverEnqueueIfActive(observer, objectInfo, changeRecord, |
| 407 | needsAccessCheck); |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 408 | } |
| 409 | } |
| 410 | |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 411 | function BeginPerformSplice(array) { |
| 412 | var objectInfo = objectInfoMap.get(array); |
| 413 | if (!IS_UNDEFINED(objectInfo)) |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 414 | ObjectInfoAddPerformingType(objectInfo, 'splice'); |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 415 | } |
| 416 | |
| 417 | function EndPerformSplice(array) { |
| 418 | var objectInfo = objectInfoMap.get(array); |
| 419 | if (!IS_UNDEFINED(objectInfo)) |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 420 | ObjectInfoRemovePerformingType(objectInfo, 'splice'); |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 421 | } |
| 422 | |
danno@chromium.org | 1fd77d5 | 2013-06-07 16:01:45 +0000 | [diff] [blame] | 423 | function EnqueueSpliceRecord(array, index, removed, addedCount) { |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 424 | var objectInfo = objectInfoMap.get(array); |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 425 | if (!ObjectInfoHasActiveObservers(objectInfo)) |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 426 | return; |
| 427 | |
| 428 | var changeRecord = { |
| 429 | type: 'splice', |
| 430 | object: array, |
| 431 | index: index, |
| 432 | removed: removed, |
| 433 | addedCount: addedCount |
| 434 | }; |
| 435 | |
jkummerow@chromium.org | c118402 | 2013-05-28 16:58:15 +0000 | [diff] [blame] | 436 | ObjectFreeze(changeRecord); |
| 437 | ObjectFreeze(changeRecord.removed); |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 438 | ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord); |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 439 | } |
| 440 | |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 441 | function NotifyChange(type, object, name, oldValue) { |
| 442 | var objectInfo = objectInfoMap.get(object); |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 443 | if (!ObjectInfoHasActiveObservers(objectInfo)) |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 444 | return; |
| 445 | |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 446 | var changeRecord = (arguments.length < 4) ? |
| 447 | { type: type, object: object, name: name } : |
| 448 | { type: type, object: object, name: name, oldValue: oldValue }; |
jkummerow@chromium.org | c118402 | 2013-05-28 16:58:15 +0000 | [diff] [blame] | 449 | ObjectFreeze(changeRecord); |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 450 | ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord); |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 451 | } |
| 452 | |
| 453 | var notifierPrototype = {}; |
| 454 | |
| 455 | function ObjectNotifierNotify(changeRecord) { |
| 456 | if (!IS_SPEC_OBJECT(this)) |
| 457 | throw MakeTypeError("called_on_non_object", ["notify"]); |
| 458 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 459 | var objectInfo = ObjectInfoGetFromNotifier(this); |
| 460 | if (IS_UNDEFINED(objectInfo)) |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 461 | throw MakeTypeError("observe_notify_non_notifier"); |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 462 | if (!IS_STRING(changeRecord.type)) |
| 463 | throw MakeTypeError("observe_type_non_string"); |
| 464 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 465 | if (!ObjectInfoHasActiveObservers(objectInfo)) |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 466 | return; |
| 467 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 468 | var newRecord = { object: ObjectInfoGetObject(objectInfo) }; |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 469 | for (var prop in changeRecord) { |
yangguo@chromium.org | a6bbcc8 | 2012-12-21 12:35:02 +0000 | [diff] [blame] | 470 | if (prop === 'object') continue; |
yangguo@chromium.org | fb37721 | 2012-11-16 14:43:43 +0000 | [diff] [blame] | 471 | %DefineOrRedefineDataProperty(newRecord, prop, changeRecord[prop], |
| 472 | READ_ONLY + DONT_DELETE); |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 473 | } |
jkummerow@chromium.org | c118402 | 2013-05-28 16:58:15 +0000 | [diff] [blame] | 474 | ObjectFreeze(newRecord); |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 475 | |
dslomov@chromium.org | e97852d | 2013-09-12 09:02:59 +0000 | [diff] [blame] | 476 | ObjectInfoEnqueueChangeRecord(objectInfo, newRecord, |
| 477 | true /* skip access check */); |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 478 | } |
| 479 | |
dslomov@chromium.org | e97852d | 2013-09-12 09:02:59 +0000 | [diff] [blame] | 480 | function ObjectNotifierPerformChange(changeType, changeFn) { |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 481 | if (!IS_SPEC_OBJECT(this)) |
| 482 | throw MakeTypeError("called_on_non_object", ["performChange"]); |
| 483 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 484 | var objectInfo = ObjectInfoGetFromNotifier(this); |
| 485 | |
| 486 | if (IS_UNDEFINED(objectInfo)) |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 487 | throw MakeTypeError("observe_notify_non_notifier"); |
| 488 | if (!IS_STRING(changeType)) |
| 489 | throw MakeTypeError("observe_perform_non_string"); |
| 490 | if (!IS_SPEC_FUNCTION(changeFn)) |
| 491 | throw MakeTypeError("observe_perform_non_function"); |
| 492 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 493 | ObjectInfoAddPerformingType(objectInfo, changeType); |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 494 | try { |
dslomov@chromium.org | e97852d | 2013-09-12 09:02:59 +0000 | [diff] [blame] | 495 | %_CallFunction(void 0, changeFn); |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 496 | } finally { |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 497 | ObjectInfoRemovePerformingType(objectInfo, changeType); |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 498 | } |
| 499 | } |
| 500 | |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 501 | function ObjectGetNotifier(object) { |
| 502 | if (!IS_SPEC_OBJECT(object)) |
| 503 | throw MakeTypeError("observe_non_object", ["getNotifier"]); |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 504 | |
yangguo@chromium.org | a6bbcc8 | 2012-12-21 12:35:02 +0000 | [diff] [blame] | 505 | if (ObjectIsFrozen(object)) return null; |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 506 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 507 | var objectInfo = ObjectInfoGet(object); |
| 508 | return ObjectInfoGetNotifier(objectInfo); |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 509 | } |
| 510 | |
jkummerow@chromium.org | 1048047 | 2013-07-17 08:22:15 +0000 | [diff] [blame] | 511 | function CallbackDeliverPending(callback) { |
| 512 | var callbackInfo = callbackInfoMap.get(callback); |
| 513 | if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) |
yangguo@chromium.org | a6bbcc8 | 2012-12-21 12:35:02 +0000 | [diff] [blame] | 514 | return false; |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 515 | |
jkummerow@chromium.org | 1048047 | 2013-07-17 08:22:15 +0000 | [diff] [blame] | 516 | // Clear the pending change records from callback and return it to its |
| 517 | // "optimized" state. |
| 518 | var priority = callbackInfo.priority; |
| 519 | callbackInfoMap.set(callback, priority); |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 520 | |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 521 | if (observationState.pendingObservers) |
| 522 | delete observationState.pendingObservers[priority]; |
| 523 | |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 524 | var delivered = []; |
jkummerow@chromium.org | 1048047 | 2013-07-17 08:22:15 +0000 | [diff] [blame] | 525 | %MoveArrayContents(callbackInfo, delivered); |
| 526 | |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 527 | try { |
dslomov@chromium.org | e97852d | 2013-09-12 09:02:59 +0000 | [diff] [blame] | 528 | %_CallFunction(void 0, delivered, callback); |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 529 | } catch (ex) {} |
yangguo@chromium.org | a6bbcc8 | 2012-12-21 12:35:02 +0000 | [diff] [blame] | 530 | return true; |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 531 | } |
| 532 | |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 533 | function ObjectDeliverChangeRecords(callback) { |
| 534 | if (!IS_SPEC_FUNCTION(callback)) |
| 535 | throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); |
| 536 | |
jkummerow@chromium.org | 1048047 | 2013-07-17 08:22:15 +0000 | [diff] [blame] | 537 | while (CallbackDeliverPending(callback)) {} |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 538 | } |
| 539 | |
| 540 | function DeliverChangeRecords() { |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 541 | while (observationState.pendingObservers) { |
yangguo@chromium.org | fb37721 | 2012-11-16 14:43:43 +0000 | [diff] [blame] | 542 | var pendingObservers = observationState.pendingObservers; |
hpayer@chromium.org | c5d4971 | 2013-09-11 08:25:48 +0000 | [diff] [blame] | 543 | observationState.pendingObservers = null; |
yangguo@chromium.org | fb37721 | 2012-11-16 14:43:43 +0000 | [diff] [blame] | 544 | for (var i in pendingObservers) { |
jkummerow@chromium.org | 1048047 | 2013-07-17 08:22:15 +0000 | [diff] [blame] | 545 | CallbackDeliverPending(pendingObservers[i]); |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 546 | } |
| 547 | } |
| 548 | } |
| 549 | |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 550 | function SetupObjectObserve() { |
| 551 | %CheckIsBootstrapping(); |
| 552 | InstallFunctions($Object, DONT_ENUM, $Array( |
| 553 | "deliverChangeRecords", ObjectDeliverChangeRecords, |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 554 | "getNotifier", ObjectGetNotifier, |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 555 | "observe", ObjectObserve, |
| 556 | "unobserve", ObjectUnobserve |
| 557 | )); |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 558 | InstallFunctions($Array, DONT_ENUM, $Array( |
| 559 | "observe", ArrayObserve, |
| 560 | "unobserve", ArrayUnobserve |
| 561 | )); |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 562 | InstallFunctions(notifierPrototype, DONT_ENUM, $Array( |
jkummerow@chromium.org | 4e308cf | 2013-05-17 13:39:16 +0000 | [diff] [blame] | 563 | "notify", ObjectNotifierNotify, |
| 564 | "performChange", ObjectNotifierPerformChange |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 565 | )); |
danno@chromium.org | 72204d5 | 2012-10-31 10:02:10 +0000 | [diff] [blame] | 566 | } |
| 567 | |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 568 | SetupObjectObserve(); |