ferrum 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f524fb695add05ab04355a4f694710f0cda6ce317048f234cd6d597a6db5b54
4
- data.tar.gz: 4f755ea3ce87efa0a92b2689188ec8ce78979b716e8053e9687fe4f9e97baf2e
3
+ metadata.gz: 703207ec942dcaa7a09a6a2436e9a358da43a4f44e195150814ff1d00e05e3f8
4
+ data.tar.gz: 27e7a22921a96e1fbf4d36215170fb884e4ec72e904686cb12eae014f6dfd0a6
5
5
  SHA512:
6
- metadata.gz: f52f33ea6d6292ab9f829f9e4870ba7be5fe0f9c045f4b4fe1e540b5363440027ae8890987e92e95612b91e1d82d28a19ff3d204cabeac0bebde96943d399baa
7
- data.tar.gz: 3156ae9cc12403dd2b497119371b895d7f37865318ffa2d424676df91346403d85386615abe2db020dffe77b055de72b5e44ccf69137d9bd1fa01a9ae75ffa0f
6
+ metadata.gz: b241f4500a4d330b2da5209e789a20c0df95f37963a6031846f51e8950378885749a2c3217116ef37dc09a5efcc421eaf4b1301536a38b9eb0747f2e1037f904
7
+ data.tar.gz: 60abc95e3633224aa08923da5b78026a687d21f380b3a818b99dcc1f32950fce83a7de574276f21973360ea257aaa8560490202e735953cbd6b714466eeefee8
data/README.md CHANGED
@@ -23,10 +23,12 @@ There's no official Chrome or Chromium package for Linux don't install it this
23
23
  way because it either will be outdated or unofficial, both are bad. Download it
24
24
  from official [source](https://www.chromium.org/getting-involved/download-chromium).
25
25
  Chrome binary should be in the `PATH` or `BROWSER_PATH` or you can pass it as an
26
- option.
26
+ option to browser instance `:browser_path`.
27
+
28
+ Add this to your Gemfile:
27
29
 
28
30
  ``` ruby
29
- gem install "ferrum"
31
+ gem "ferrum"
30
32
  ```
31
33
 
32
34
  Navigate to a website and save a screenshot:
@@ -641,3 +643,61 @@ browser.on(:dialog) do |dialog|
641
643
  end
642
644
  browser.goto("https://google.com")
643
645
  ```
646
+
647
+
648
+ ## Thread safety ##
649
+
650
+ Ferrum is fully thread-safe. You can create one browser or a few as you wish and
651
+ start playing around using threads. Example below shows how to create a few pages
652
+ which share the same context. Context is similar to an incognito profile but you
653
+ can have more than one, think of it like it's independent browser session:
654
+
655
+ ```ruby
656
+ browser = Ferrum::Browser.new
657
+ context = browser.contexts.create
658
+
659
+ t1 = Thread.new(context) do |c|
660
+ page = c.create_page
661
+ page.goto("https://www.google.com/search?q=Ruby+headless+driver+for+Capybara")
662
+ page.screenshot(path: "t1.png")
663
+ end
664
+
665
+ t2 = Thread.new(context) do |c|
666
+ page = c.create_page
667
+ page.goto("https://www.google.com/search?q=Ruby+static+typing")
668
+ page.screenshot(path: "t2.png")
669
+ end
670
+
671
+ t1.join
672
+ t2.join
673
+
674
+ context.dispose
675
+ browser.quit
676
+ ```
677
+
678
+ or you can create two independent contexts:
679
+
680
+ ```ruby
681
+ browser = Ferrum::Browser.new
682
+
683
+ t1 = Thread.new(browser) do |b|
684
+ context = b.contexts.create
685
+ page = context.create_page
686
+ page.goto("https://www.google.com/search?q=Ruby+headless+driver+for+Capybara")
687
+ page.screenshot(path: "t1.png")
688
+ context.dispose
689
+ end
690
+
691
+ t2 = Thread.new(browser) do |b|
692
+ context = b.contexts.create
693
+ page = context.create_page
694
+ page.goto("https://www.google.com/search?q=Ruby+static+typing")
695
+ page.screenshot(path: "t2.png")
696
+ context.dispose
697
+ end
698
+
699
+ t1.join
700
+ t2.join
701
+
702
+ browser.quit
703
+ ```
@@ -5,15 +5,10 @@ require "ferrum/node"
5
5
 
6
6
  module Ferrum
7
7
  class Error < StandardError; end
8
- class NoSuchWindowError < Error; end
8
+ class NoSuchPageError < Error; end
9
+ class NoSuchTargetError < Error; end
9
10
  class NotImplementedError < Error; end
10
11
 
11
- class EmptyTargetsError < Error
12
- def initialize
13
- super("There aren't targets available")
14
- end
15
- end
16
-
17
12
  class StatusError < Error
18
13
  def initialize(url)
19
14
  super("Request to #{url} failed to reach server, check DNS and/or server status")
@@ -3,7 +3,7 @@
3
3
  require "base64"
4
4
  require "forwardable"
5
5
  require "ferrum/page"
6
- require "ferrum/targets"
6
+ require "ferrum/contexts"
7
7
  require "ferrum/browser/process"
8
8
  require "ferrum/browser/client"
9
9
 
@@ -14,19 +14,19 @@ module Ferrum
14
14
  BASE_URL_SCHEMA = %w[http https].freeze
15
15
 
16
16
  extend Forwardable
17
- delegate %i[window_handle window_handles switch_to_window
18
- open_new_window close_window within_window page] => :targets
17
+ delegate %i[default_context] => :contexts
18
+ delegate %i[targets create_target create_page page pages windows] => :default_context
19
19
  delegate %i[goto back forward refresh
20
20
  at_css at_xpath css xpath current_url title body
21
21
  headers cookies network
22
22
  mouse keyboard
23
- screenshot pdf
23
+ screenshot pdf viewport_size
24
24
  evaluate evaluate_on evaluate_async execute
25
25
  frame_url frame_title within_frame
26
26
  on] => :page
27
27
 
28
- attr_reader :client, :process, :logger, :js_errors, :slowmo, :base_url,
29
- :options, :window_size
28
+ attr_reader :client, :process, :contexts, :logger, :js_errors,
29
+ :slowmo, :base_url, :options, :window_size
30
30
  attr_writer :timeout
31
31
 
32
32
  def initialize(options = nil)
@@ -82,7 +82,7 @@ module Ferrum
82
82
 
83
83
  def reset
84
84
  @window_size = @original_window_size
85
- targets.reset
85
+ contexts.reset
86
86
  end
87
87
 
88
88
  def restart
@@ -93,11 +93,7 @@ module Ferrum
93
93
  def quit
94
94
  @client.close
95
95
  @process.stop
96
- @client = @process = @targets = nil
97
- end
98
-
99
- def targets
100
- @targets ||= Targets.new(self)
96
+ @client = @process = @contexts = nil
101
97
  end
102
98
 
103
99
  def resize(**options)
@@ -115,6 +111,7 @@ module Ferrum
115
111
  Ferrum.started
116
112
  @process = Process.start(@options)
117
113
  @client = Client.new(self, @process.ws_url, 0, false)
114
+ @contexts = Contexts.new(self)
118
115
  end
119
116
  end
120
117
  end
@@ -76,7 +76,7 @@ module Ferrum
76
76
  when "Cannot find context with specified id"
77
77
  raise NoExecutionContextError.new(error)
78
78
  when "No target with given id found"
79
- raise NoSuchWindowError
79
+ raise NoSuchPageError
80
80
  else
81
81
  raise BrowserError.new(error)
82
82
  end
@@ -84,6 +84,15 @@ module Ferrum
84
84
  end
85
85
  end
86
86
 
87
+ def self.directory_remover(path)
88
+ proc do
89
+ begin
90
+ FileUtils.remove_entry(path)
91
+ rescue Errno::ENOENT
92
+ end
93
+ end
94
+ end
95
+
87
96
  def self.detect_browser_path
88
97
  if RUBY_PLATFORM.include?("darwin")
89
98
  [
@@ -119,7 +128,9 @@ module Ferrum
119
128
  host = options.fetch(:host, BROWSER_HOST)
120
129
  @options.merge!("remote-debugging-address" => host)
121
130
 
122
- @options.merge!("user-data-dir" => Dir.mktmpdir)
131
+ @temp_user_data_dir = Dir.mktmpdir
132
+ ObjectSpace.define_finalizer(self, self.class.directory_remover(@temp_user_data_dir))
133
+ @options.merge!("user-data-dir" => @temp_user_data_dir)
123
134
 
124
135
  @options = DEFAULT_OPTIONS.merge(@options)
125
136
 
@@ -158,8 +169,8 @@ module Ferrum
158
169
  end
159
170
 
160
171
  def stop
161
- return unless @pid
162
- kill
172
+ kill if @pid
173
+ remove_temp_user_data_dir if @temp_user_data_dir
163
174
  ObjectSpace.undefine_finalizer(self)
164
175
  end
165
176
 
@@ -175,6 +186,11 @@ module Ferrum
175
186
  @pid = nil
176
187
  end
177
188
 
189
+ def remove_temp_user_data_dir
190
+ self.class.directory_remover(@temp_user_data_dir).call
191
+ @temp_user_data_dir = nil
192
+ end
193
+
178
194
  def parse_ws_url(read_io, timeout)
179
195
  output = ""
180
196
  start = Ferrum.monotonic_time
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ferrum/target"
4
+
5
+ module Ferrum
6
+ class Context
7
+ POSITION = %i[first last].freeze
8
+
9
+ attr_reader :id, :targets
10
+
11
+ def initialize(browser, contexts, id)
12
+ @browser, @contexts, @id = browser, contexts, id
13
+ @targets = Concurrent::Hash.new
14
+ @pendings = Concurrent::MVar.new
15
+ end
16
+
17
+ def default_target
18
+ @default_target ||= create_target
19
+ end
20
+
21
+ def page
22
+ default_target.page
23
+ end
24
+
25
+ def pages
26
+ @targets.values.map(&:page)
27
+ end
28
+
29
+ # When we call `page` method on target it triggers ruby to connect to given
30
+ # page by WebSocket, if there are many opened windows but we need only one
31
+ # it makes more sense to get and connect to the needed one only which
32
+ # usually is the last one.
33
+ def windows(pos = nil, size = 1)
34
+ raise ArgumentError if pos && !POSITION.include?(pos)
35
+ windows = @targets.values.select(&:window?)
36
+ windows = windows.send(pos, size) if pos
37
+ windows.map(&:page)
38
+ end
39
+
40
+ def create_page
41
+ create_target.page
42
+ end
43
+
44
+ def create_target
45
+ target_id = @browser.command("Target.createTarget",
46
+ browserContextId: @id,
47
+ url: "about:blank")["targetId"]
48
+ target = @pendings.take(@browser.timeout)
49
+ raise NoSuchTargetError unless target.is_a?(Target)
50
+ @targets[target.id] = target
51
+ target
52
+ end
53
+
54
+ def add_target(params)
55
+ target = Target.new(@browser, params)
56
+ if target.window?
57
+ @targets[target.id] = target
58
+ else
59
+ @pendings.put(target, @browser.timeout)
60
+ end
61
+ end
62
+
63
+ def update_target(target_id, params)
64
+ @targets[target_id].update(params)
65
+ end
66
+
67
+ def delete_target(target_id)
68
+ @targets.delete(target_id)
69
+ end
70
+
71
+ def dispose
72
+ @contexts.dispose(@id)
73
+ end
74
+
75
+ def inspect
76
+ %(#<#{self.class} @id=#{@id.inspect} @targets=#{@targets.inspect} @default_target=#{@default_target.inspect}>)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ferrum/context"
4
+
5
+ module Ferrum
6
+ class Contexts
7
+ attr_reader :contexts
8
+
9
+ def initialize(browser)
10
+ @contexts = Concurrent::Hash.new
11
+ @browser = browser
12
+ subscribe
13
+ discover
14
+ end
15
+
16
+ def default_context
17
+ @default_context ||= create
18
+ end
19
+
20
+ def find_by(target_id:)
21
+ @contexts.find { |_, c| c.targets.keys.include?(target_id) }&.last
22
+ end
23
+
24
+ def create
25
+ response = @browser.command("Target.createBrowserContext")
26
+ context_id = response["browserContextId"]
27
+ context = Context.new(@browser, self, context_id)
28
+ @contexts[context_id] = context
29
+ context
30
+ end
31
+
32
+ def dispose(context_id)
33
+ context = @contexts[context_id]
34
+ @browser.command("Target.disposeBrowserContext",
35
+ browserContextId: context.id)
36
+ @contexts.delete(context_id)
37
+ true
38
+ end
39
+
40
+ def reset
41
+ @default_context = nil
42
+ @contexts.keys.each { |id| dispose(id) }
43
+ end
44
+
45
+ private
46
+
47
+ def subscribe
48
+ @browser.client.on("Target.targetCreated") do |params|
49
+ info = params["targetInfo"]
50
+ next unless info["type"] == "page"
51
+
52
+ context_id = info["browserContextId"]
53
+ @contexts[context_id]&.add_target(info)
54
+ end
55
+
56
+ @browser.client.on("Target.targetInfoChanged") do |params|
57
+ info = params["targetInfo"]
58
+ next unless info["type"] == "page"
59
+
60
+ context_id, target_id = info.values_at("browserContextId", "targetId")
61
+ @contexts[context_id]&.update_target(target_id, info)
62
+ end
63
+
64
+ @browser.client.on("Target.targetDestroyed") do |params|
65
+ if context = find_by(target_id: params["targetId"])
66
+ context.delete_target(params["targetId"])
67
+ end
68
+ end
69
+
70
+ @browser.client.on("Target.targetCrashed") do |params|
71
+ if context = find_by(target_id: params["targetId"])
72
+ context.delete_target(params["targetId"])
73
+ end
74
+ end
75
+ end
76
+
77
+ def discover
78
+ @browser.command("Target.setDiscoverTargets", discover: true)
79
+ end
80
+ end
81
+ end
@@ -73,7 +73,7 @@ module Ferrum
73
73
  @authorized_ids[type] << request.interception_id
74
74
  request.continue(authChallengeResponse: response)
75
75
  elsif index + 1 < total
76
- next # There are other callbacks that can handle this, skip
76
+ next # There are other callbacks that can handle this
77
77
  else
78
78
  request.continue
79
79
  end
@@ -30,6 +30,10 @@ module Ferrum
30
30
  @request["url"]
31
31
  end
32
32
 
33
+ def url_fragment
34
+ @request["urlFragment"]
35
+ end
36
+
33
37
  def method
34
38
  @request["method"]
35
39
  end
@@ -14,8 +14,6 @@ require "ferrum/browser/client"
14
14
 
15
15
  module Ferrum
16
16
  class Page
17
- NEW_WINDOW_WAIT = ENV.fetch("FERRUM_NEW_WINDOW_WAIT", 0.3).to_f
18
-
19
17
  class Event < Concurrent::Event
20
18
  def iteration
21
19
  synchronize { @iteration }
@@ -37,7 +35,7 @@ module Ferrum
37
35
  :headers, :cookies, :network,
38
36
  :mouse, :keyboard
39
37
 
40
- def initialize(target_id, browser, new_window = false)
38
+ def initialize(target_id, browser)
41
39
  @target_id, @browser = target_id, browser
42
40
  @event = Event.new.tap(&:set)
43
41
 
@@ -45,11 +43,6 @@ module Ferrum
45
43
  @waiting_frames ||= Set.new
46
44
  @frame_stack = []
47
45
 
48
- # Dirty hack because new window doesn't have events at all
49
- sleep(NEW_WINDOW_WAIT) if new_window
50
-
51
- @session_id = @browser.command("Target.attachToTarget", targetId: @target_id)["sessionId"]
52
-
53
46
  host = @browser.process.host
54
47
  port = @browser.process.port
55
48
  ws_url = "ws://#{host}:#{port}/devtools/page/#{@target_id}"
@@ -83,12 +76,7 @@ module Ferrum
83
76
 
84
77
  def close
85
78
  @headers.clear
86
- @browser.command("Target.detachFromTarget", sessionId: @session_id)
87
79
  @browser.command("Target.closeTarget", targetId: @target_id)
88
- close_connection
89
- end
90
-
91
- def close_connection
92
80
  @client.close
93
81
  end
94
82
 
@@ -173,10 +161,6 @@ module Ferrum
173
161
  end
174
162
  end
175
163
 
176
- on("Page.windowOpen") do
177
- @browser.targets.refresh
178
- end
179
-
180
164
  on("Page.navigatedWithinDocument") do
181
165
  @event.set if @waiting_frames.empty?
182
166
  end
@@ -19,6 +19,19 @@ module Ferrum
19
19
  save_file(path, data)
20
20
  end
21
21
 
22
+ def viewport_size
23
+ evaluate <<~JS
24
+ [window.innerWidth, window.innerHeight]
25
+ JS
26
+ end
27
+
28
+ def document_size
29
+ evaluate <<~JS
30
+ [document.documentElement.offsetWidth,
31
+ document.documentElement.offsetHeight]
32
+ JS
33
+ end
34
+
22
35
  private
23
36
 
24
37
  def save_file(path, data)
@@ -57,7 +70,7 @@ module Ferrum
57
70
  end
58
71
 
59
72
  if !!opts[:full]
60
- width, height = evaluate("[document.documentElement.offsetWidth, document.documentElement.offsetHeight]")
73
+ width, height = document_size
61
74
  options.merge!(clip: { x: 0, y: 0, width: width, height: height, scale: scale }) if width > 0 && height > 0
62
75
  elsif opts[:selector]
63
76
  rect = evaluate("document.querySelector('#{opts[:selector]}').getBoundingClientRect()")
@@ -66,7 +79,7 @@ module Ferrum
66
79
 
67
80
  if scale != 1.0
68
81
  if !options[:clip]
69
- width, height = evaluate("[document.documentElement.clientWidth, document.documentElement.clientHeight]")
82
+ width, height = viewport_size
70
83
  options[:clip] = { x: 0, y: 0, width: width, height: height }
71
84
  end
72
85
 
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ferrum
4
+ class Target
5
+ NEW_WINDOW_WAIT = ENV.fetch("FERRUM_NEW_WINDOW_WAIT", 0.3).to_f
6
+
7
+ def initialize(browser, params = nil)
8
+ @browser = browser
9
+ @params = params
10
+ end
11
+
12
+ def update(params)
13
+ @params = params
14
+ end
15
+
16
+ def page
17
+ @page ||= begin
18
+ # Dirty hack because new window doesn't have events at all
19
+ sleep(NEW_WINDOW_WAIT) if window?
20
+ Page.new(id, @browser)
21
+ end
22
+ end
23
+
24
+ def id
25
+ @params["targetId"]
26
+ end
27
+
28
+ def type
29
+ @params["type"]
30
+ end
31
+
32
+ def title
33
+ @params["title"]
34
+ end
35
+
36
+ def url
37
+ @params["url"]
38
+ end
39
+
40
+ def opener_id
41
+ @params["openerId"]
42
+ end
43
+
44
+ def context_id
45
+ @params["browserContextId"]
46
+ end
47
+
48
+ def window?
49
+ !!opener_id
50
+ end
51
+ end
52
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ferrum
4
- VERSION = "0.4"
4
+ VERSION = "0.5"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ferrum
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.4'
4
+ version: '0.5'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Vorotilin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-16 00:00:00.000000000 Z
11
+ date: 2019-09-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: websocket-driver
@@ -170,20 +170,6 @@ dependencies:
170
170
  - - "~>"
171
171
  - !ruby/object:Gem::Version
172
172
  version: '1.3'
173
- - !ruby/object:Gem::Dependency
174
- name: byebug
175
- requirement: !ruby/object:Gem::Requirement
176
- requirements:
177
- - - "~>"
178
- - !ruby/object:Gem::Version
179
- version: '10.0'
180
- type: :development
181
- prerelease: false
182
- version_requirements: !ruby/object:Gem::Requirement
183
- requirements:
184
- - - "~>"
185
- - !ruby/object:Gem::Version
186
- version: '10.0'
187
173
  description: Ferrum allows you to control headless Chrome browser
188
174
  email:
189
175
  - d.vorotilin@gmail.com
@@ -199,6 +185,8 @@ files:
199
185
  - lib/ferrum/browser/process.rb
200
186
  - lib/ferrum/browser/subscriber.rb
201
187
  - lib/ferrum/browser/web_socket.rb
188
+ - lib/ferrum/context.rb
189
+ - lib/ferrum/contexts.rb
202
190
  - lib/ferrum/cookies.rb
203
191
  - lib/ferrum/dialog.rb
204
192
  - lib/ferrum/headers.rb
@@ -217,7 +205,7 @@ files:
217
205
  - lib/ferrum/page/frame.rb
218
206
  - lib/ferrum/page/runtime.rb
219
207
  - lib/ferrum/page/screenshot.rb
220
- - lib/ferrum/targets.rb
208
+ - lib/ferrum/target.rb
221
209
  - lib/ferrum/version.rb
222
210
  homepage: https://github.com/route/ferrum
223
211
  licenses:
@@ -1,123 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ferrum
4
- class Targets
5
- TARGETS_RETRY_ATTEMPTS = 3
6
- TARGETS_RETRY_WAIT = 0.001
7
-
8
- def initialize(browser)
9
- @page = nil
10
- @mutex = Mutex.new
11
- @browser = browser
12
- @_default = targets.first["targetId"]
13
-
14
- @browser.client.on("Target.detachedFromTarget") do |params|
15
- page = remove_page(params["targetId"])
16
- page&.close_connection
17
- end
18
-
19
- reset
20
- end
21
-
22
- def push(target_id, page = nil)
23
- @targets[target_id] = page
24
- end
25
-
26
- def refresh
27
- @mutex.synchronize do
28
- targets.each { |t| push(t["targetId"]) if !default?(t) && !has?(t) }
29
- end
30
- end
31
-
32
- def page
33
- raise NoSuchWindowError unless @page
34
- @page
35
- end
36
-
37
- def window_handle
38
- page.target_id
39
- end
40
-
41
- def window_handles
42
- @mutex.synchronize { @targets.keys }
43
- end
44
-
45
- def switch_to_window(target_id)
46
- @page = find_or_create_page(target_id)
47
- end
48
-
49
- def open_new_window
50
- target_id = @browser.command("Target.createTarget", url: "about:blank", browserContextId: @_context_id)["targetId"]
51
- page = Page.new(target_id, @browser, true)
52
- push(target_id, page)
53
- target_id
54
- end
55
-
56
- def close_window(target_id)
57
- remove_page(target_id)&.close
58
- end
59
-
60
- def within_window(locator)
61
- original = window_handle
62
-
63
- if window_handles.include?(locator)
64
- switch_to_window(locator)
65
- yield
66
- else
67
- raise NoSuchWindowError
68
- end
69
- ensure
70
- switch_to_window(original)
71
- end
72
-
73
- def reset
74
- if @page
75
- @page.close
76
- @browser.command("Target.disposeBrowserContext", browserContextId: @_context_id)
77
- end
78
-
79
- @page = nil
80
- @targets = {}
81
- @_context_id = nil
82
-
83
- @_context_id = @browser.command("Target.createBrowserContext")["browserContextId"]
84
- target_id = @browser.command("Target.createTarget", url: "about:blank", browserContextId: @_context_id)["targetId"]
85
- @page = Page.new(target_id, @browser)
86
- push(target_id, @page)
87
- end
88
-
89
- def find_or_create_page(target_id)
90
- page = @targets[target_id]
91
- page ||= Page.new(target_id, @browser, true)
92
- @targets[target_id] ||= page
93
- page
94
- end
95
-
96
- private
97
-
98
- def remove_page(target_id)
99
- page = @targets.delete(target_id)
100
- @page = nil if page && @page == page
101
- page
102
- end
103
-
104
- def targets
105
- Ferrum.with_attempts(errors: EmptyTargetsError,
106
- max: TARGETS_RETRY_ATTEMPTS,
107
- wait: TARGETS_RETRY_WAIT) do
108
- # Targets cannot be empty the must be at least one default target.
109
- targets = @browser.command("Target.getTargets")["targetInfos"]
110
- raise EmptyTargetsError if targets.empty?
111
- targets
112
- end
113
- end
114
-
115
- def default?(target)
116
- @_default == target["targetId"]
117
- end
118
-
119
- def has?(target)
120
- @targets.key?(target["targetId"])
121
- end
122
- end
123
- end