bidi2pdf 0.1.21 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93fc35bee7628afb6627782be91a2fc62caf14f8f365929c166f133d74adebfc
4
- data.tar.gz: ca9381a65bd24341c2e486f34fd47f920fbf996e540c54fb421129c2f175f29e
3
+ metadata.gz: '008d5b0569ba4b7f8bbcd4092da72ce14e5d0e42589a97813f7c64c8bc6a0d9f'
4
+ data.tar.gz: 8266f91bfa7606d843bed392cee203060cc3b13d98435cc9a0c4a679b5041285
5
5
  SHA512:
6
- metadata.gz: a457600c1c6a638ee87a28d4f6221918b754a065d6d07acef9e057a4a6a3dce7c0411947cf2c340cb0a5e0ff62171bd9e934f65ca9bd15d9d514f754de11746c
7
- data.tar.gz: 383a01704a6871e5e5aafcd7fdd7a97e499009fb7cba63e13adfe2f08c91ba190b78a9d2117e02fd22de1381686c61ab1c94f43fb2d6b6e92e45c9365cfb5122
6
+ metadata.gz: 27ce2475fa8d30bc9c5447353767c6d2c06db59f57daa7fab7e7a337e26703cc1a0df04439d2b8b68455e5a872138775321a7f31988beb02dba47df0dd06d48d
7
+ data.tar.gz: acb216b0c656916ee8e8ea797b1fccd026c67fa6260ac5f230f1c44190ffb00455fd83efb5238826c97af0f0051cf68a7335d09e51e0aaa25b85c2efdc802290
data/CHANGELOG.md CHANGED
@@ -8,10 +8,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
- [unreleased]: https://github.com/dieter-medium/bidi2pdf/compare/v0.1.21..HEAD
11
+ [unreleased]: https://github.com/dieter-medium/bidi2pdf/compare/v0.1.22..HEAD
12
12
 
13
13
  <!-- generated by git-cliff end -->
14
14
 
15
+ ## [0.1.22] - 2026-10-11
16
+
17
+ ### 🐛 Fixed
18
+ - Report a network-idle timeout as a timeout
19
+ - Let the network-idle wait time out cleanly
20
+
21
+ ### 🔄 Changed
22
+ - Merge pull request #164 from dieter-medium/fix/network-idle-timeout
23
+ - Merge pull request #163 from dieter-medium/feat/disable-script-execution
24
+
25
+ ### 🚀 Added
26
+ - Let a tab switch off script execution
27
+
15
28
  ## [0.1.21] - 2026-10-09
16
29
 
17
30
  ### 🐛 Fixed
@@ -555,7 +568,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
555
568
 
556
569
  ### 🔄 Released
557
570
 
558
- - [unreleased](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.21..HEAD)
571
+ - [unreleased](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.22..HEAD)
572
+ - [0.1.22](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.21..v0.1.22)
559
573
  - [0.1.21](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.20..v0.1.21)
560
574
  - [0.1.20](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.19..v0.1.20)
561
575
  - [0.1.19](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.18..v0.1.19)
data/README.md CHANGED
@@ -347,6 +347,9 @@ tab.navigate_to "https://example.com/invoice/14432423"
347
347
  # or inline
348
348
  # tab.inject_style content: "body { background-color: red; }"
349
349
 
350
+ # Printing HTML you do not trust? Switch its scripts off before navigating:
351
+ # tab.disable_script_execution
352
+
350
353
  tab.wait_until_network_idle
351
354
  tab.print("my.pdf")
352
355
 
@@ -334,15 +334,20 @@ module Bidi2pdf
334
334
  end
335
335
  end
336
336
 
337
- # Waits until the network is idle in the browser tab.
337
+ # Waits until the network is idle in the browser tab. A timeout does not raise: the
338
+ # network_idle.bidi2pdf event then carries +timed_out: true+, so subscribers never report a busy
339
+ # page as idle.
338
340
  #
339
341
  # @param [Integer] timeout The timeout duration in seconds. Defaults to 10.
340
342
  # @param [Float] poll_interval The polling interval in seconds. Defaults to 0.1.
343
+ # @return [Symbol] :idle or :timeout
341
344
  def wait_until_network_idle(timeout: 10, poll_interval: 0.1)
342
345
  Bidi2pdf.notification_service.instrument("network_idle.bidi2pdf") do |instrumentation_payload|
343
- network_events.wait_until_network_idle(timeout: timeout, poll_interval: poll_interval)
346
+ status = network_events.wait_until_network_idle(timeout: timeout, poll_interval: poll_interval)
344
347
 
345
348
  instrumentation_payload[:requests] = network_events.all_events.dup
349
+ instrumentation_payload[:timed_out] = status == :timeout
350
+ status
346
351
  end
347
352
  end
348
353
 
@@ -496,6 +501,18 @@ module Bidi2pdf
496
501
  client.send_cmd_and_wait(cmd) { |response| response }
497
502
  end
498
503
 
504
+ # Switches JavaScript off (or on again) for this tab, through Chrome's own
505
+ # Emulation.setScriptExecutionDisabled (CDP, via goog:cdp.sendCommand). Call it before
506
+ # navigating: it holds for every later navigation in the tab, so a page printed from untrusted
507
+ # HTML runs none of its scripts.
508
+ #
509
+ # @param [Boolean] disabled Defaults to true.
510
+ def disable_script_execution(disabled: true)
511
+ cmd = Commands::CdpSendCommand.new(method: "Emulation.setScriptExecutionDisabled", params: { value: disabled },
512
+ session: cdp_session)
513
+ client.send_cmd_and_wait(cmd) { |response| response }
514
+ end
515
+
499
516
  private
500
517
 
501
518
  def build_command_and_extractor(print_options)
@@ -95,24 +95,33 @@ module Bidi2pdf
95
95
  end
96
96
  end
97
97
 
98
+ # Waits until no request is in flight, or until +timeout+ seconds passed.
99
+ #
100
+ # @return [Symbol] :idle when every request completed, :timeout when some were still open -
101
+ # it never raises for a timeout, so a caller decides whether a busy page is good enough.
98
102
  def wait_until_network_idle(timeout: 10, poll_interval: 0.01)
99
103
  start_time = Time.now
100
104
 
101
105
  loop do
102
106
  unless events.values.any?(&:in_progress?)
103
107
  Bidi2pdf.logger.debug "✅ All network events completed."
104
- break
108
+ return :idle
105
109
  end
106
110
 
107
111
  if Time.now - start_time > timeout
108
- Bidi2pdf.logger.warn "⏰ Timeout while waiting for network events to complete. Still in progress: #{in_progress.map(&:id)}"
109
- # rubocop:enable Layout/LineLength
110
- break
112
+ log_idle_timeout
113
+ return :timeout
111
114
  end
112
115
 
113
116
  sleep(poll_interval)
114
117
  end
115
118
  end
119
+
120
+ # Names the requests still open when the wait gives up - it carries on, it does not raise.
121
+ def log_idle_timeout
122
+ in_progress = events.values.select(&:in_progress?)
123
+ Bidi2pdf.logger.warn "⏰ Timeout while waiting for network events to complete. Still in progress: #{in_progress.map(&:id)}"
124
+ end
116
125
  end
117
126
  end
118
127
  end
@@ -53,8 +53,9 @@ module Bidi2pdf
53
53
  write(event: "page_loaded")
54
54
  end
55
55
 
56
- def on_network_idle(_event)
57
- write(event: "network_idle")
56
+ # A wait that timed out is its own event: a consumer reading "network_idle" can trust it.
57
+ def on_network_idle(event)
58
+ write(event: event.payload[:timed_out] ? "network_idle_timeout" : "network_idle")
58
59
  end
59
60
 
60
61
  def on_console(event)
@@ -61,7 +61,7 @@ module Bidi2pdf
61
61
  def network_idle(event)
62
62
  return unless logger.info?
63
63
 
64
- requests = event.payload[:requests]
64
+ requests = event.payload[:requests] || []
65
65
  transfered = requests.map { |request| request.bytes_received || 0 }.sum
66
66
  status_counts = requests
67
67
  .group_by { |evt| evt.http_status_code || 0 }
@@ -70,8 +70,12 @@ module Bidi2pdf
70
70
  .map { |code, count| "#{code}: #{count}" }
71
71
  .join(", ")
72
72
 
73
- logger.info "Network was idle after #{event.duration.round(1)}ms, #{requests.size} requests, " \
74
- "transferred #{transfered} bytes (status codes: #{status_counts})"
73
+ summary = "#{requests.size} requests, transferred #{transfered} bytes (status codes: #{status_counts})"
74
+ if event.payload[:timed_out]
75
+ logger.warn "Network was still busy after #{event.duration.round(1)}ms (timed out), #{summary}"
76
+ else
77
+ logger.info "Network was idle after #{event.duration.round(1)}ms, #{summary}"
78
+ end
75
79
  end
76
80
 
77
81
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
@@ -196,7 +196,8 @@ module Bidi2pdf
196
196
  "properties" => {
197
197
  "schema_version" => { "const" => 1 },
198
198
  "t_ms" => { "type" => "integer", "description" => "milliseconds since the render started" },
199
- "event" => { "type" => "string", "enum" => %w[navigate page_loaded network_idle console print screenshot result] },
199
+ "event" => { "type" => "string", "enum" => %w[navigate page_loaded network_idle network_idle_timeout console print screenshot result],
200
+ "description" => "network_idle_timeout: the wait for an idle network gave up with requests still open" },
200
201
  "url" => { "type" => "string", "description" => "present on a navigate event" },
201
202
  "level" => { "type" => "string", "description" => "present on a console event" },
202
203
  "text" => { "type" => "string", "description" => "present on a console event" },
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bidi2pdf
4
- VERSION = "0.1.21"
4
+ VERSION = "0.1.22"
5
5
  end
@@ -169,10 +169,13 @@ module Bidi2pdf
169
169
  # @return [Object] The result from the style creation promise.
170
170
  def inject_style: (?url: untyped?, ?content: untyped?, ?id: untyped?) -> untyped
171
171
 
172
- # Waits until the network is idle in the browser tab.
172
+ # Waits until the network is idle in the browser tab. A timeout does not raise: the
173
+ # network_idle.bidi2pdf event then carries +timed_out: true+, so subscribers never report a busy
174
+ # page as idle.
173
175
  #
174
176
  # @param [Integer] timeout The timeout duration in seconds. Defaults to 10.
175
177
  # @param [Float] poll_interval The polling interval in seconds. Defaults to 0.1.
178
+ # @return [Symbol] :idle or :timeout
176
179
  def wait_until_network_idle: (?timeout: ::Integer, ?poll_interval: ::Float) -> untyped
177
180
 
178
181
  # Waits until the page is fully loaded in the browser tab.
@@ -227,6 +230,14 @@ module Bidi2pdf
227
230
  # @param [Float, nil] device_pixel_ratio Defaults to nil (browser default).
228
231
  def set_viewport: (width: untyped, height: untyped, ?device_pixel_ratio: untyped?) -> untyped
229
232
 
233
+ # Switches JavaScript off (or on again) for this tab, through Chrome's own
234
+ # Emulation.setScriptExecutionDisabled (CDP, via goog:cdp.sendCommand). Call it before
235
+ # navigating: it holds for every later navigation in the tab, so a page printed from untrusted
236
+ # HTML runs none of its scripts.
237
+ #
238
+ # @param [Boolean] disabled Defaults to true.
239
+ def disable_script_execution: (?disabled: bool) -> untyped
240
+
230
241
  private
231
242
 
232
243
  def build_command_and_extractor: (untyped print_options) -> ::Array[untyped]
@@ -24,7 +24,14 @@ module Bidi2pdf
24
24
 
25
25
  def log_network_traffic: (?format: ::Symbol) -> untyped
26
26
 
27
- def wait_until_network_idle: (?timeout: ::Integer, ?poll_interval: ::Float) -> untyped
27
+ # Waits until no request is in flight, or until +timeout+ seconds passed.
28
+ #
29
+ # @return [Symbol] :idle when every request completed, :timeout when some were still open -
30
+ # it never raises for a timeout, so a caller decides whether a busy page is good enough.
31
+ def wait_until_network_idle: (?timeout: ::Integer, ?poll_interval: ::Float) -> (:idle | :timeout | untyped)
32
+
33
+ # Names the requests still open when the wait gives up - it carries on, it does not raise.
34
+ def log_idle_timeout: () -> untyped
28
35
  end
29
36
  end
30
37
  end
@@ -31,7 +31,8 @@ module Bidi2pdf
31
31
 
32
32
  def on_page_loaded: (untyped _event) -> untyped
33
33
 
34
- def on_network_idle: (untyped _event) -> untyped
34
+ # A wait that timed out is its own event: a consumer reading "network_idle" can trust it.
35
+ def on_network_idle: (untyped event) -> untyped
35
36
 
36
37
  def on_console: (untyped event) -> untyped
37
38
 
@@ -26,7 +26,7 @@ module Bidi2pdf
26
26
 
27
27
  MANIFEST: { "$schema" => "https://json-schema.org/draft/2020-12/schema", "title" => "Bidi2pdf render manifest", "description" => "--manifest FILE output", "type" => "object", "required" => ::Array["schema_version" | "bidi2pdf_version" | "created_at" | "input" | "output" | "navigation" | "render"], "properties" => { "schema_version" => { "const" => 1 }, "bidi2pdf_version" => { "type" => "string" }, "created_at" => { "type" => "string", "format" => "date-time" }, "input" => { "type" => "object", "required" => ::Array["type"], "properties" => { "type" => { "type" => "string", "enum" => ::Array["url" | "html_file" | "stdin"] }, "url" => { "type" => ::Array["string" | "null"] } } }, "output" => { "type" => "object", "properties" => { "path" => { "type" => ::Array["string" | "null"] }, "pages" => { "type" => ::Array["integer" | "null"] }, "bytes" => { "type" => ::Array["integer" | "null"] }, "sha256" => { "type" => ::Array["string" | "null"] } } }, "browser" => { "type" => "object" }, "navigation" => untyped, "render" => { "type" => "object", "properties" => { "duration_ms" => { "type" => ::Array["integer" | "null"] } } }, "console" => untyped, "network_failures" => untyped, "warnings" => untyped, "headers" => { "type" => "object", "description" => "present only when custom headers were given; secret-bearing values are redacted" } } }
28
28
 
29
- EVENT: { "$schema" => "https://json-schema.org/draft/2020-12/schema", "title" => "Bidi2pdf --json-stream event", "description" => "one JSON object per stderr line while --json-stream is active. In structured mode (--json/--output -), the last line is always {event: \"result\", result: <the matching command's own schema - see \"result\"'s own oneOf>}. In human mode (--json-stream given alone), only progress events are emitted - there is no synthetic final \"result\" event, since there is no structured result being built; the human-readable log is the completion/failure signal.", "type" => "object", "required" => ::Array["schema_version" | "t_ms" | "event"], "properties" => { "schema_version" => { "const" => 1 }, "t_ms" => { "type" => "integer", "description" => "milliseconds since the render started" }, "event" => { "type" => "string", "enum" => ::Array["navigate" | "page_loaded" | "network_idle" | "console" | "print" | "screenshot" | "result"] }, "url" => { "type" => "string", "description" => "present on a navigate event" }, "level" => { "type" => "string", "description" => "present on a console event" }, "text" => { "type" => "string", "description" => "present on a console event" }, "result" => { "description" => "present only on the final \"result\" event (structured mode only); shape depends on which command produced this stream - each branch's own \"command\" value disambiguates", "oneOf" => ::Array[untyped] } } }
29
+ EVENT: { "$schema" => "https://json-schema.org/draft/2020-12/schema", "title" => "Bidi2pdf --json-stream event", "description" => "one JSON object per stderr line while --json-stream is active. In structured mode (--json/--output -), the last line is always {event: \"result\", result: <the matching command's own schema - see \"result\"'s own oneOf>}. In human mode (--json-stream given alone), only progress events are emitted - there is no synthetic final \"result\" event, since there is no structured result being built; the human-readable log is the completion/failure signal.", "type" => "object", "required" => ::Array["schema_version" | "t_ms" | "event"], "properties" => { "schema_version" => { "const" => 1 }, "t_ms" => { "type" => "integer", "description" => "milliseconds since the render started" }, "event" => { "type" => "string", "enum" => ::Array["navigate" | "page_loaded" | "network_idle" | "network_idle_timeout" | "console" | "print" | "screenshot" | "result"], "description" => "network_idle_timeout: the wait for an idle network gave up with requests still open" }, "url" => { "type" => "string", "description" => "present on a navigate event" }, "level" => { "type" => "string", "description" => "present on a console event" }, "text" => { "type" => "string", "description" => "present on a console event" }, "result" => { "description" => "present only on the final \"result\" event (structured mode only); shape depends on which command produced this stream - each branch's own \"command\" value disambiguates", "oneOf" => ::Array[untyped] } } }
30
30
 
31
31
  RECIPE_SOURCE: { "description" => "exactly one of url, file, stdin", "oneOf" => ::Array[{ "type" => "object", "required" => ::Array["url"], "properties" => { "url" => { "type" => "string" } }, "additionalProperties" => false } | { "type" => "object", "required" => ::Array["file"], "properties" => { "file" => { "type" => "string" } }, "additionalProperties" => false } | { "type" => "object", "required" => ::Array["stdin"], "properties" => { "stdin" => { "const" => true } }, "additionalProperties" => false }] }
32
32
 
@@ -1,3 +1,3 @@
1
1
  module Bidi2pdf
2
- VERSION: "0.1.21"
2
+ VERSION: "0.1.22"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bidi2pdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.21
4
+ version: 0.1.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dieter S.