Skip to content

Commit b0af0d6

Browse files
committed
feat: Add ability to connect to iframe targets
1 parent 765386a commit b0af0d6

File tree

4 files changed

+53
-16
lines changed

4 files changed

+53
-16
lines changed

lib/ferrum/browser/options/chrome.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def merge_required(flags, options, user_data_dir)
7878
def merge_default(flags, options)
7979
defaults = except("headless", "disable-gpu") if options.headless == false
8080
defaults ||= DEFAULT_OPTIONS
81-
# On Windows, the --disable-gpu flag is a temporary work around for a few bugs.
81+
# On Windows, the --disable-gpu flag is a temporary workaround for a few bugs.
8282
# See https://bugs.chromium.org/p/chromium/issues/detail?id=737678 for more information.
8383
defaults = defaults.merge("disable-gpu" => nil) if Utils::Platform.windows?
8484
# Use Metal on Apple Silicon

lib/ferrum/context.rb

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def initialize(client, contexts, id)
1313
@client = client
1414
@contexts = contexts
1515
@targets = Concurrent::Map.new
16-
@pendings = Concurrent::MVar.new
16+
@pendings = Concurrent::Map.new
1717
end
1818

1919
def default_target
@@ -29,7 +29,7 @@ def pages
2929
end
3030

3131
# When we call `page` method on target it triggers ruby to connect to given
32-
# page by WebSocket, if there are many opened windows but we need only one
32+
# page by WebSocket, if there are many opened windows, but we need only one
3333
# it makes more sense to get and connect to the needed one only which
3434
# usually is the last one.
3535
def windows(pos = nil, size = 1)
@@ -46,19 +46,26 @@ def create_page(**options)
4646
end
4747

4848
def create_target
49-
@client.command("Target.createTarget", browserContextId: @id, url: "about:blank")
50-
target = @pendings.take(@client.timeout)
51-
raise NoSuchTargetError unless target.is_a?(Target)
49+
target_id = @client.command("Target.createTarget", browserContextId: @id, url: "about:blank")["targetId"]
5250

53-
target
51+
new_pending = Concurrent::IVar.new
52+
pending = @pendings.put_if_absent(target_id, new_pending) || new_pending
53+
resolved = pending.value(@client.timeout)
54+
raise NoSuchTargetError unless resolved
55+
56+
@pendings.delete(target_id)
57+
@targets[target_id]
5458
end
5559

5660
def add_target(params:, session_id: nil)
5761
new_target = Target.new(@client, session_id, params)
58-
target = @targets.put_if_absent(new_target.id, new_target)
59-
target ||= new_target # `put_if_absent` returns nil if added a new value or existing if there was one already
60-
@pendings.put(target, @client.timeout) if @pendings.empty?
61-
target
62+
# `put_if_absent` returns nil if added a new value or existing if there was one already
63+
target = @targets.put_if_absent(new_target.id, new_target) || new_target
64+
65+
new_pending = Concurrent::IVar.new
66+
pending = @pendings.put_if_absent(target.id, new_pending) || new_pending
67+
pending.try_set(true)
68+
true
6269
end
6370

6471
def update_target(target_id, params)
@@ -69,6 +76,21 @@ def delete_target(target_id)
6976
@targets.delete(target_id)
7077
end
7178

79+
def attach_target(target_id)
80+
target = @targets[target_id]
81+
raise NoSuchTargetError unless target
82+
83+
session = @client.command("Target.attachToTarget", targetId: target_id, flatten: true)
84+
target.session_id = session["sessionId"]
85+
true
86+
end
87+
88+
def find_target
89+
@targets.each_value { |t| return t if yield(t) }
90+
91+
nil
92+
end
93+
7294
def close_targets_connection
7395
@targets.each_value do |target|
7496
next unless target.connected?

lib/ferrum/contexts.rb

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def size
7070
def subscribe
7171
@client.on("Target.attachedToTarget") do |params|
7272
info, session_id = params.values_at("targetInfo", "sessionId")
73-
next unless info["type"] == "page"
73+
next unless %w[page iframe].include?(info["type"])
7474

7575
context_id = info["browserContextId"]
7676
@contexts[context_id]&.add_target(session_id: session_id, params: info)
@@ -81,15 +81,21 @@ def subscribe
8181

8282
@client.on("Target.targetCreated") do |params|
8383
info = params["targetInfo"]
84-
next unless info["type"] == "page"
84+
next unless %w[page iframe].include?(info["type"])
8585

8686
context_id = info["browserContextId"]
87-
@contexts[context_id]&.add_target(params: info)
87+
88+
if info["type"] == "iframe" &&
89+
(target = @contexts[context_id].find_target { |t| t.connected? && t.page.frame_by(id: info["targetId"]) })
90+
@contexts[context_id]&.add_target(session_id: target.page.client.session_id, params: info)
91+
else
92+
@contexts[context_id]&.add_target(params: info)
93+
end
8894
end
8995

9096
@client.on("Target.targetInfoChanged") do |params|
9197
info = params["targetInfo"]
92-
next unless info["type"] == "page"
98+
next unless %w[page iframe].include?(info["type"])
9399

94100
context_id, target_id = info.values_at("browserContextId", "targetId")
95101
@contexts[context_id]&.update_target(target_id, info)

lib/ferrum/target.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ class Target
88
# where we enhance page class and build page ourselves.
99
attr_writer :page
1010

11-
attr_reader :session_id, :options
11+
attr_reader :options
12+
attr_accessor :session_id
1213

1314
def initialize(browser_client, session_id = nil, params = nil)
1415
@page = nil
@@ -67,11 +68,19 @@ def window?
6768
!!opener_id
6869
end
6970

71+
def iframe?
72+
type == "iframe"
73+
end
74+
7075
def maybe_sleep_if_new_window
7176
# Dirty hack because new window doesn't have events at all
7277
sleep(NEW_WINDOW_WAIT) if window?
7378
end
7479

80+
def command(...)
81+
client.command(...)
82+
end
83+
7584
private
7685

7786
def build_client

0 commit comments

Comments
 (0)