Class: Dommy::MutationObserver

Inherits:
Object
  • Object
show all
Includes:
Bridge::Methods
Defined in:
lib/dommy/mutation_observer.rb

Instance Method Summary collapse

Methods included from Bridge::Methods

included

Constructor Details

#initialize(window, callback) ⇒ MutationObserver

Returns a new instance of MutationObserver.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/dommy/mutation_observer.rb', line 73

def initialize(window, callback)
  @window = window
  @document = window.document
  @callback = callback
  @observed = []
  @records = []
  @scheduled = false
  @registered_docs = []
  # Transient registered observers (WHATWG DOM): when a node is removed from
  # an observed subtree, its subtree keeps being observed until the next
  # microtask checkpoint, so mutations inside the just-removed subtree (e.g.
  # removing its children in the same task) are still recorded. Each entry is
  # { root: wrapped removed node, source: the registration that matched }.
  @transients = []
end

Instance Method Details

#__js_call__(method, args) ⇒ Object



91
92
93
94
95
96
97
98
99
100
# File 'lib/dommy/mutation_observer.rb', line 91

def __js_call__(method, args)
  case method
  when "observe"
    observe(args[0], args[1])
  when "disconnect"
    disconnect
  when "takeRecords"
    take_records
  end
end

#add_transient(root_wrapped, source_entry) ⇒ Object

Register a transient registered observer for a node just removed from an observed subtree (see @transients). Carries the matched registration's options so subsequent mutations inside the removed subtree record the same types. Deduped by node identity.



137
138
139
140
141
142
143
# File 'lib/dommy/mutation_observer.rb', line 137

def add_transient(root_wrapped, source_entry)
  return unless root_wrapped && source_entry && source_entry[:subtree]
  return if @transients.any? { |t| t[:root].equal?(root_wrapped) }

  @transients << {root: root_wrapped, source: source_entry}
  nil
end

#enqueue(record) ⇒ Object



145
146
147
148
149
150
151
152
# File 'lib/dommy/mutation_observer.rb', line 145

def enqueue(record)
  @records << record
  return nil if @scheduled

  @scheduled = true
  @window.scheduler.queue_microtask(proc { flush })
  nil
end

#find_matching_entry(target_wrapped) ⇒ Object

Find the observer entry that matches target_wrapped. Returns the entry with options (attributes, attributeFilter, etc.) or nil if target doesn't match any observed scope.



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/dommy/mutation_observer.rb', line 111

def find_matching_entry(target_wrapped)
  entry = @observed.find do |e|
    observed_wrapped = e[:target]
    next false unless observed_wrapped

    if observed_wrapped.is_a?(Document)
      Internal::ObserverMatcher.matches_document?(target_wrapped, subtree: e[:subtree])
    else
      Internal::ObserverMatcher.matches?(observed_wrapped, target_wrapped, subtree: e[:subtree])
    end
  end
  return entry if entry

  # A transient registered observer matches the removed node itself and its
  # (now-detached) descendants, with the source registration's options.
  transient = @transients.find do |t|
    root = t[:root]
    root && (root.equal?(target_wrapped) || Internal::ObserverMatcher.matches?(root, target_wrapped, subtree: true))
  end
  transient && transient[:source]
end

#matches_wrapped?(target_wrapped) ⇒ Boolean

Matches a wrapped target against this observer's scope. Called by MutationCoordinator.

Returns:

  • (Boolean)


104
105
106
# File 'lib/dommy/mutation_observer.rb', line 104

def matches_wrapped?(target_wrapped)
  find_matching_entry(target_wrapped) != nil
end

#recordsObject

Public: introspection used by linkedom-style tests that peek at pending records without draining (observer.records[0]).



156
157
158
# File 'lib/dommy/mutation_observer.rb', line 156

def records
  @records.dup
end