Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
undid `--blink-settings` overrides. `mobile:` defaults to `nil` and leaves touch as is, `false` turns it off [#664]
- A click or navigation timed out when the last loading iframe moved to another process, e.g. submitting a form that
opens an inline PDF in Chrome 145+, where the PDF viewer runs out of process [#672]
- A click or navigation didn't wait for an out-of-process iframe to load, e.g. a cross-site iframe with site isolation
on. Ferrum now follows its loading in the iframe's own target [#685]

### Removed

Expand Down
9 changes: 5 additions & 4 deletions lib/ferrum/contexts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -230,17 +230,18 @@ def handle_attach(target, session_id, params, parent)
elsif target.worker? || target.shared_worker?
connect_worker(target)
else
add_extensions_and_resume(session_id, params["waitingForDebugger"])
add_extensions_and_resume(target, session_id, params["waitingForDebugger"])
end
end

# A page or iframe is paused until we resume it, so the extensions we
# register now run before any script of its first document. An
# out-of-process iframe ignores them until the Page domain is enabled.
def add_extensions_and_resume(session_id, waiting)
# out-of-process iframe ignores them until the Page domain is enabled,
# and its parent page needs the Page domain to see it finish loading.
def add_extensions_and_resume(target, session_id, waiting)
extensions = @client.options.extensions
session = @client.session(session_id)
session.command("Page.enable", async: true) if extensions.any?
session.command("Page.enable", async: true) if extensions.any? || target.iframe?
extensions.each do |source|
session.command("Page.addScriptToEvaluateOnNewDocument", async: true, source: source)
end
Expand Down
1 change: 1 addition & 0 deletions lib/ferrum/page.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def initialize(client, context_id:, target_id:, proxy: nil)
@options = client.options

@frames = Concurrent::Map.new
@remote_frames = Concurrent::Map.new
@main_frame = Frame.new(nil, self)
@event = Utils::Event.new.tap(&:set)
@document_version = 0
Expand Down
45 changes: 43 additions & 2 deletions lib/ferrum/page/frames.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def frame_by(id: nil, name: nil, execution_id: nil)
def frames_subscribe
subscribe_frame_attached
subscribe_frame_detached
subscribe_remote_frames
subscribe_frame_started_navigating
subscribe_frame_started_loading
subscribe_frame_navigated
Expand Down Expand Up @@ -104,11 +105,50 @@ def subscribe_frame_detached
frame.execution_id = nil
else
@frames.delete(params["frameId"])
@event.set if @main_frame.idle? && frame&.idle? == false && frames.size == 1
forget_remote_frame(params["frameId"]) unless params["reason"] == "swap"
@event.set if @main_frame.idle? && frame&.idle? == false && frames.size == 1 && @remote_frames.values.none?
end
end
end

# A frame swapped into another process (`Page.frameDetached` with reason `swap`) leaves the page's frame tree
# and loads in its own `iframe` target, so its loading events come over that target's session. Until it stops
# loading there {#idling?} stays false, as it does for any other loading frame.
def subscribe_remote_frames
on("Target.attachedToTarget") do |params|
info, session_id = params.values_at("targetInfo", "sessionId")
next unless info["type"] == "iframe"

frame_id = info["targetId"]
@remote_frames.put_if_absent(frame_id, true)
subscribe_remote_frame_loading(client.session(session_id), frame_id)
end

on("Target.detachedFromTarget") do |params|
forget_remote_frame(params["targetId"])
end
end

def subscribe_remote_frame_loading(session, frame_id)
session.on("Page.frameStartedLoading") do |params|
next unless params["frameId"] == frame_id && @remote_frames.key?(frame_id)

@remote_frames[frame_id] = true
@event.reset
end

session.on("Page.frameStoppedLoading") do |params|
next unless params["frameId"] == frame_id && @remote_frames.key?(frame_id)

@remote_frames[frame_id] = false
@event.set if idling?
end
end

def forget_remote_frame(frame_id)
@event.set if @remote_frames.delete(frame_id) && idling?
end

# Tracks the +loaderId+ for each navigating frame and clears stale
# lifecycle events from the previous navigation.
def subscribe_frame_started_navigating
Expand Down Expand Up @@ -257,11 +297,12 @@ def subscribe_execution_contexts_cleared
end

children.each { |id| @frames.delete(id) }
@remote_frames.clear
end
end

def idling?
@frames.values.all?(&:idle?)
@frames.values.all?(&:idle?) && @remote_frames.values.none?
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion sig/ferrum/contexts.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ module Ferrum

def connect_worker: (Target target) -> void

def add_extensions_and_resume: (String session_id, bool? waiting) -> void
def add_extensions_and_resume: (Target target, String session_id, bool? waiting) -> void

def resume: (String session_id) -> void

Expand Down
1 change: 1 addition & 0 deletions sig/ferrum/page.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ module Ferrum
@target_id: String
@options: Browser::Options
@frames: ::Concurrent::Map[String, Frame]
@remote_frames: ::Concurrent::Map[String, bool]
@main_frame: Frame
@event: Utils::Event
@mouse: Mouse
Expand Down
6 changes: 6 additions & 0 deletions sig/ferrum/page/frames.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ module Ferrum

def subscribe_frame_detached: () -> untyped

def subscribe_remote_frames: () -> untyped

def subscribe_remote_frame_loading: (SessionClient session, String frame_id) -> untyped

def forget_remote_frame: (String? frame_id) -> untyped

def subscribe_frame_started_navigating: () -> untyped

def subscribe_frame_started_loading: () -> untyped
Expand Down
11 changes: 11 additions & 0 deletions spec/frame_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,17 @@
expect(page.evaluate("location.pathname")).to eq("/slow")
end

it "waits for a cross-site frame a click adds", :launch do
browser = Ferrum::Browser.new(base_url: base_url,
browser_options: { "site-per-process" => nil, "disable-features" => "Translate" })
browser.go_to("/cross_site_iframe")
browser.at_css("button").click

expect(browser.evaluate("window.frameLoaded")).to be(true)
ensure
browser&.quit
end

context "with lazy loading iframes" do
it "does not block visit on lazy iframe" do
expect { page.go_to("/lazy_iframe") }.not_to raise_error
Expand Down
17 changes: 17 additions & 0 deletions spec/support/views/cross_site_iframe.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<body>
<button>Add cross-site iframe</button>

<script>
window.frameLoaded = false;

document.querySelector("button").addEventListener("click", function () {
var frame = document.createElement("iframe");
frame.onload = function () { window.frameLoaded = true; };
frame.src = location.origin.replace("127.0.0.1", "localhost") + "/slow_image";
document.body.appendChild(frame);
});
</script>
</body>
</html>
6 changes: 6 additions & 0 deletions spec/support/views/slow_image.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<img src="/show_cookies/set_cookie_slow">
</body>
</html>
Loading