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 _(root, predicateBody, polling, timeout, ...args) {
    const predicate = new Function('...args', predicateBody);
    root = root || document
    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<*>}
     */
    async function pollMutation() {
        const success = await predicate(root, ...args);
        if (success) return Promise.resolve(success);
        let fulfill;
        const result = new Promise((x) => (fulfill = x));
        const observer = new MutationObserver(async () => {
            if (timedOut) {
                observer.disconnect();
                fulfill();
            }
            const success = await predicate(root, ...args);
            if (success) {
                observer.disconnect();
                fulfill(success);
            }
        });
        observer.observe(root, {
            childList: true,
            subtree: true,
            attributes: true,
        });
        return result;
    }
    async function pollRaf() {
        let fulfill;
        const result = new Promise((x) => (fulfill = x));
        await onRaf();
        return result;
        async function onRaf() {
            if (timedOut) {
                fulfill();
                return;
            }
            const success = await predicate(root, ...args);
            if (success) fulfill(success);
            else requestAnimationFrame(onRaf);
        }
    }
    async function pollInterval(pollInterval) {
        let fulfill;
        const result = new Promise((x) => (fulfill = x));
        await onTimeout();
        return result;
        async function onTimeout() {
            if (timedOut) {
                fulfill();
                return;
            }
            const success = await predicate(root, ...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, root: 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
46
# File 'lib/puppeteer/wait_task.rb', line 12

def initialize(dom_world:, predicate_body:, title:, polling:, timeout:, args: [], binding_function: nil, root: 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
  @root = root
  @predicate_body = "return (#{predicate_body})(...args);"
  @args = args
  @binding_function = binding_function
  @run_count = 0
  @dom_world.task_manager.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 > 0
    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:



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

def await_promise
  @promise.value!
end

#rerunObject



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

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,
      @root,
      @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



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

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