The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.

Please select the desired protocol below to get the URL.

This URL has Read-Only access.

Statistics
| Branch: | Revision:

main_repo / deps / v8 / src / object-observe.js @ f230a1cf

History | View | Annotate | Download (18.1 KB)

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

    
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

    
59
var observationState = %GetObservationState();
60
if (IS_UNDEFINED(observationState.callbackInfoMap)) {
61
  observationState.callbackInfoMap = %ObservationWeakMapCreate();
62
  observationState.objectInfoMap = %ObservationWeakMapCreate();
63
  observationState.notifierObjectInfoMap = %ObservationWeakMapCreate();
64
  observationState.pendingObservers = null;
65
  observationState.nextCallbackPriority = 0;
66
}
67

    
68
function ObservationWeakMap(map) {
69
  this.map_ = map;
70
}
71

    
72
ObservationWeakMap.prototype = {
73
  get: function(key) {
74
    key = %UnwrapGlobalProxy(key);
75
    if (!IS_SPEC_OBJECT(key)) return UNDEFINED;
76
    return %WeakCollectionGet(this.map_, key);
77
  },
78
  set: function(key, value) {
79
    key = %UnwrapGlobalProxy(key);
80
    if (!IS_SPEC_OBJECT(key)) return UNDEFINED;
81
    %WeakCollectionSet(this.map_, key, value);
82
  },
83
  has: function(key) {
84
    return !IS_UNDEFINED(this.get(key));
85
  }
86
};
87

    
88
var callbackInfoMap =
89
    new ObservationWeakMap(observationState.callbackInfoMap);
90
var objectInfoMap = new ObservationWeakMap(observationState.objectInfoMap);
91
var notifierObjectInfoMap =
92
    new ObservationWeakMap(observationState.notifierObjectInfoMap);
93

    
94
function TypeMapCreate() {
95
  return { __proto__: null };
96
}
97

    
98
function TypeMapAddType(typeMap, type, ignoreDuplicate) {
99
  typeMap[type] = ignoreDuplicate ? 1 : (typeMap[type] || 0) + 1;
100
}
101

    
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)
120
    return true;
121

    
122
  for (var type in typeMap1) {
123
    if (TypeMapHasType(typeMap1, type) && TypeMapHasType(typeMap2, type))
124
      return false;
125
  }
126

    
127
  return true;
128
}
129

    
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
  };
149
}
150

    
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);
190
  }
191

    
192
  return objectInfo.notifier;
193
}
194

    
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;
226
  }
227

    
228
  ObjectInfoNormalizeChangeObservers(objectInfo);
229
  var priority = CallbackInfoGetPriority(callbackInfo);
230
  objectInfo.changeObservers[priority] = observer;
231
}
232

    
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];
246
}
247

    
248
function ObjectInfoHasActiveObservers(objectInfo) {
249
  if (IS_UNDEFINED(objectInfo) || !objectInfo.changeObservers)
250
    return false;
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;
258
  }
259

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

    
287
  return true;
288
}
289

    
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);
295
}
296

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

    
325
function ObjectObserve(object, callback, acceptList) {
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"]);
330
  if (ObjectIsFrozen(callback))
331
    throw MakeTypeError("observe_callback_frozen");
332
  if (!AcceptArgIsValid(acceptList))
333
    throw MakeTypeError("observe_accept_invalid");
334

    
335
  var objectInfo = ObjectInfoGet(object);
336
  ObjectInfoAddObserver(objectInfo, callback, acceptList);
337
  return object;
338
}
339

    
340
function ObjectUnobserve(object, callback) {
341
  if (!IS_SPEC_OBJECT(object))
342
    throw MakeTypeError("observe_non_object", ["unobserve"]);
343
  if (!IS_SPEC_FUNCTION(callback))
344
    throw MakeTypeError("observe_non_function", ["unobserve"]);
345

    
346
  var objectInfo = objectInfoMap.get(object);
347
  if (IS_UNDEFINED(objectInfo))
348
    return object;
349

    
350
  ObjectInfoRemoveObserver(objectInfo, callback);
351
  return object;
352
}
353

    
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

    
365
function ObserverEnqueueIfActive(observer, objectInfo, changeRecord,
366
                                 needsAccessCheck) {
367
  if (!ObserverIsActive(observer, objectInfo) ||
368
      !TypeMapHasType(ObserverGetAcceptTypes(observer), changeRecord.type)) {
369
    return;
370
  }
371

    
372
  var callback = ObserverGetCallback(observer);
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

    
381
  var callbackInfo = CallbackInfoNormalize(callback);
382
  if (!observationState.pendingObservers)
383
    observationState.pendingObservers = { __proto__: null };
384
  observationState.pendingObservers[callbackInfo.priority] = callback;
385
  callbackInfo.push(changeRecord);
386
  %SetObserverDeliveryPending();
387
}
388

    
389
function ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord,
390
                                       skipAccessCheck) {
391
  // TODO(rossberg): adjust once there is a story for symbols vs proxies.
392
  if (IS_SYMBOL(changeRecord.name)) return;
393

    
394
  var needsAccessCheck = !skipAccessCheck &&
395
      %IsAccessCheckNeeded(changeRecord.object);
396

    
397
  if (ChangeObserversIsOptimized(objectInfo.changeObservers)) {
398
    var observer = objectInfo.changeObservers;
399
    ObserverEnqueueIfActive(observer, objectInfo, changeRecord,
400
                            needsAccessCheck);
401
    return;
402
  }
403

    
404
  for (var priority in objectInfo.changeObservers) {
405
    var observer = objectInfo.changeObservers[priority];
406
    ObserverEnqueueIfActive(observer, objectInfo, changeRecord,
407
                            needsAccessCheck);
408
  }
409
}
410

    
411
function BeginPerformSplice(array) {
412
  var objectInfo = objectInfoMap.get(array);
413
  if (!IS_UNDEFINED(objectInfo))
414
    ObjectInfoAddPerformingType(objectInfo, 'splice');
415
}
416

    
417
function EndPerformSplice(array) {
418
  var objectInfo = objectInfoMap.get(array);
419
  if (!IS_UNDEFINED(objectInfo))
420
    ObjectInfoRemovePerformingType(objectInfo, 'splice');
421
}
422

    
423
function EnqueueSpliceRecord(array, index, removed, addedCount) {
424
  var objectInfo = objectInfoMap.get(array);
425
  if (!ObjectInfoHasActiveObservers(objectInfo))
426
    return;
427

    
428
  var changeRecord = {
429
    type: 'splice',
430
    object: array,
431
    index: index,
432
    removed: removed,
433
    addedCount: addedCount
434
  };
435

    
436
  ObjectFreeze(changeRecord);
437
  ObjectFreeze(changeRecord.removed);
438
  ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord);
439
}
440

    
441
function NotifyChange(type, object, name, oldValue) {
442
  var objectInfo = objectInfoMap.get(object);
443
  if (!ObjectInfoHasActiveObservers(objectInfo))
444
    return;
445

    
446
  var changeRecord = (arguments.length < 4) ?
447
      { type: type, object: object, name: name } :
448
      { type: type, object: object, name: name, oldValue: oldValue };
449
  ObjectFreeze(changeRecord);
450
  ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord);
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

    
459
  var objectInfo = ObjectInfoGetFromNotifier(this);
460
  if (IS_UNDEFINED(objectInfo))
461
    throw MakeTypeError("observe_notify_non_notifier");
462
  if (!IS_STRING(changeRecord.type))
463
    throw MakeTypeError("observe_type_non_string");
464

    
465
  if (!ObjectInfoHasActiveObservers(objectInfo))
466
    return;
467

    
468
  var newRecord = { object: ObjectInfoGetObject(objectInfo) };
469
  for (var prop in changeRecord) {
470
    if (prop === 'object') continue;
471
    %DefineOrRedefineDataProperty(newRecord, prop, changeRecord[prop],
472
        READ_ONLY + DONT_DELETE);
473
  }
474
  ObjectFreeze(newRecord);
475

    
476
  ObjectInfoEnqueueChangeRecord(objectInfo, newRecord,
477
                                true /* skip access check */);
478
}
479

    
480
function ObjectNotifierPerformChange(changeType, changeFn) {
481
  if (!IS_SPEC_OBJECT(this))
482
    throw MakeTypeError("called_on_non_object", ["performChange"]);
483

    
484
  var objectInfo = ObjectInfoGetFromNotifier(this);
485

    
486
  if (IS_UNDEFINED(objectInfo))
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

    
493
  ObjectInfoAddPerformingType(objectInfo, changeType);
494
  try {
495
    %_CallFunction(UNDEFINED, changeFn);
496
  } finally {
497
    ObjectInfoRemovePerformingType(objectInfo, changeType);
498
  }
499
}
500

    
501
function ObjectGetNotifier(object) {
502
  if (!IS_SPEC_OBJECT(object))
503
    throw MakeTypeError("observe_non_object", ["getNotifier"]);
504

    
505
  if (ObjectIsFrozen(object)) return null;
506

    
507
  var objectInfo = ObjectInfoGet(object);
508
  return ObjectInfoGetNotifier(objectInfo);
509
}
510

    
511
function CallbackDeliverPending(callback) {
512
  var callbackInfo = callbackInfoMap.get(callback);
513
  if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo))
514
    return false;
515

    
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);
520

    
521
  if (observationState.pendingObservers)
522
    delete observationState.pendingObservers[priority];
523

    
524
  var delivered = [];
525
  %MoveArrayContents(callbackInfo, delivered);
526

    
527
  try {
528
    %_CallFunction(UNDEFINED, delivered, callback);
529
  } catch (ex) {}
530
  return true;
531
}
532

    
533
function ObjectDeliverChangeRecords(callback) {
534
  if (!IS_SPEC_FUNCTION(callback))
535
    throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]);
536

    
537
  while (CallbackDeliverPending(callback)) {}
538
}
539

    
540
function DeliverChangeRecords() {
541
  while (observationState.pendingObservers) {
542
    var pendingObservers = observationState.pendingObservers;
543
    observationState.pendingObservers = null;
544
    for (var i in pendingObservers) {
545
      CallbackDeliverPending(pendingObservers[i]);
546
    }
547
  }
548
}
549

    
550
function SetupObjectObserve() {
551
  %CheckIsBootstrapping();
552
  InstallFunctions($Object, DONT_ENUM, $Array(
553
    "deliverChangeRecords", ObjectDeliverChangeRecords,
554
    "getNotifier", ObjectGetNotifier,
555
    "observe", ObjectObserve,
556
    "unobserve", ObjectUnobserve
557
  ));
558
  InstallFunctions($Array, DONT_ENUM, $Array(
559
    "observe", ArrayObserve,
560
    "unobserve", ArrayUnobserve
561
  ));
562
  InstallFunctions(notifierPrototype, DONT_ENUM, $Array(
563
    "notify", ObjectNotifierNotify,
564
    "performChange", ObjectNotifierPerformChange
565
  ));
566
}
567

    
568
SetupObjectObserve();