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
Original file line number Diff line number Diff line change
Expand Up @@ -4657,6 +4657,7 @@ struct v8_dom_runtime::implementation final {
this.credentials = String(options.credentials ?? input?.credentials ?? 'same-origin');
this.mode = String(options.mode ?? input?.mode ?? 'cors');
this.redirect = String(options.redirect ?? input?.redirect ?? 'follow');
this.destination = String(options.destination ?? input?.destination ?? '');
}
}

Expand Down Expand Up @@ -6343,7 +6344,7 @@ bool v8_dom_runtime::has_pending_tasks() const noexcept
|| !impl_->pending_dialog_close_events.empty()
|| !impl_->pending_programmatic_scroll_events.empty()
|| !impl_->pending_frame_hydrations.empty()
|| !impl_->connected_resources.empty()
|| impl_->has_ready_connected_resource_task()
|| impl_->resize_observers_pending
|| impl_->has_due_timer();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1930,7 +1930,8 @@
connected_resources.push_back(connected_resource_task{
&node,
v8::Global<v8::Context>(isolate, resource_context),
v8::Global<v8::Object>(isolate, wrapper)});
v8::Global<v8::Object>(isolate, wrapper),
{}, {}, {}, false});
}

void enqueue_connected_resources_subtree(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -914,17 +914,15 @@

void prefetch_connected_resources()
{
if (!load_resource_callback || connected_resources.empty()) return;
if (connected_resources.empty()) return;
for (auto& task : connected_resources) {
{
std::lock_guard lock(resource_prefetch_mutex);
if (prefetched_resources.size() >= maximum_resource_prefetches) return;
}
if (task.node == nullptr) continue;
auto task_context = task.context.Get(isolate);
if (task_context.IsEmpty()) continue;
activate_css_cascade(task_context);
v8::Context::Scope task_context_scope(task_context);
if (begin_connected_resource_interception(task)) continue;
if (!load_resource_callback) continue;
const auto& base = current_base_address();
if (task.node->tag == "link") {
const auto href = task.node->attributes.find("href");
Expand Down Expand Up @@ -984,7 +982,99 @@
return resource_root / relative;
}

bool load_connected_resource(dom_node& node, v8::Local<v8::Object> wrapper)
static uint32_t connected_resource_kind(const dom_node& node)
{
return node.tag == "script" ? WEBSCENE_RESOURCE_SCRIPT
: node.tag == "link" ? WEBSCENE_RESOURCE_STYLESHEET
: node.tag == "img" ? WEBSCENE_RESOURCE_IMAGE
: WEBSCENE_RESOURCE_DATA;
}

static uint32_t connected_resource_destination(const dom_node& node)
{
return node.tag == "script" ? WEBSCENE_REQUEST_DESTINATION_SCRIPT
: node.tag == "link" ? WEBSCENE_REQUEST_DESTINATION_STYLE
: node.tag == "img" ? WEBSCENE_REQUEST_DESTINATION_IMAGE
: WEBSCENE_REQUEST_DESTINATION_NONE;
}

bool begin_connected_resource_interception(connected_resource_task& task)
{
if (task.interception_started) return task.controlled_fetch.valid();
task.interception_started = true;
if (task.node == nullptr) return false;
const auto attribute_name = task.node->tag == "link" ? "href" : "src";
const auto authored = task.node->attributes.find(attribute_name);
if (authored == task.node->attributes.end() || authored->second.empty()
|| authored->second.starts_with("data:")
|| authored->second.starts_with("blob:")
|| object_urls.contains(authored->second)) {
return false;
}
const auto& base = current_base_address();
task.controlled_url = resolve_resource_url(authored->second, base);
resource_request_context request_context{
WEBSCENE_RESOURCE_INITIATOR_SUBRESOURCE,
current_security_origin(),
base,
WEBSCENE_FETCH_MODE_NONE,
connected_resource_destination(*task.node),
"GET", {}, {}, WEBSCENE_FETCH_CREDENTIALS_INCLUDE, {}};
request_context.cookie = cookie_header_for(
task.controlled_url, request_context);
task.controlled_fetch = enqueue_controlled_service_worker_fetch(
task.controlled_url, request_context);
if (task.controlled_fetch.valid()) {
task.controlled_deadline = std::chrono::steady_clock::now()
+ std::chrono::seconds(30);
}
return task.controlled_fetch.valid();
}

bool has_ready_connected_resource_task() const
{
if (connected_resources.empty()) return false;
const auto now = std::chrono::steady_clock::now();
const auto& task = connected_resources.front();
return !task.interception_started || !task.controlled_fetch.valid()
|| task.controlled_fetch.wait_for(std::chrono::seconds(0))
== std::future_status::ready
|| (task.controlled_deadline != std::chrono::steady_clock::time_point{}
&& now >= task.controlled_deadline);
}

bool load_connected_text_resource(
const std::string& value,
const std::string& base,
uint32_t kind,
std::string& content,
std::string& resolved_name,
std::shared_ptr<const immutable_text_source>* immutable_content,
const service_worker_fetch_result* controlled)
{
if (controlled == nullptr || !controlled->handled) {
return load_text_resource(
value, base, kind, content, resolved_name, immutable_content);
}
if (!controlled->error.empty() || controlled->status < 200U
|| controlled->status >= 300U) {
return false;
}
if (immutable_content != nullptr) immutable_content->reset();
content = controlled->body;
resolved_name = controlled->url.empty()
? resolve_resource_url(value, base)
: controlled->url;
record_feature(
"resource", "service-worker:intercepted-" + resource_kind_name(kind),
"supported", {}, "service-worker-fetch");
return true;
}

bool load_connected_resource(
dom_node& node,
v8::Local<v8::Object> wrapper,
const service_worker_fetch_result* controlled = nullptr)
{
auto local_context = isolate->GetCurrentContext();
const auto dispatch_resource_event = [&](const char* type) {
Expand Down Expand Up @@ -1033,12 +1123,14 @@
? (stylesheet = object_url->second,
stylesheet_name = href_iterator->second,
true)
: load_text_resource(
: load_connected_text_resource(
href_iterator->second,
base,
WEBSCENE_RESOURCE_STYLESHEET,
stylesheet,
stylesheet_name);
stylesheet_name,
nullptr,
controlled);
if (loaded) {
const auto first_new_rule = add_stylesheet(
std::move(stylesheet),
Expand Down Expand Up @@ -1084,7 +1176,9 @@
} else {
frame_last_error_value = "Unable to load connected stylesheet: " + href_iterator->second;
++frame_script_error_count;
return false;
const auto dispatched = dispatch_resource_event("error");
if (dispatched) frame_last_error_value.clear();
return dispatched;
}
}
} else if (node.tag == "script") {
Expand All @@ -1094,16 +1188,19 @@
std::shared_ptr<const immutable_text_source> immutable_source;
std::string script_name;
const auto& base = current_base_address();
if (!load_text_resource(
if (!load_connected_text_resource(
source_iterator->second,
base,
WEBSCENE_RESOURCE_SCRIPT,
source,
script_name,
&immutable_source)) {
&immutable_source,
controlled)) {
frame_last_error_value = "Unable to load connected script: " + source_iterator->second;
++frame_script_error_count;
return false;
const auto dispatched = dispatch_resource_event("error");
if (dispatched) frame_last_error_value.clear();
return dispatched;
}
std::string error;
// A dynamically inserted external script and its load event are one
Expand All @@ -1119,7 +1216,7 @@
: execute_in_context(local_context,script_source,script_name,error,false,std::move(immutable_source)))) {
frame_last_error_value = "Connected script failed: " + error;
++frame_script_error_count;
return false;
return dispatch_resource_event("error");
}
loaded_resource_names.push_back(resource_leaf_name(script_name));
++frame_script_execution_count;
Expand Down Expand Up @@ -1191,12 +1288,14 @@
true)
: decode_base64_data_url(source->second, markup)
? (resolved_source = source->second, true)
: load_text_resource(
: load_connected_text_resource(
source->second,
base,
WEBSCENE_RESOURCE_IMAGE,
markup,
resolved_source);
resolved_source,
nullptr,
controlled);
if (!loaded) {
return mark_broken_image();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,21 @@
static constexpr size_t maximum_service_worker_fetch_body_bytes =
64U * 1024U * 1024U;

struct service_worker_fetch_result final {
bool handled{};
uint32_t status{200U};
std::string status_text{"OK"};
std::string url;
std::string response_type{"basic"};
bool redirected{};
std::vector<std::pair<std::string, std::string>> headers;
std::string body;
std::string error;
};

struct service_worker_fetch_request final {
std::string url;
std::string method{"GET"};
uint32_t destination{WEBSCENE_REQUEST_DESTINATION_NONE};
std::vector<std::pair<std::string, std::string>> headers;
std::string client_id;
uint64_t client_generation{};
std::promise<service_worker_fetch_result> promise;
std::function<void()> notify_client;

void settle(service_worker_fetch_result result)
{
promise.set_value(std::move(result));
if (notify_client) notify_client();
}
};

static void set_service_worker_client_properties(
Expand Down Expand Up @@ -887,7 +883,7 @@
.ToLocal(&request_constructor_value)
|| !request_constructor_value->IsFunction()) {
failure.error = "Service worker fetch dispatcher is unavailable";
fetch_request->promise.set_value(std::move(failure));
fetch_request->settle(std::move(failure));
std::lock_guard lock(state->mutex);
if (state->active_fetches > 0U) --state->active_fetches;
} else {
Expand All @@ -896,6 +892,21 @@
realm,
js_string(child->isolate, "method"),
js_dom_string(child->isolate, fetch_request->method)).Check();
const auto destination = fetch_request->destination
== WEBSCENE_REQUEST_DESTINATION_SCRIPT ? "script"
: fetch_request->destination
== WEBSCENE_REQUEST_DESTINATION_STYLE ? "style"
: fetch_request->destination
== WEBSCENE_REQUEST_DESTINATION_IMAGE ? "image"
: fetch_request->destination
== WEBSCENE_REQUEST_DESTINATION_DOCUMENT ? "document"
: fetch_request->destination
== WEBSCENE_REQUEST_DESTINATION_FONT ? "font"
: "";
options->Set(
realm,
js_string(child->isolate, "destination"),
js_string(child->isolate, destination)).Check();
auto headers = v8::Array::New(
child->isolate,
static_cast<int>(fetch_request->headers.size()));
Expand All @@ -920,7 +931,7 @@
if (!request_constructor_value.As<v8::Function>()->NewInstance(
realm, 2, request_arguments).ToLocal(&request_value)) {
failure.error = child->describe_reported_exception(caught, realm);
fetch_request->promise.set_value(std::move(failure));
fetch_request->settle(std::move(failure));
std::lock_guard lock(state->mutex);
if (state->active_fetches > 0U) --state->active_fetches;
} else {
Expand All @@ -946,7 +957,7 @@
|| !dispatched->IsPromise()) {
failure.error = child->describe_reported_exception(
caught, realm);
fetch_request->promise.set_value(std::move(failure));
fetch_request->settle(std::move(failure));
std::lock_guard lock(state->mutex);
if (state->active_fetches > 0U) --state->active_fetches;
} else {
Expand Down Expand Up @@ -1053,6 +1064,27 @@
for (auto iterator = pending_fetches.begin();
iterator != pending_fetches.end();) {
auto promise = iterator->promise.Get(child->isolate);
auto client_retired = false;
{
std::lock_guard lock(state->mutex);
const auto client = state->clients.find(
iterator->request->client_id);
client_retired = client == state->clients.end()
|| client->second.generation
!= iterator->request->client_generation;
if (client_retired && state->active_fetches > 0U) {
--state->active_fetches;
}
}
if (client_retired) {
service_worker_fetch_result retired;
retired.handled = true;
retired.error = "Service worker fetch client was retired";
iterator->request->settle(std::move(retired));
iterator->promise.Reset();
iterator = pending_fetches.erase(iterator);
continue;
}
if (promise->State() == v8::Promise::kPending) {
++iterator;
continue;
Expand Down Expand Up @@ -1123,17 +1155,9 @@
}
{
std::lock_guard lock(state->mutex);
const auto client = state->clients.find(
iterator->request->client_id);
if (client == state->clients.end()
|| client->second.generation
!= iterator->request->client_generation) {
result.handled = true;
result.error = "Service worker fetch client was retired";
}
if (state->active_fetches > 0U) --state->active_fetches;
}
iterator->request->promise.set_value(std::move(result));
iterator->request->settle(std::move(result));
iterator->promise.Reset();
iterator = pending_fetches.erase(iterator);
}
Expand Down Expand Up @@ -1653,6 +1677,7 @@
request->url = url;
request->method = request_context.method.empty()
? "GET" : request_context.method;
request->destination = request_context.destination;
if (!request_context.content_type.empty()) {
request->headers.emplace_back(
"content-type", request_context.content_type);
Expand All @@ -1662,6 +1687,7 @@
}
request->client_id = client->descriptor.id;
request->client_generation = client->descriptor.generation;
request->notify_client = runtime_work_available;
auto future = request->promise.get_future().share();
{
std::lock_guard lock(runtime->mutex);
Expand All @@ -1672,14 +1698,14 @@
service_worker_fetch_result result;
result.handled = true;
result.error = "Service worker fetch client was retired";
request->promise.set_value(std::move(result));
request->settle(std::move(result));
return future;
}
if (runtime->active_fetches >= maximum_service_worker_fetches) {
service_worker_fetch_result result;
result.handled = true;
result.error = "Service worker fetch queue capacity exhausted";
request->promise.set_value(std::move(result));
request->settle(std::move(result));
return future;
}
++runtime->active_fetches;
Expand Down
Loading
Loading