-
-
Notifications
You must be signed in to change notification settings - Fork 132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Service worker support #391
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -71,11 +71,12 @@ def reset | |
# @return [Cookies] | ||
attr_reader :cookies | ||
|
||
def initialize(target_id, browser, proxy: nil) | ||
def initialize(target_id, browser, proxy: nil, type: "page") | ||
@frames = Concurrent::Map.new | ||
@main_frame = Frame.new(nil, self) | ||
@browser = browser | ||
@target_id = target_id | ||
@type = type | ||
@timeout = @browser.timeout | ||
@event = Event.new.tap(&:set) | ||
self.proxy = proxy | ||
|
@@ -355,13 +356,21 @@ def subscribe | |
end | ||
|
||
def prepare_page | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should I keep everything in this method or split out the worker preparation into a different method? |
||
command("Page.enable") | ||
command("Runtime.enable") | ||
command("DOM.enable") | ||
command("CSS.enable") | ||
command("Log.enable") | ||
if @type == "page" | ||
command("Page.enable") | ||
command("DOM.enable") | ||
command("CSS.enable") | ||
command("Runtime.enable") | ||
command("Log.enable") | ||
command("ServiceWorker.enable") | ||
end | ||
|
||
command("Network.enable") | ||
|
||
if @type == "worker" | ||
command("Runtime.runIfWaitingForDebugger") | ||
end | ||
|
||
if use_authorized_proxy? | ||
network.authorize(user: @proxy_user, | ||
password: @proxy_password, | ||
|
@@ -387,6 +396,8 @@ def prepare_page | |
|
||
inject_extensions | ||
|
||
return if @type == "worker" | ||
|
||
width, height = @browser.window_size | ||
resize(width: width, height: height) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ class Target | |
# You can create page yourself and assign it to target, used in cuprite | ||
# where we enhance page class and build page ourselves. | ||
attr_writer :page | ||
attr_reader :connection | ||
|
||
def initialize(browser, params = nil) | ||
@page = nil | ||
|
@@ -23,12 +24,19 @@ def attached? | |
end | ||
|
||
def page | ||
@page ||= build_page | ||
connection if page? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initially I had this return nil if it was a worker because other parts of the code were causing failures otherwise, but not sure what the impact of this is. |
||
end | ||
|
||
def network | ||
connection.network | ||
end | ||
|
||
def build(**options) | ||
connection(**options) | ||
end | ||
|
||
def build_page(**options) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should I just |
||
maybe_sleep_if_new_window | ||
Page.new(id, @browser, **options) | ||
connection(**options) | ||
end | ||
|
||
def id | ||
|
@@ -59,9 +67,27 @@ def window? | |
!!opener_id | ||
end | ||
|
||
def page? | ||
@params["type"] == "page" | ||
end | ||
|
||
def worker? | ||
@params["type"] == "worker" | ||
end | ||
|
||
def maybe_sleep_if_new_window | ||
# Dirty hack because new window doesn't have events at all | ||
sleep(NEW_WINDOW_WAIT) if window? | ||
end | ||
|
||
def connection(**options) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure what the proper naming would be for this if it can be both a page and a worker. |
||
@connection ||= begin | ||
maybe_sleep_if_new_window if page? | ||
|
||
options.merge!(type: @params["type"]) | ||
|
||
Page.new(id, @browser, **options) | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,19 @@ | |
browser.go_to("/ferrum/with_js") | ||
expect(browser.network.traffic.length).to eq(4) | ||
end | ||
|
||
it "keeps track of service workers" do | ||
page.go_to("/ferrum/service_worker") | ||
|
||
browser.network.wait_for_idle | ||
traffic = browser.targets.values.map { _1.network.traffic }.flatten | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should the worker bubble up its traffic to its parent page? |
||
urls = traffic.map { |e| e.request.url } | ||
|
||
expect(urls.size).to eq(3) | ||
expect(urls.grep(%r{/ferrum/service_worker$}).size).to eq(1) | ||
expect(urls.grep(%r{/ferrum/one.png$}).size).to eq(1) | ||
expect(urls.grep(%r{^blob:}).size).to eq(1) | ||
end | ||
end | ||
|
||
it "#wait_for_idle" do | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just an image I came up with on Figma, no particular attachment to it if you'd like something else. I tried a SVG but it didn't work with the |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<html> | ||
<body> | ||
<div id="service_worker_code" style="display: none"> | ||
async function imageFromUrl(url) { | ||
const response = await fetch(url); | ||
|
||
if (!response.ok) { | ||
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`); | ||
} | ||
|
||
const blobResult = await response.blob(); | ||
const imageBitmap = await createImageBitmap(blobResult); | ||
return imageBitmap; | ||
} | ||
self.onmessage = async (event) => { | ||
try { | ||
const image = await imageFromUrl(event.data.url); | ||
|
||
self.postMessage({ | ||
data: image | ||
}, [image]); | ||
} | ||
catch(e) | ||
{ | ||
self.postMessage({ | ||
error: e | ||
}); | ||
} | ||
}; | ||
</div> | ||
<script> | ||
var code = document.getElementById("service_worker_code").innerText; | ||
var objectUrl = URL.createObjectURL(new Blob([code],{ type: "application/javascript"})) | ||
var worker = new Worker(objectUrl); | ||
|
||
worker.addEventListener("message", function(event) { | ||
let canvas = document.getElementById('canvas'); | ||
let context = canvas.getContext('2d'); | ||
let imageBitmap = event.data.data; | ||
context.drawImage(imageBitmap, 0, 0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Drawn to the canvas so the page isn't blank. |
||
}); | ||
|
||
let path = "one.png" | ||
let url = new URL(path, document.baseURI).href; | ||
worker.postMessage({ url: url }); | ||
</script> | ||
<canvas width="400" height="400" id="canvas"></canvas> | ||
</body> | ||
</html> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want me to use
Page
or create another class for workers?