Class: Puppeteer::WaitTask
- Inherits:
-
Object
- Object
- Puppeteer::WaitTask
show all
- Defined in:
- lib/puppeteer/wait_task.rb
Defined Under Namespace
Classes: TerminatedError, TimeoutError
Constant Summary
collapse
- WAIT_FOR_PREDICATE_PAGE_FUNCTION =
<<~JAVASCRIPT
function _(root, predicateBody, polling, ...args) {
const predicate = new Function('...args', predicateBody);
const observedRoot = root || document;
if (polling === 'mutation' && typeof MutationObserver === 'undefined') {
polling = 'raf';
}
function createDeferred() {
let resolve;
let reject;
let finished = false;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return {
promise,
resolve: (value) => {
if (finished) return;
finished = true;
resolve(value);
},
reject: (error) => {
if (finished) return;
finished = true;
reject(error);
},
finished: () => finished,
};
}
class MutationPoller {
constructor(fn, root) {
this.fn = fn;
this.root = root;
this.observer = null;
this.deferred = null;
}
async start() {
this.deferred = createDeferred();
const result = await this.fn();
if (result) {
this.deferred.resolve(result);
return;
}
this.observer = new MutationObserver(async () => {
const result = await this.fn();
if (!result) {
return;
}
this.deferred.resolve(result);
await this.stop();
});
this.observer.observe(this.root, {
childList: true,
subtree: true,
attributes: true,
});
}
async stop() {
if (!this.deferred) {
return;
}
if (!this.deferred.finished()) {
this.deferred.reject(new Error('Polling stopped'));
}
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
}
result() {
if (!this.deferred) {
return Promise.reject(new Error('Polling never started'));
}
return this.deferred.promise;
}
}
class RAFPoller {
constructor(fn) {
this.fn = fn;
this.deferred = null;
this.rafId = null;
}
async start() {
this.deferred = createDeferred();
const result = await this.fn();
if (result) {
this.deferred.resolve(result);
return;
}
const poll = async () => {
if (!this.deferred || this.deferred.finished()) {
return;
}
const result = await this.fn();
if (result) {
this.deferred.resolve(result);
await this.stop();
} else {
this.rafId = requestAnimationFrame(poll);
}
};
this.rafId = requestAnimationFrame(poll);
}
async stop() {
if (!this.deferred) {
return;
}
if (!this.deferred.finished()) {
this.deferred.reject(new Error('Polling stopped'));
}
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
}
result() {
if (!this.deferred) {
return Promise.reject(new Error('Polling never started'));
}
return this.deferred.promise;
}
}
class IntervalPoller {
constructor(fn, ms) {
this.fn = fn;
this.ms = ms;
this.interval = null;
this.deferred = null;
}
async start() {
this.deferred = createDeferred();
const result = await this.fn();
if (result) {
this.deferred.resolve(result);
return;
}
this.interval = setInterval(async () => {
const result = await this.fn();
if (!result) {
return;
}
this.deferred.resolve(result);
await this.stop();
}, this.ms);
}
async stop() {
if (!this.deferred) {
return;
}
if (!this.deferred.finished()) {
this.deferred.reject(new Error('Polling stopped'));
}
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
}
result() {
if (!this.deferred) {
return Promise.reject(new Error('Polling never started'));
}
return this.deferred.promise;
}
}
const runner = () => predicate(...args);
let poller;
if (polling === 'raf') {
poller = new RAFPoller(runner);
} else if (polling === 'mutation') {
poller = new MutationPoller(runner, observedRoot);
} else if (typeof polling === 'number') {
poller = new IntervalPoller(runner, polling);
} else {
throw new Error('Unknown polling option: ' + polling);
}
poller.start();
return poller;
}
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
47
48
49
50
51
52
53
54
|
# 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)
if polling < 0
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 = build_predicate_body(predicate_body)
@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 = Async::Promise.new
@poller_handle = nil
@generic_error = Puppeteer::Error.new('Waiting failed')
if timeout && timeout > 0
timeout_error = TimeoutError.new(timeout: timeout)
@timeout_task = Async do |task|
task.sleep(timeout / 1000.0)
@timeout_task = nil
terminate(timeout_error) unless @timeout_cleared
end
end
async_rerun
end
|
Instance Method Details
57
58
59
|
# File 'lib/puppeteer/wait_task.rb', line 57
def await_promise
@promise.wait
end
|
#rerun ⇒ Object
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
|
# File 'lib/puppeteer/wait_task.rb', line 71
def rerun
run_count = (@run_count += 1)
context = nil
success = nil
error = nil
return if @terminated || run_count != @run_count
reset_poller
begin
context = @dom_world.execution_context
if @binding_function
@dom_world.add_binding_to_context(context, @binding_function)
end
return if @terminated || run_count != @run_count
@poller_handle = context.evaluate_handle(
WAIT_FOR_PREDICATE_PAGE_FUNCTION,
@root,
@predicate_body,
@polling,
*@args,
)
success = @poller_handle.evaluate_handle('poller => poller.result()')
rescue => err
error = err
end
return if @terminated || run_count != @run_count
if error
bad_error = get_bad_error(error)
if bad_error
@generic_error.cause = bad_error
terminate(@generic_error)
else
reset_poller
end
return
end
@promise.resolve(success) unless @promise.resolved?
cleanup
end
|
#terminate(error = nil) ⇒ Object
61
62
63
64
65
66
67
68
69
|
# File 'lib/puppeteer/wait_task.rb', line 61
def terminate(error = nil)
return if @terminated
@terminated = true
if error && !@promise.resolved?
@promise.reject(error)
end
cleanup
end
|