Class: Bidi2pdf::Bidi::BrowserTab

Inherits:
Object
  • Object
show all
Includes:
JsLoggerHelper
Defined in:
lib/bidi2pdf/bidi/browser_tab.rb

Overview

Represents a browser tab for managing interactions and communication using the Bidi2pdf library. This class provides methods for creating browser tabs, managing cookies, navigating to URLs, executing scripts, handling network events, and general tab lifecycle management.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client, browsing_context_id, user_context_id) ⇒ BrowserTab

Initializes a new browser tab.

Parameters:

  • client (Object)

    The WebSocket client for communication.

  • browsing_context_id (String)

    The ID of the browsing context.

  • user_context_id (String)

    The ID of the user context.



73
74
75
76
77
78
79
80
81
82
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 73

def initialize(client, browsing_context_id, user_context_id)
  @client = client
  @browsing_context_id = browsing_context_id
  @user_context_id = user_context_id
  @tabs = []
  @network_events = NetworkEvents.new browsing_context_id
  @logger_events = LoggerEvents.new browsing_context_id
  @navigation_failed_events = NavigationFailedEvents.new browsing_context_id
  @open = true
end

Instance Attribute Details

#browsing_context_idString (readonly)

Returns The browsing context ID.

Returns:

  • (String)

    The browsing context ID.



48
49
50
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 48

def browsing_context_id
  @browsing_context_id
end

#clientObject (readonly)

Returns The WebSocket client.

Returns:

  • (Object)

    The WebSocket client.



45
46
47
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 45

def client
  @client
end

#logger_eventsLoggerEvents (readonly)

Returns The logger events handler.

Returns:



63
64
65
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 63

def logger_events
  @logger_events
end

Returns The navigation failed events handler.

Returns:



66
67
68
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 66

def navigation_failed_events
  @navigation_failed_events
end

#network_eventsNetworkEvents (readonly)

Returns The network events handler.

Returns:



57
58
59
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 57

def network_events
  @network_events
end

#openBoolean (readonly)

Returns Whether the tab is open.

Returns:

  • (Boolean)

    Whether the tab is open.



60
61
62
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 60

def open
  @open
end

#tabsArray<BrowserTab> (readonly)

Returns The list of tabs.

Returns:



54
55
56
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 54

def tabs
  @tabs
end

#user_context_idString (readonly)

Returns The user context ID.

Returns:

  • (String)

    The user context ID.



51
52
53
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 51

def user_context_id
  @user_context_id
end

Instance Method Details

#add_headers(headers:, url_patterns:) ⇒ AddHeadersInterceptor

Adds headers to requests in the browser tab.

Parameters:

  • headers (Hash)

    The headers to add.

  • url_patterns (Array<String>)

    The URL patterns to match.

Returns:



140
141
142
143
144
145
146
147
148
149
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 140

def add_headers(
  headers:,
  url_patterns:
)
  @header_interceptor = AddHeadersInterceptor.new(
    context: browsing_context_id,
    url_patterns: url_patterns,
    headers: headers
  ).tap { |interceptor| interceptor.register_with_client(client: client) }
end

#basic_auth(username:, password:, url_patterns:) ⇒ AuthInterceptor

Configures basic authentication for requests in the browser tab.

Parameters:

  • username (String)

    The username for authentication.

  • password (String)

    The password for authentication.

  • url_patterns (Array<String>)

    The URL patterns to match.

Returns:



157
158
159
160
161
162
163
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 157

def basic_auth(username:, password:, url_patterns:)
  @basic_auth_interceptor = AuthInterceptor.new(
    context: browsing_context_id,
    url_patterns: url_patterns,
    username: username, password: password
  ).tap { |interceptor| interceptor.register_with_client(client: client) }
end

#closeObject

Closes the browser tab and its associated resources.



397
398
399
400
401
402
403
404
405
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 397

def close
  return unless open

  close_tabs
  remove_event_listeners
  close_context

  @open = false
end

#create_browser_tabBrowserTab

Creates a new browser tab.

Returns:



87
88
89
90
91
92
93
94
95
96
97
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 87

def create_browser_tab
  cmd = Bidi2pdf::Bidi::Commands::CreateTab.new(user_context_id: user_context_id)
  client.send_cmd_and_wait(cmd) do |response|
    tab_browsing_context_id = response["result"]["context"]

    BrowserTab.new(client, tab_browsing_context_id, user_context_id).tap do |tab|
      tabs << tab
      Bidi2pdf.logger.debug1 "Created new browser tab: #{tab.inspect}"
    end
  end
end

#execute_script(script, wrap_in_promise: false) ⇒ Object

Executes a script in the browser tab.

This method allows you to execute JavaScript code within the context of the browser tab. Optionally, the script can be wrapped in a JavaScript Promise to handle asynchronous operations.

Parameters:

  • script (String)

    The JavaScript code to execute.

    • This can be any valid JavaScript code that you want to run in the browser tab.

  • wrap_in_promise (Boolean) (defaults to: false)

    Whether to wrap the script in a Promise. Defaults to false.

    • If true, the script will be wrapped in a Promise to handle asynchronous execution.

    • Use this option when the script involves asynchronous operations like network requests. You can use the predefined variable result to store the result of the script.

Returns:

  • (Object)

    The result of the script execution.

    • If the script executes successfully, the result of the last evaluated expression is returned.

    • If the script fails, an error or exception details may be returned.



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 258

def execute_script(script, wrap_in_promise: false)
  Bidi2pdf.notification_service.instrument("execute_script.bidi2pdf") do
    if wrap_in_promise
      script = "        new Promise((resolve, reject) => {\n          try {\n            let result;\n\n            \#{script}\n\n            resolve(result);\n          } catch (error) {\n            reject(error);\n          }\n        });\n      JS\n    end\n\n    cmd = Bidi2pdf::Bidi::Commands::ScriptEvaluate.new context: browsing_context_id, expression: script\n    client.send_cmd_and_wait(cmd) do |response|\n      Bidi2pdf.logger.debug2 \"Script Result: \#{response.inspect}\"\n\n      response[\"result\"]\n    end\n  end\nend\n"

#inject_script(url: nil, content: nil, id: nil) ⇒ Object

Injects a JavaScript script element into the page, either from a URL or with inline content.

Parameters:

  • url (String, nil) (defaults to: nil)

    The URL of the script to load (optional).

  • content (String, nil) (defaults to: nil)

    The JavaScript content to inject (optional).

  • id (String, nil) (defaults to: nil)

    The ID attribute for the script element (optional).

Returns:

  • (Object)

    The result from the script creation promise.



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 291

def inject_script(url: nil, content: nil, id: nil)
  script_code = generate_script_element_code(url: url, content: content, id: id)
  response = execute_script(script_code)

  if response
    if response["type"] == "exception"
      handle_injection_exception(response, url, ScriptInjectionError)
    elsif response["type"] == "success"
      Bidi2pdf.logger.debug1 "Script injected successfully: #{response.inspect}"
      response
    else
      Bidi2pdf.logger.warn "Script injected unknown state: #{response.inspect}"
      response
    end
  else
    Bidi2pdf.logger.error "Failed to inject script: #{url || content}"
    raise ScriptInjectionError, "Failed to inject script: #{url || content}"
  end
end

#inject_style(url: nil, content: nil, id: nil) ⇒ Object

Injects a CSS style element into the page, either from a URL or with inline content.

Parameters:

  • url (String, nil) (defaults to: nil)

    The URL of the stylesheet to load (optional).

  • content (String, nil) (defaults to: nil)

    The CSS content to inject (optional).

  • id (String, nil) (defaults to: nil)

    The ID attribute for the style element (optional).

Returns:

  • (Object)

    The result from the style creation promise.



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 317

def inject_style(url: nil, content: nil, id: nil)
  style_code = generate_style_element_code(url: url, content: content, id: id)
  response = execute_script(style_code)

  if response
    if response["type"] == "exception"
      handle_injection_exception(response, url, StyleInjectionError)
    elsif response["type"] == "success"
      Bidi2pdf.logger.debug1 "Style injected successfully: #{response.inspect}"
      response
    else
      Bidi2pdf.logger.warn "Style injection unknown state: #{response.inspect}"
      response
    end
  else
    Bidi2pdf.logger.error "Failed to inject style: #{url || content}"
    raise StyleInjectionError, "Failed to inject style: #{url || content}"
  end
end

#log_network_traffic(format: :console, output: nil, print_options: { background: true }) {|pdf_base64| ... } ⇒ Object

Logs network traffic in the browser tab.

Parameters:

  • format (Symbol) (defaults to: :console)

    The format for logging (:console or :pdf). Defaults to :console.

  • output (String, nil) (defaults to: nil)

    The output file for PDF logging. Defaults to nil.

  • print_options (Hash) (defaults to: { background: true })

    Options for printing. Defaults to { background: true }.

Yields:

  • (pdf_base64)

    A block to handle the PDF content.



375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 375

def log_network_traffic(format: :console, output: nil, print_options: { background: true }, &)
  format = format.to_sym

  if format == :console
    network_events.log_network_traffic format: :console
  elsif format == :pdf
    html_content = network_events.log_network_traffic format: :html

    return unless html_content

    logging_tab = create_browser_tab

    logging_tab.render_html_content(html_content)
    logging_tab.wait_until_network_idle

    logging_tab.print(output, print_options: print_options, &)

    logging_tab.close
  end
end

Navigates the browser tab to a specified URL.

This method registers necessary event listeners and sends a navigation command to the browser tab, instructing it to load the specified URL. It validates that the URL is properly formatted before attempting navigation.

Examples:

browser_tab.navigate_to("https://example.com")

Parameters:

  • url (String)

    The URL to navigate to.

Raises:



175
176
177
178
179
180
181
182
183
184
185
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 175

def navigate_to(url, wait: "complete")
  begin
    URI.parse(url)
  rescue URI::InvalidURIError => e
    raise NavigationError, "Invalid URL: #{url} - #{e.message}"
  end

  Bidi2pdf.notification_service.instrument("navigate_to.bidi2pdf", url: url) do
    navigate_with_listeners url, wait: wait
  end
end

Prints the content of the browser tab.

rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity

Parameters:

  • outputfile (String, nil) (defaults to: nil)

    The output file for the PDF. Defaults to nil.

  • print_options (Hash) (defaults to: { background: true })

    Options for printing. Defaults to { background: true }.

Yields:

  • (pdf_base64)

    A block to handle the PDF content.

Returns:

  • (String, nil)

    The base64-encoded PDF content, or nil if outputfile or block is provided.



414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 414

def print(outputfile = nil, print_options: { background: true }, &block)
  Bidi2pdf.notification_service.instrument("print.bidi2pdf") do |instrumentation_payload|
    cmd, extractor = build_command_and_extractor(print_options)

    instrumentation_payload[:cmd] = cmd

    client.send_cmd_and_wait(cmd) do |response|
      if response["result"]
        pdf_base64 = extractor.call response

        instrumentation_payload[:pdf_base64] = pdf_base64

        if outputfile
          raise PrintError, "Folder does not exist: #{File.dirname(outputfile)}" unless File.directory?(File.dirname(outputfile))

          File.binwrite(outputfile, Base64.decode64(pdf_base64))
          Bidi2pdf.logger.info "PDF saved as '#{outputfile}'."
        else
          Bidi2pdf.logger.info "PDF generated successfully."
        end

        block.call(pdf_base64) if block_given?

        return pdf_base64 unless outputfile || block_given?
      else
        Bidi2pdf.logger.error "Error printing: #{response}"
      end
    end
  end
end

#render_html_content(html_content, wait: "complete") ⇒ Object

Renders HTML content in the browser tab.

rubocop:disable Metrics/BlockLength

Parameters:

  • html_content (String)

    The HTML content to render.



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 191

def render_html_content(html_content, wait: "complete")
  Bidi2pdf.notification_service.instrument("render_html_content.bidi2pdf", url: "data:text/html") do |instrumentation_payload|
    base64_encoded = Base64.strict_encode64(html_content)

    instrumentation_payload[:data] = base64_encoded

    data_url = "data:text/html;charset=utf-8;base64,#{base64_encoded}"

    begin
      navigate_with_listeners data_url, wait: wait
    rescue Bidi2pdf::CmdTimeoutError
      Bidi2pdf.logger.info "Waiting for page to load seemed to have timed out. Checking if page is ready via javascript..."

      # check if the page is still loading
      execute_script "        const desiredState = \"interactive\";\#{\" \"}\n        const timeoutMs = 30000;\#{\"        \"}\n        const intervalMs = 50;\n\n        result = new Promise((resolveReady, rejectReady) => {\n          const start = Date.now();\n\n          const reachedDesired = (state) =>\n            desiredState === \"interactive\"\n              ? state === \"interactive\" || state === \"complete\"\n              : state === \"complete\";\n\n          const check = () => {\n            const state = document.readyState;\n            if (reachedDesired(state)) {\n              resolveReady(state); // resolves to \"interactive\" or \"complete\"\n              return;\n            }\n            if (Date.now() - start >= timeoutMs) {\n              rejectReady(new Error(`Timeout waiting for document.readyState=\"${desiredState}\". Last state=\"${state}\".`));\n              return;\n            }\n\n            console.warn(`Waiting for document.readyState=\"${desiredState}\". Last state=\"${state}\".`);\n\n            setTimeout(check, intervalMs);\n          };\n\n          // Immediate check in case the page is already loaded\n          check();\n        });\n      JS\n    end\n  end\nend\n", wrap_in_promise: false

Sets a cookie in the browser tab.

Parameters:

  • name (String)

    The name of the cookie.

  • value (String)

    The value of the cookie.

  • domain (String)

    The domain for the cookie.

  • path (String) (defaults to: "/")

    The path for the cookie. Defaults to “/”.

  • secure (Boolean) (defaults to: true)

    Whether the cookie is secure. Defaults to true.

  • http_only (Boolean) (defaults to: false)

    Whether the cookie is HTTP-only. Defaults to false.

  • same_site (String) (defaults to: "strict")

    The SameSite attribute for the cookie. Defaults to “strict”.

  • ttl (Integer) (defaults to: 30)

    The time-to-live for the cookie in seconds. Defaults to 30.



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 109

def set_cookie(
  name:,
  value:,
  domain:,
  path: "/",
  secure: true,
  http_only: false,
  same_site: "strict",
  ttl: 30
)
  cmd = Bidi2pdf::Bidi::Commands::SetTabCookie.new(
    browsing_context_id: browsing_context_id,
    name: name,
    value: value,
    domain: domain,
    path: path,
    secure: secure,
    http_only: http_only,
    same_site: same_site,
    ttl: ttl
  )
  client.send_cmd_and_wait(cmd) do |response|
    Bidi2pdf.logger.debug1 "Cookie set: #{response.inspect}"
  end
end

#wait_until_network_idle(timeout: 10, poll_interval: 0.1) ⇒ Object

Waits until the network is idle in the browser tab.

Parameters:

  • timeout (Integer) (defaults to: 10)

    The timeout duration in seconds. Defaults to 10.

  • poll_interval (Float) (defaults to: 0.1)

    The polling interval in seconds. Defaults to 0.1.



341
342
343
344
345
346
347
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 341

def wait_until_network_idle(timeout: 10, poll_interval: 0.1)
  Bidi2pdf.notification_service.instrument("network_idle.bidi2pdf") do |instrumentation_payload|
    network_events.wait_until_network_idle(timeout: timeout, poll_interval: poll_interval)

    instrumentation_payload[:requests] = network_events.all_events.dup
  end
end

#wait_until_page_loaded(check_script: nil) ⇒ Object

Waits until the page is fully loaded in the browser tab.

This method executes a JavaScript script that checks if the page has finished loading.

Parameters:

  • check_script (String) (defaults to: nil)

    The JavaScript code to check if the page is loaded.

    • Defaults to a script that polls the ‘window.loaded` property.

Returns:

  • (Object)

    The result of the script execution.

    • If the page is loaded successfully, the Promise resolves with the value ‘’done’‘.

    • If the script fails, an error or exception details may be returned.



359
360
361
362
363
364
365
366
367
# File 'lib/bidi2pdf/bidi/browser_tab.rb', line 359

def wait_until_page_loaded(check_script: nil)
  check_script ||= "    new Promise(resolve => { const check = () => window.loaded ? resolve('done') : setTimeout(check, 100); check(); });\n  JS\n\n  Bidi2pdf.notification_service.instrument(\"page_loaded.bidi2pdf\") do\n    execute_script check_script\n  end\nend\n"