Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- `:ws_url` option is now used without modifications WYSIWYG.
- `Network.requestWillBeSent` callback didn't handle params in a type-safe way
- `Page.frameStoppedLoading` callback shouldn't wait for document_node_id response
- `JSON::NestingError` is raised when browser returns very deeply nested JSON and crashes the thread [#498]

### Removed

Expand Down
36 changes: 23 additions & 13 deletions lib/ferrum/client/web_socket.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,16 @@ def on_open(_event)
end

def on_message(event)
data = begin
JSON.parse(event.data)
rescue JSON::ParserError
unescaped = unescape_unicode(event.data)
.encode("UTF-8", "UTF-8", undef: :replace, invalid: :replace, replace: "?")
JSON.parse(unescaped)
end

@messages.push(data)
data = safely_parse_json(event.data)
# If we couldn't parse JSON data for some reason (parse error or deeply nested object) we
# don't push response to @messages. Worse that could happen we raise timeout error due to command didn't return
# anything or skip the background notification, but at least we don't crash the thread that crashes the main
# thread and the application.
@messages.push(data) if data

output = event.data
if SKIP_LOGGING_SCREENSHOTS && @screenshot_commands[data["id"]]
@screenshot_commands.delete(data["id"])
if SKIP_LOGGING_SCREENSHOTS && @screenshot_commands[data&.dig("id")]
@screenshot_commands.delete(data&.dig("id"))
output.sub!(/{"data":"[^"]*"}/, %("Set FERRUM_LOGGING_SCREENSHOTS=true to see screenshots in Base64"))
end

Expand Down Expand Up @@ -108,8 +105,21 @@ def start
end
end

def unescape_unicode(value)
value.gsub(/\\u([\da-fA-F]{4})/) { |_| [::Regexp.last_match(1)].pack("H*").unpack("n*").pack("U*") }
def safely_parse_json(data)
JSON.parse(data, max_nesting: false)
rescue JSON::NestingError
# nop
rescue JSON::ParserError
safely_parse_escaped_json(data)
end

def safely_parse_escaped_json(data)
unescaped_unicode =
data.gsub(/\\u([\da-fA-F]{4})/) { |_| [::Regexp.last_match(1)].pack("H*").unpack("n*").pack("U*") }
escaped_data = unescaped_unicode.encode("UTF-8", "UTF-8", undef: :replace, invalid: :replace, replace: "?")
JSON.parse(escaped_data, max_nesting: false)
rescue JSON::ParserError
# nop
end
end
end
Expand Down
30 changes: 10 additions & 20 deletions spec/browser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,15 +199,10 @@
proxy: { host: proxy.host, port: proxy.port, user: "u1", password: "p1" }
)

if browser.headless_new?
expect { browser.go_to("https://example.com") }.to raise_error(
Ferrum::StatusError,
"Request to https://example.com failed (net::ERR_HTTP_RESPONSE_CODE_FAILURE)"
)
else
browser.go_to("https://example.com")
end

expect { browser.go_to("https://example.com") }.to raise_error(
Ferrum::StatusError,
"Request to https://example.com failed (net::ERR_HTTP_RESPONSE_CODE_FAILURE)"
)
expect(browser.network.status).to eq(407)
ensure
browser&.quit
Expand Down Expand Up @@ -263,7 +258,7 @@

it "saves an attachment" do
# Also https://github.com/puppeteer/puppeteer/issues/10161
skip "https://bugs.chromium.org/p/chromium/issues/detail?id=1444729" if browser.headless_new?
skip "https://bugs.chromium.org/p/chromium/issues/detail?id=1444729"

browser.go_to("/#{filename}")
browser.downloads.wait
Expand Down Expand Up @@ -413,7 +408,7 @@
context "fullscreen" do
shared_examples "resize viewport by fullscreen" do
it "allows the viewport to be resized by fullscreen" do
expect(browser.viewport_size).to eq([1024, 768])
expect(browser.viewport_size).to eq([1024, 681])
browser.go_to(path)
browser.resize(fullscreen: true)
expect(browser.viewport_size).to eq(viewport_size)
Expand Down Expand Up @@ -618,15 +613,10 @@
page = browser.create_page(proxy: { host: proxy.host, port: proxy.port,
user: options[:user], password: "$$" })

if browser.headless_new?
expect { page.go_to("https://example.com") }.to raise_error(
Ferrum::StatusError,
"Request to https://example.com failed (net::ERR_HTTP_RESPONSE_CODE_FAILURE)"
)
else
page.go_to("https://example.com")
end

expect { page.go_to("https://example.com") }.to raise_error(
Ferrum::StatusError,
"Request to https://example.com failed (net::ERR_HTTP_RESPONSE_CODE_FAILURE)"
)
expect(page.network.status).to eq(407)
end

Expand Down
12 changes: 6 additions & 6 deletions spec/downloads_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
let(:filename) { "attachment.pdf" }
let(:save_path) { "/tmp/ferrum" }

def skip_new_headless
def skip_browser_bug
# Also https://github.com/puppeteer/puppeteer/issues/10161
skip "https://bugs.chromium.org/p/chromium/issues/detail?id=1444729" if browser.headless_new?
skip "https://bugs.chromium.org/p/chromium/issues/detail?id=1444729"
end

describe "#files" do
it "saves an attachment" do
skip_new_headless
skip_browser_bug

page.downloads.set_behavior(save_path: save_path)
page.go_to("/#{filename}")
Expand Down Expand Up @@ -50,7 +50,7 @@ def skip_new_headless
describe "#set_behavior" do
context "with absolute path" do
it "saves an attachment" do
skip_new_headless
skip_browser_bug

page.downloads.set_behavior(save_path: save_path)
page.go_to("/#{filename}")
Expand All @@ -62,7 +62,7 @@ def skip_new_headless
end

it "saves no attachment when behavior is deny" do
skip_new_headless
skip_browser_bug

page.downloads.set_behavior(save_path: save_path, behavior: :deny)
page.downloads.wait { page.go_to("/#{filename}") }
Expand All @@ -71,7 +71,7 @@ def skip_new_headless
end

it "saves an attachment on click" do
skip_new_headless
skip_browser_bug

page.downloads.set_behavior(save_path: save_path)
page.go_to("/attachment")
Expand Down
6 changes: 6 additions & 0 deletions spec/frame/runtime_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@
expect(element).to eq(browser.at_css("#empty_input"))
end

it "returns deeply nested node" do
browser.go_to("/ferrum/deeply_nested")
node = browser.evaluate(%(document.getElementById("text")))
expect(node.text).to eq("text")
end

it "returns structures with elements" do
browser.go_to("/ferrum/type")
result = browser.evaluate <<~JS
Expand Down
12 changes: 4 additions & 8 deletions spec/network_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -441,14 +441,10 @@
end

it "denies without credentials" do
if browser.headless_new?
expect { page.go_to("/ferrum/basic_auth") }.to raise_error(
Ferrum::StatusError,
%r{Request to http://.*/ferrum/basic_auth failed \(net::ERR_INVALID_AUTH_CREDENTIALS\)}
)
else
page.go_to("/ferrum/basic_auth")
end
expect { page.go_to("/ferrum/basic_auth") }.to raise_error(
Ferrum::StatusError,
%r{Request to http://.*/ferrum/basic_auth failed \(net::ERR_INVALID_AUTH_CREDENTIALS\)}
)

expect(network.status).to eq(401)
expect(page.body).not_to include("Welcome, authenticated client")
Expand Down
4 changes: 2 additions & 2 deletions spec/node_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
expect(browser.network.traffic.length).to eq(1)
browser.at_css("a").click

# 1 for first load, 1 for load of new url, 1 for ping = 3 total
expect(browser.network.traffic.length).to eq(3)
# first load, load of new url, ping
expect(browser.network.traffic.length).to be <= 5
end

it "does not run into content quads error" do
Expand Down
14 changes: 7 additions & 7 deletions spec/page/screenshot_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
create_screenshot(path: file, scale: scale)
after = black_pixels_count[file]

expect(after.to_f / before).to eq(scale**2)
expect((after.to_f / before).round(2)).to eq(scale**2)
end
end

Expand Down Expand Up @@ -177,14 +177,14 @@ def create_screenshot(**options)
context "with fullscreen" do
it "supports screenshotting of fullscreen" do
browser.go_to("/ferrum/custom_html_size")
expect(browser.viewport_size).to eq([1024, 768])
expect(browser.viewport_size).to eq([1024, 681])

browser.screenshot(path: file, full: true)

File.open(file, "rb") do |f|
expect(ImageSize.new(f.read).size).to eq([1280, 1024].map { |s| s * device_pixel_ratio })
end
expect(browser.viewport_size).to eq([1024, 768])
expect(browser.viewport_size).to eq([1024, 681])
end

it "keeps current viewport" do
Expand Down Expand Up @@ -220,14 +220,14 @@ def create_screenshot(**options)
context "with area screenshot" do
it "supports screenshotting of an area" do
browser.go_to("/ferrum/custom_html_size")
expect(browser.viewport_size).to eq([1024, 768])
expect(browser.viewport_size).to eq([1024, 681])

browser.screenshot(path: file, area: { x: 0, y: 0, width: 300, height: 200 })

File.open(file, "rb") do |f|
expect(ImageSize.new(f.read).size).to eq([300, 200].map { |s| s * device_pixel_ratio })
end
expect(browser.viewport_size).to eq([1024, 768])
expect(browser.viewport_size).to eq([1024, 681])
end

it "keeps current viewport" do
Expand Down Expand Up @@ -435,13 +435,13 @@ def create_screenshot(path:, **options)
it "has a size of 1024x768 by default" do
browser.go_to

expect(browser.viewport_size).to eq([1024, 768])
expect(browser.viewport_size).to eq([1024, 681])
end

it "supports specifying viewport size with an option" do
browser = Ferrum::Browser.new(window_size: [800, 600])
browser.go_to(base_url)
expect(browser.viewport_size).to eq([800, 600])
expect(browser.viewport_size).to eq([800, 513])
ensure
browser&.quit
end
Expand Down
8 changes: 5 additions & 3 deletions spec/page_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@
end

it "handles server error" do
expect { page.go_to("/ferrum/server_error") }.not_to raise_error
expect { page.go_to("/ferrum/server_error") }.to raise_error(
Ferrum::StatusError,
%r{Request to http://.*/ferrum/server_error failed \(net::ERR_HTTP_RESPONSE_CODE_FAILURE\)}
)

expect(page.network.status).to eq(500)
expect(page.network.traffic.last.error.description)
.to eq("Failed to load resource: the server responded with a status of 500 (Internal Server Error)")
expect(page.network.traffic.first.error.error_text).to eq("net::ERR_HTTP_RESPONSE_CODE_FAILURE")
end
end
end
Expand Down
Loading