Blame view

lib/jsdom/living/mutation-observer/MutationObserver-impl.js 3.51 KB
858f2bdf5   Boyan Georgiev   fixes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
  "use strict";
  
  const { wrapperForImpl } = require("../generated/utils");
  
  // If we were to implement the MutationObserver by spec, the MutationObservers will not be collected by the GC because
  // all the MO are kept in a mutation observer list (https://github.com/jsdom/jsdom/pull/2398/files#r238123889). The
  // mutation observer list is primarily used to invoke the mutation observer callback in the same order than the
  // mutation observer creation.
  // In order to get around this issue, we will assign an increasing id for each mutation observer, this way we would be
  // able to invoke the callback in the creation order without having to keep a list of all the mutation observers.
  let mutationObserverId = 0;
  
  // https://dom.spec.whatwg.org/#mutationobserver
  class MutationObserverImpl {
    // https://dom.spec.whatwg.org/#dom-mutationobserver-mutationobserver
    constructor(globalObject, args) {
      const [callback] = args;
  
      this._callback = callback;
      this._nodeList = [];
      this._recordQueue = [];
  
      this._id = ++mutationObserverId;
    }
  
    // https://dom.spec.whatwg.org/#dom-mutationobserver-observe
    observe(target, options) {
      if (("attributeOldValue" in options || "attributeFilter" in options) && !("attributes" in options)) {
        options.attributes = true;
      }
  
      if ("characterDataOldValue" in options & !("characterData" in options)) {
        options.characterData = true;
      }
  
      if (!options.childList && !options.attributes && !options.characterData) {
        throw new TypeError("The options object must set at least one of 'attributes', 'characterData', or 'childList' " +
          "to true.");
      } else if (options.attributeOldValue && !options.attributes) {
        throw new TypeError("The options object may only set 'attributeOldValue' to true when 'attributes' is true or " +
          "not present.");
      } else if (("attributeFilter" in options) && !options.attributes) {
        throw new TypeError("The options object may only set 'attributeFilter' when 'attributes' is true or not " +
          "present.");
      } else if (options.characterDataOldValue && !options.characterData) {
        throw new TypeError("The options object may only set 'characterDataOldValue' to true when 'characterData' is " +
          "true or not present.");
      }
  
      const existingRegisteredObserver = target._registeredObserverList.find(registeredObserver => {
        return registeredObserver.observer === this;
      });
  
      if (existingRegisteredObserver) {
        for (const node of this._nodeList) {
          node._registeredObserverList = node._registeredObserverList.filter(registeredObserver => {
            return registeredObserver.source !== existingRegisteredObserver;
          });
        }
  
        existingRegisteredObserver.options = options;
      } else {
        target._registeredObserverList.push({
          observer: this,
          options
        });
  
        this._nodeList.push(target);
      }
    }
  
    // https://dom.spec.whatwg.org/#dom-mutationobserver-disconnect
    disconnect() {
      for (const node of this._nodeList) {
        node._registeredObserverList = node._registeredObserverList.filter(registeredObserver => {
          return registeredObserver.observer !== this;
        });
      }
  
      this._recordQueue = [];
    }
  
    // https://dom.spec.whatwg.org/#dom-mutationobserver-takerecords
    takeRecords() {
      // TODO: revisit if https://github.com/jsdom/webidl2js/pull/108 gets fixed.
      const records = this._recordQueue.map(wrapperForImpl);
      this._recordQueue = [];
  
      return records;
    }
  }
  
  module.exports = {
    implementation: MutationObserverImpl
  };