Class: Puppeteer::WaitTask

Inherits:
Object
  • Object
show all
Defined in:
lib/puppeteer/wait_task.rb

Defined Under Namespace

Classes: TerminatedError, TimeoutError

Constant Summary collapse

WAIT_FOR_PREDICATE_PAGE_FUNCTION =
<<~JAVASCRIPT
async function _(predicateBody, polling, timeout, ...args) {
    const predicate = new Function('...args', predicateBody);
    let timedOut = false;
    if (timeout)
        setTimeout(() => (timedOut = true), timeout);
    if (polling === 'raf')
        return await pollRaf();
    if (polling === 'mutation')
        return await pollMutation();
    if (typeof polling === 'number')
        return await pollInterval(polling);
    /**
     * @return {!Promise<*>}
     */
    function pollMutation() {
        const success = predicate(...args);
        if (success)
            return Promise.resolve(success);
        let fulfill;
        const result = new Promise((x) => (fulfill = x));
        const observer = new MutationObserver(() => {
            if (timedOut) {
                observer.disconnect();
                fulfill();
            }
            const success = predicate(...args);
            if (success) {
                observer.disconnect();
                fulfill(success);
            }
        });
        observer.observe(document, {
            childList: true,
            subtree: true,
            attributes: true,
        });
        return result;
    }
    function pollRaf() {
        let fulfill;
        const result = new Promise((x) => (fulfill = x));
        onRaf();
        return result;
        function onRaf() {
            if (timedOut) {
                fulfill();
                return;
            }
            const success = predicate(...args);
            if (success)
                fulfill(success);
            else
                requestAnimationFrame(onRaf);
        }
    }
    function pollInterval(pollInterval) {
        let fulfill;
        const result = new Promise((x) => (fulfill = x));
        onTimeout();
        return result;
        function onTimeout() {
            if (timedOut) {
                fulfill();
                return;
            }
            const success = predicate(...args);
            if (success)
                fulfill(success);
            else
                setTimeout(onTimeout, pollInterval);
        }
    }
}
JAVASCRIPT

Instance Method Summary collapse

Constructor Details

#initialize(dom_world:, predicate_body:, title:, polling:, timeout:, args: [], binding_function: nil) ⇒ WaitTask

Returns a new instance of WaitTask.



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
# File 'lib/puppeteer/wait_task.rb', line 12

def initialize(dom_world:, predicate_body:, title:, polling:, timeout:, args: [], binding_function: nil)
  if polling.is_a?(String)
    if polling != 'raf' && polling != 'mutation'
      raise ArgumentError.new("Unknown polling option: #{polling}")
    end
  elsif polling.is_a?(Numeric)
    unless polling.positive?
      raise ArgumentError.new("Cannot poll with non-positive interval: #{polling}")
    end
  else
    raise ArgumentError.new("Unknown polling options: #{polling}")
  end

  @dom_world = dom_world
  @polling = polling
  @timeout = timeout
  @predicate_body = "return (#{predicate_body})(...args);"
  @args = args
  @binding_function = binding_function
  @run_count = 0
  @dom_world.send(:_wait_tasks).add(self)
  if binding_function
    @dom_world.send(:_bound_functions)[binding_function.name] = binding_function
  end
  @promise = resolvable_future

  # Since page navigation requires us to re-install the pageScript, we should track
  # timeout on our end.
  if timeout
    timeout_error = TimeoutError.new(title: title, timeout: timeout)
    Concurrent::Promises.schedule(timeout / 1000.0) { terminate(timeout_error) unless @timeout_cleared }
  end
  async_rerun
end

Instance Method Details

#await_promisePuppeteer::JSHandle

Returns:



48
49
50
# File 'lib/puppeteer/wait_task.rb', line 48

def await_promise
  @promise.value!
end

#rerunObject



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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/puppeteer/wait_task.rb', line 58

def rerun
  run_count = (@run_count += 1)
  context = @dom_world.execution_context

  return if @terminated || run_count != @run_count
  if @binding_function
    @dom_world.add_binding_to_context(context, @binding_function)
  end
  return if @terminated || run_count != @run_count

  begin
    success = context.evaluate_handle(
      WAIT_FOR_PREDICATE_PAGE_FUNCTION,
      @predicate_body,
      @polling,
      @timeout,
      *@args,
    )
  rescue => err
    error = err
  end

  if @terminated || run_count != @run_count
    if success
      success.dispose
    end
    return
  end

  # Ignore timeouts in pageScript - we track timeouts ourselves.
  # If the frame's execution context has already changed, `frame.evaluate` will
  # throw an error - ignore this predicate run altogether.
  if !error && (@dom_world.evaluate("s => !s", success) rescue true)
    success.dispose
    return
  end

  # When the page is navigated, the promise is rejected.
  # We will try again in the new execution context.
  if error && error.message.include?('Execution context was destroyed')
    return
  end

  # We could have tried to evaluate in a context which was already
  # destroyed.
  if error && error.message.include?('Cannot find context with specified id')
    return
  end

  if error
    @promise.reject(error)
  else
    @promise.fulfill(success)
  end

  cleanup
end

#terminate(error) ⇒ Object



52
53
54
55
56
# File 'lib/puppeteer/wait_task.rb', line 52

def terminate(error)
  @terminated = true
  @promise.reject(error)
  cleanup
end