Compare commits

...

11 commits

Author SHA1 Message Date
Timothy Flynn
9b8f6b8108 RequestServer: Issue a network request for failed cached responses
If transferring a cached response body fails for any reason, we will now
issue a network request instead of failing the request outright.

The catch here is that we will have already transferred the response
code and headers to the client, and potentially some of the body. So we
attempt to only request the remaining data over the network using a
range request. This feels a bit sketchy, but this is also how Chromium
behaves.

However, the server may or may not support range requests. If they do,
we can expect an HTTP 206 response with the bytes we need. If not, we
will receive an HTTP 200 (assuming the request succeeded), along with
the entire object's body. In this case, we also behave like Chromium,
and internally drop number of bytes we had already transferred.
2025-10-16 09:06:48 -04:00
Timothy Flynn
fc9233f198 RequestServer: Delete unreadable cache files (for now)
If we are unable to pipe the response body from a cache file to the
client, let's take the extra safe approach of deleting the cache file
for now. We already remove the file if we weren't able to read its
metadata during initialization.
2025-10-16 09:06:48 -04:00
Jelle Raaijmakers
27cb5d8c1e CI: Switch Flatpak builds back temporarily to GitHub runners
Until Blacksmith runners enable redirect_dir, the flatpak builder
container is unable to install updates for packages in its system dirs.
2025-10-16 14:26:19 +02:00
Jelle Raaijmakers
188384710a CI: Remove options with default values from Flatpak build
No functional changes.
2025-10-16 14:26:19 +02:00
Zaggy1024
9cd0f9c445 LibMedia: Support BT.470 System B/G color primaries 2025-10-16 05:12:29 -05:00
Zaggy1024
b684bc0a9d LibMedia: Reorder the BT.2020 matrix to match previous lines 2025-10-16 05:12:29 -05:00
Callum Law
2af071380e LibWeb: Dont load a style sheet's fonts until it has an owning document
We need a style sheet to have an owning document to load it's fonts (to
generate a length resolution context).

Fixes #6445
2025-10-16 10:27:32 +01:00
Callum Law
9651969708 LibWeb: Propagate CSSStyleSheet owning documents and shadow roots
Previously these were only stored on the root style sheet and were
accessed by imported stylesheets via their owner rule.

Propagating these to imported style sheets allows us to more easily know
when they change for said imported style sheets.
2025-10-16 10:27:32 +01:00
Callum Law
3708fc6aa7 LibWeb: Resolve relative lengths in @font-face using correct viewport
As with everywhere else we should be using the document rather than the
window's viewports.

Fixes #6467
2025-10-16 10:27:32 +01:00
Callum Law
29fb63c928 LibWeb: Support length resolution context for document lacking navigable
Some documents (e.g. those created by DOMParser.parseFromString()) will
not be associated with a navigable. These documents effectively have a
viewport of 0x0.
2025-10-16 10:27:32 +01:00
Andreas Kling
c23ed104e5 LibJS: Micro-optimize ECMAScriptFunctionObject::internal_construct()
- Add FLATTEN (same as we do for internal_call()).
- Demote nice-to-have VERIFYs to ASSERTs.
- Pass already-known Realm to ordinary_create_from_constructor

1.03x speedup on Octane/earley-boyer.js
2025-10-16 10:47:10 +02:00
23 changed files with 300 additions and 55 deletions

View file

@ -23,15 +23,16 @@ jobs:
&& contains(github.event.pull_request.labels.*.name, 'flatpak')
name: Flatpak ${{ matrix.arch }}
# FIXME: Temporarily run on GitHub runners until Blacksmith runners have overlay redirect_dir enabled.
strategy:
fail-fast: false
matrix:
arch: ['x86_64']
runner_labels: ['["blacksmith-8vcpu-ubuntu-2404"]']
runner_labels: ['["ubuntu-24.04"]']
include:
- arch: 'aarch64'
runner_labels: '["blacksmith-8vcpu-ubuntu-2404-arm"]'
runner_labels: '["ubuntu-24.04-arm"]'
secrets: inherit
uses: ./.github/workflows/flatpak-template.yml

View file

@ -24,7 +24,5 @@ jobs:
with:
bundle: Ladybird.flatpak
manifest-path: Meta/CMake/flatpak/org.ladybird.Ladybird.json
cache: 'true'
arch: ${{ inputs.arch }}
# Note: default cache key is 'flatpak-builder-${arch}-${sha256(manifestPath)}'
upload-artifact: 'true'

View file

@ -81,15 +81,17 @@ jobs:
flatpak:
if: github.repository == 'LadybirdBrowser/ladybird'
name: Flatpak ${{ matrix.arch }}
# FIXME: Temporarily run on GitHub runners until Blacksmith runners have overlay redirect_dir enabled.
strategy:
fail-fast: false
matrix:
arch: [ 'x86_64' ]
runner_labels: [ '["blacksmith-8vcpu-ubuntu-2404"]' ]
runner_labels: [ '["ubuntu-24.04"]' ]
include:
- arch: 'aarch64'
runner_labels: '["blacksmith-8vcpu-ubuntu-2404-arm"]'
runner_labels: '["ubuntu-24.04-arm"]'
secrets: inherit
uses: ./.github/workflows/flatpak-template.yml

View file

@ -157,13 +157,19 @@ ALWAYS_INLINE ThrowCompletionOr<GC::Ref<Object>> construct(VM& vm, FunctionObjec
// 10.1.13 OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] ), https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor
template<typename T, typename... Args>
ThrowCompletionOr<GC::Ref<T>> ordinary_create_from_constructor(VM& vm, FunctionObject const& constructor, GC::Ref<Object> (Intrinsics::*intrinsic_default_prototype)(), Args&&... args)
ALWAYS_INLINE ThrowCompletionOr<GC::Ref<T>> ordinary_create_from_constructor(VM& vm, Realm& realm, FunctionObject const& constructor, GC::Ref<Object> (Intrinsics::*intrinsic_default_prototype)(), Args&&... args)
{
auto& realm = *vm.current_realm();
auto* prototype = TRY(get_prototype_from_constructor(vm, constructor, intrinsic_default_prototype));
return realm.create<T>(forward<Args>(args)..., *prototype);
}
// 10.1.13 OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] ), https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor
template<typename T, typename... Args>
ALWAYS_INLINE ThrowCompletionOr<GC::Ref<T>> ordinary_create_from_constructor(VM& vm, FunctionObject const& constructor, GC::Ref<Object> (Intrinsics::*intrinsic_default_prototype)(), Args&&... args)
{
return ordinary_create_from_constructor<T>(vm, *vm.current_realm(), constructor, intrinsic_default_prototype, forward<Args>(args)...);
}
// 7.3.35 AddValueToKeyedGroup ( groups, key, value ), https://tc39.es/ecma262/#sec-add-value-to-keyed-group
template<typename GroupsType, typename KeyType>
void add_value_to_keyed_group(VM& vm, GroupsType& groups, KeyType key, Value value)

View file

@ -549,7 +549,7 @@ FLATTEN ThrowCompletionOr<Value> ECMAScriptFunctionObject::internal_call(Executi
}
// 10.2.2 [[Construct]] ( argumentsList, newTarget ), https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget
ThrowCompletionOr<GC::Ref<Object>> ECMAScriptFunctionObject::internal_construct(ExecutionContext& callee_context, FunctionObject& new_target)
FLATTEN ThrowCompletionOr<GC::Ref<Object>> ECMAScriptFunctionObject::internal_construct(ExecutionContext& callee_context, FunctionObject& new_target)
{
auto& vm = this->vm();
@ -566,14 +566,14 @@ ThrowCompletionOr<GC::Ref<Object>> ECMAScriptFunctionObject::internal_construct(
// 3. If kind is base, then
if (kind == ConstructorKind::Base) {
// a. Let thisArgument be ? OrdinaryCreateFromConstructor(newTarget, "%Object.prototype%").
this_argument = TRY(ordinary_create_from_constructor<Object>(vm, new_target, &Intrinsics::object_prototype, ConstructWithPrototypeTag::Tag));
this_argument = TRY(ordinary_create_from_constructor<Object>(vm, *realm(), new_target, &Intrinsics::object_prototype, ConstructWithPrototypeTag::Tag));
}
// 4. Let calleeContext be PrepareForOrdinaryCall(F, newTarget).
prepare_for_ordinary_call(vm, callee_context, &new_target);
// 5. Assert: calleeContext is now the running execution context.
VERIFY(&vm.running_execution_context() == &callee_context);
ASSERT(&vm.running_execution_context() == &callee_context);
// 6. If kind is base, then
if (kind == ConstructorKind::Base) {
@ -628,7 +628,7 @@ ThrowCompletionOr<GC::Ref<Object>> ECMAScriptFunctionObject::internal_construct(
auto this_binding = TRY(constructor_env->get_this_binding(vm));
// 16. Assert: Type(thisBinding) is Object.
VERIFY(this_binding.is_object());
ASSERT(this_binding.is_object());
// 17. Return thisBinding.
return this_binding.as_object();
@ -765,7 +765,7 @@ void ECMAScriptFunctionObject::ordinary_call_bind_this(VM& vm, ExecutionContext&
this_value = MUST(this_argument.to_object(vm));
// ii. NOTE: ToObject produces wrapper objects using calleeRealm.
VERIFY(vm.current_realm() == callee_realm);
ASSERT(vm.current_realm() == callee_realm);
}
}
@ -908,7 +908,7 @@ ThrowCompletionOr<Value> ECMAScriptFunctionObject::ordinary_call_evaluate_body(V
if (kind() == FunctionKind::Async)
return AsyncFunctionDriverWrapper::create(realm, generator_object);
VERIFY(kind() == FunctionKind::Generator);
ASSERT(kind() == FunctionKind::Generator);
return generator_object;
}

View file

@ -50,6 +50,11 @@ ALWAYS_INLINE constexpr FloatMatrix3x3 generate_rgb_to_xyz_matrix(FloatVector2 r
return vectors_to_matrix(matrix_row(matrix, 0) * scale_vector, matrix_row(matrix, 1) * scale_vector, matrix_row(matrix, 2) * scale_vector);
}
constexpr FloatVector2 BT_470_BG_WHITE = { 0.310f, 0.316f };
constexpr FloatVector2 BT_470_BG_RED = { 0.67f, 0.33f };
constexpr FloatVector2 BT_470_BG_GREEN = { 0.21f, 0.71f };
constexpr FloatVector2 BT_470_BG_BLUE = { 0.14f, 0.08f };
constexpr FloatVector2 ILLUMINANT_D65 = { 0.3127f, 0.3290f };
constexpr FloatVector2 BT_709_RED = { 0.64f, 0.33f };
@ -64,14 +69,18 @@ constexpr FloatVector2 BT_2020_RED = { 0.708f, 0.292f };
constexpr FloatVector2 BT_2020_GREEN = { 0.170f, 0.797f };
constexpr FloatVector2 BT_2020_BLUE = { 0.131f, 0.046f };
constexpr FloatMatrix3x3 bt_2020_rgb_to_xyz = generate_rgb_to_xyz_matrix(BT_2020_RED, BT_2020_GREEN, BT_2020_BLUE, ILLUMINANT_D65);
constexpr FloatMatrix3x3 bt_470_bg_rgb_to_xyz = generate_rgb_to_xyz_matrix(BT_470_BG_RED, BT_470_BG_GREEN, BT_470_BG_BLUE, BT_470_BG_WHITE);
constexpr FloatMatrix3x3 bt_709_rgb_to_xyz = generate_rgb_to_xyz_matrix(BT_709_RED, BT_709_GREEN, BT_709_BLUE, ILLUMINANT_D65);
constexpr FloatMatrix3x3 bt_601_rgb_to_xyz = generate_rgb_to_xyz_matrix(BT_601_RED, BT_601_GREEN, BT_601_BLUE, ILLUMINANT_D65);
constexpr FloatMatrix3x3 bt_2020_rgb_to_xyz = generate_rgb_to_xyz_matrix(BT_2020_RED, BT_2020_GREEN, BT_2020_BLUE, ILLUMINANT_D65);
DecoderErrorOr<FloatMatrix3x3> get_conversion_matrix(ColorPrimaries input_primaries, ColorPrimaries output_primaries)
{
FloatMatrix3x3 input_conversion_matrix;
switch (input_primaries) {
case ColorPrimaries::BT470BG:
input_conversion_matrix = bt_470_bg_rgb_to_xyz;
break;
case ColorPrimaries::BT709:
input_conversion_matrix = bt_709_rgb_to_xyz;
break;
@ -87,6 +96,9 @@ DecoderErrorOr<FloatMatrix3x3> get_conversion_matrix(ColorPrimaries input_primar
FloatMatrix3x3 output_conversion_matrix;
switch (output_primaries) {
case ColorPrimaries::BT470BG:
output_conversion_matrix = bt_470_bg_rgb_to_xyz.inverse();
break;
case ColorPrimaries::BT709:
output_conversion_matrix = bt_709_rgb_to_xyz.inverse();
break;

View file

@ -21,7 +21,6 @@ enum class NetworkError {
MalformedUrl,
InvalidContentEncoding,
RequestServerDied,
CacheReadFailed,
Unknown,
};
@ -48,8 +47,6 @@ constexpr StringView network_error_to_string(NetworkError network_error)
return "Response could not be decoded with its Content-Encoding"sv;
case NetworkError::RequestServerDied:
return "RequestServer is currently unavailable"sv;
case NetworkError::CacheReadFailed:
return "RequestServer encountered an error reading a cached HTTP response"sv;
case NetworkError::Unknown:
return "An unexpected network error occurred"sv;
}

View file

@ -53,6 +53,12 @@ void CSSImportRule::visit_edges(Cell::Visitor& visitor)
void CSSImportRule::set_parent_style_sheet(CSSStyleSheet* parent_style_sheet)
{
Base::set_parent_style_sheet(parent_style_sheet);
if (m_style_sheet && parent_style_sheet) {
for (auto owning_document_or_shadow_root : parent_style_sheet->owning_documents_or_shadow_roots())
m_style_sheet->add_owning_document_or_shadow_root(*owning_document_or_shadow_root);
}
// Crude detection of whether we're already fetching.
if (m_style_sheet || m_document_load_event_delayer.has_value())
return;
@ -174,9 +180,13 @@ void CSSImportRule::set_style_sheet(GC::Ref<CSSStyleSheet> style_sheet)
{
m_style_sheet = style_sheet;
m_style_sheet->set_owner_css_rule(this);
m_document->style_computer().invalidate_rule_cache();
m_document->style_computer().load_fonts_from_sheet(*m_style_sheet);
m_document->invalidate_style(DOM::StyleInvalidationReason::CSSImportRule);
if (m_parent_style_sheet) {
for (auto owning_document_or_shadow_root : m_parent_style_sheet->owning_documents_or_shadow_roots())
m_style_sheet->add_owning_document_or_shadow_root(*owning_document_or_shadow_root);
}
m_style_sheet->invalidate_owners(DOM::StyleInvalidationReason::CSSImportRule);
}
// https://drafts.csswg.org/cssom/#dom-cssimportrule-media

View file

@ -321,11 +321,31 @@ void CSSStyleSheet::add_owning_document_or_shadow_root(DOM::Node& document_or_sh
{
VERIFY(document_or_shadow_root.is_document() || document_or_shadow_root.is_shadow_root());
m_owning_documents_or_shadow_roots.set(document_or_shadow_root);
// All owning documents or shadow roots must be part of the same document so we only need to load this style
// sheet's fonts against the document of the first
if (this->owning_documents_or_shadow_roots().size() == 1)
document_or_shadow_root.document().style_computer().load_fonts_from_sheet(*this);
for (auto const& import_rule : m_import_rules) {
if (import_rule->loaded_style_sheet())
import_rule->loaded_style_sheet()->add_owning_document_or_shadow_root(document_or_shadow_root);
}
}
void CSSStyleSheet::remove_owning_document_or_shadow_root(DOM::Node& document_or_shadow_root)
{
m_owning_documents_or_shadow_roots.remove(document_or_shadow_root);
// All owning documents or shadow roots must be part of the same document so we only need to unload this style
// sheet's fonts once we have none remaining.
if (this->owning_documents_or_shadow_roots().size() == 0)
document_or_shadow_root.document().style_computer().unload_fonts_from_sheet(*this);
for (auto const& import_rule : m_import_rules) {
if (import_rule->loaded_style_sheet())
import_rule->loaded_style_sheet()->remove_owning_document_or_shadow_root(document_or_shadow_root);
}
}
void CSSStyleSheet::invalidate_owners(DOM::StyleInvalidationReason reason)
@ -342,11 +362,6 @@ GC::Ptr<DOM::Document> CSSStyleSheet::owning_document() const
if (!m_owning_documents_or_shadow_roots.is_empty())
return (*m_owning_documents_or_shadow_roots.begin())->document();
if (m_owner_css_rule && m_owner_css_rule->parent_style_sheet()) {
if (auto document = m_owner_css_rule->parent_style_sheet()->owning_document())
return document;
}
if (auto* element = const_cast<CSSStyleSheet*>(this)->owner_node())
return element->document();

View file

@ -66,6 +66,7 @@ public:
bool evaluate_media_queries(DOM::Document const&);
void for_each_effective_keyframes_at_rule(Function<void(CSSKeyframesRule const&)> const& callback) const;
HashTable<GC::Ptr<DOM::Node>> owning_documents_or_shadow_roots() const { return m_owning_documents_or_shadow_roots; }
void add_owning_document_or_shadow_root(DOM::Node& document_or_shadow_root);
void remove_owning_document_or_shadow_root(DOM::Node& document_or_shadow_root);
void invalidate_owners(DOM::StyleInvalidationReason);

View file

@ -142,8 +142,13 @@ Length::ResolutionContext Length::ResolutionContext::for_document(DOM::Document
auto const& initial_font = document.style_computer().initial_font();
Gfx::FontPixelMetrics const& initial_font_metrics = initial_font.pixel_metrics();
Length::FontMetrics font_metrics { CSSPixels { initial_font.pixel_size() }, initial_font_metrics, InitialValues::line_height() };
CSSPixelRect viewport_rect;
if (document.navigable())
viewport_rect = document.navigable()->viewport_rect();
return Length::ResolutionContext {
.viewport_rect = document.navigable()->viewport_rect(),
.viewport_rect = viewport_rect,
.font_metrics = font_metrics,
.root_font_metrics = font_metrics,
};

View file

@ -77,7 +77,7 @@ ParsedFontFace ParsedFontFace::from_descriptors(CSSFontFaceDescriptors const& de
font_family = extract_font_name(*value);
ComputationContext computation_context {
.length_resolution_context = Length::ResolutionContext::for_window(*descriptors.parent_rule()->parent_style_sheet()->owning_document()->window())
.length_resolution_context = Length::ResolutionContext::for_document(*descriptors.parent_rule()->parent_style_sheet()->owning_document())
};
Optional<int> weight;

View file

@ -117,7 +117,6 @@ void StyleSheetList::add_sheet(CSSStyleSheet& sheet)
}
document().style_computer().invalidate_rule_cache();
document().style_computer().load_fonts_from_sheet(sheet);
document_or_shadow_root().invalidate_style(DOM::StyleInvalidationReason::StyleSheetListAddSheet);
}
@ -132,7 +131,6 @@ void StyleSheetList::remove_sheet(CSSStyleSheet& sheet)
return;
}
m_document_or_shadow_root->document().style_computer().unload_fonts_from_sheet(sheet);
m_document_or_shadow_root->document().style_computer().invalidate_rule_cache();
document_or_shadow_root().invalidate_style(DOM::StyleInvalidationReason::StyleSheetListRemoveSheet);
}

View file

@ -31,7 +31,6 @@ GC::Ref<WebIDL::ObservableArray> create_adopted_style_sheets_list(Node& document
return WebIDL::NotAllowedError::create(document_or_shadow_root.realm(), "Sharing a StyleSheet between documents is not allowed."_utf16);
style_sheet.add_owning_document_or_shadow_root(document_or_shadow_root);
document_or_shadow_root.document().style_computer().load_fonts_from_sheet(style_sheet);
document_or_shadow_root.document().style_computer().invalidate_rule_cache();
document_or_shadow_root.invalidate_style(DOM::StyleInvalidationReason::AdoptedStyleSheetsList);
return {};
@ -43,7 +42,6 @@ GC::Ref<WebIDL::ObservableArray> create_adopted_style_sheets_list(Node& document
auto& style_sheet = static_cast<CSS::CSSStyleSheet&>(object);
style_sheet.remove_owning_document_or_shadow_root(document_or_shadow_root);
document_or_shadow_root.document().style_computer().unload_fonts_from_sheet(style_sheet);
document_or_shadow_root.document().style_computer().invalidate_rule_cache();
document_or_shadow_root.invalidate_style(DOM::StyleInvalidationReason::AdoptedStyleSheetsList);
return {};

View file

@ -346,6 +346,10 @@ void CacheEntryReader::pipe_error(Error error)
{
dbgln("\033[31;1mError transferring cache to pipe for\033[0m {}: {}", m_url, error);
// FIXME: We may not want to actually remove the cache file for all errors. For now, let's assume the file is not
// useable at this point and remove it.
remove();
if (m_on_pipe_error)
m_on_pipe_error(m_bytes_piped);

View file

@ -117,18 +117,24 @@ struct ConnectionFromClient::ActiveRequest : public Weakable<ActiveRequest> {
i32 request_id { 0 };
WeakPtr<ConnectionFromClient> client;
int writer_fd { 0 };
HTTP::HeaderMap headers;
bool got_all_headers { false };
bool is_connect_only { false };
size_t downloaded_so_far { 0 };
URL::URL url;
ByteString method;
Optional<String> reason_phrase;
ByteBuffer body;
AllocatingMemoryStream send_buffer;
NonnullRefPtr<Core::Notifier> write_notifier;
bool done_fetching { false };
Optional<long> http_status_code;
HTTP::HeaderMap headers;
bool got_all_headers { false };
Optional<size_t> start_offset_of_resumed_response;
size_t bytes_transferred_to_client { 0 };
Optional<CacheEntryWriter&> cache_entry;
UnixDateTime request_start_time;
@ -161,9 +167,42 @@ struct ConnectionFromClient::ActiveRequest : public Weakable<ActiveRequest> {
ErrorOr<void> write_queued_bytes_without_blocking()
{
auto available_bytes = send_buffer.used_buffer_size();
// If we've received a response to a range request that is not the partial content (206) we requested, we must
// only transfer the subset of data that WebContent now needs. We discard all received bytes up to the expected
// start of the remaining data, and then transfer the remaining bytes.
if (start_offset_of_resumed_response.has_value()) {
if (http_status_code == 206) {
start_offset_of_resumed_response.clear();
} else if (http_status_code == 200) {
// All bytes currently available have already been transferred. Discard them entirely.
if (bytes_transferred_to_client + available_bytes <= *start_offset_of_resumed_response) {
bytes_transferred_to_client += available_bytes;
MUST(send_buffer.discard(available_bytes));
return {};
}
// Some bytes currently available have already been transferred. Discard those bytes and transfer the rest.
if (bytes_transferred_to_client + available_bytes > *start_offset_of_resumed_response) {
auto bytes_to_discard = *start_offset_of_resumed_response - bytes_transferred_to_client;
bytes_transferred_to_client += bytes_to_discard;
available_bytes -= bytes_to_discard;
MUST(send_buffer.discard(bytes_to_discard));
}
start_offset_of_resumed_response.clear();
} else {
return Error::from_string_literal("Unacceptable status code for resumed HTTP request");
}
}
Vector<u8> bytes_to_send;
bytes_to_send.resize(send_buffer.used_buffer_size());
bytes_to_send.resize(available_bytes);
send_buffer.peek_some(bytes_to_send);
auto result = Core::System::write(this->writer_fd, bytes_to_send);
if (result.is_error()) {
if (result.error().code() != EAGAIN) {
@ -180,7 +219,9 @@ struct ConnectionFromClient::ActiveRequest : public Weakable<ActiveRequest> {
cache_entry.clear();
}
bytes_transferred_to_client += result.value();
MUST(send_buffer.discard(result.value()));
write_notifier->set_enabled(!send_buffer.is_eof());
if (send_buffer.is_eof() && done_fetching)
schedule_self_destruction();
@ -217,16 +258,26 @@ struct ConnectionFromClient::ActiveRequest : public Weakable<ActiveRequest> {
void flush_headers_if_needed()
{
if (!http_status_code.has_value())
http_status_code = acquire_http_status_code();
if (got_all_headers)
return;
got_all_headers = true;
long http_status_code = 0;
auto result = curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &http_status_code);
VERIFY(result == CURLE_OK);
client->async_headers_became_available(request_id, headers, http_status_code, reason_phrase);
client->async_headers_became_available(request_id, headers, *http_status_code, reason_phrase);
if (g_disk_cache.has_value())
cache_entry = g_disk_cache->create_entry(url, method, http_status_code, reason_phrase, headers, request_start_time);
cache_entry = g_disk_cache->create_entry(url, method, *http_status_code, reason_phrase, headers, request_start_time);
}
long acquire_http_status_code() const
{
long code = 0;
auto result = curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &code);
VERIFY(result == CURLE_OK);
return code;
}
};
@ -483,6 +534,11 @@ void ConnectionFromClient::start_request(i32, ByteString, URL::URL, HTTP::Header
{
VERIFY(0 && "RequestServer::ConnectionFromClient::start_request is not implemented");
}
void ConnectionFromClient::issue_network_request(i32, ByteString, URL::URL, HTTP::HeaderMap, ByteBuffer, Core::ProxyData, Optional<ResumeRequestForFailedCacheEntry>)
{
VERIFY(0 && "RequestServer::ConnectionFromClient::issue_network_request is not implemented");
}
#else
void ConnectionFromClient::start_request(i32 request_id, ByteString method, URL::URL url, HTTP::HeaderMap request_headers, ByteBuffer request_body, Core::ProxyData proxy_data)
{
@ -504,25 +560,37 @@ void ConnectionFromClient::start_request(i32 request_id, ByteString method, URL:
async_request_finished(request_id, bytes_sent, {}, {});
MUST(Core::System::close(writer_fd));
},
[this, request_id, writer_fd](auto bytes_sent) {
// FIXME: We should switch to a network request automatically if reading from cache has failed.
async_request_finished(request_id, bytes_sent, {}, Requests::NetworkError::CacheReadFailed);
(void)Core::System::close(writer_fd);
[this, request_id, writer_fd, method = move(method), url = move(url), request_headers = move(request_headers), request_body = move(request_body), proxy_data](auto bytes_sent) mutable {
// FIXME: We should really also have a way to validate the data once CacheEntry is storing its crc.
ResumeRequestForFailedCacheEntry resume_request {
.start_offset = bytes_sent,
.writer_fd = writer_fd,
};
issue_network_request(request_id, move(method), move(url), move(request_headers), move(request_body), proxy_data, resume_request);
});
return;
}
}
issue_network_request(request_id, move(method), move(url), move(request_headers), move(request_body), proxy_data);
}
void ConnectionFromClient::issue_network_request(i32 request_id, ByteString method, URL::URL url, HTTP::HeaderMap request_headers, ByteBuffer request_body, Core::ProxyData proxy_data, Optional<ResumeRequestForFailedCacheEntry> resume_request)
{
auto host = url.serialized_host().to_byte_string();
m_resolver->dns.lookup(host, DNS::Messages::Class::IN, { DNS::Messages::ResourceType::A, DNS::Messages::ResourceType::AAAA }, { .validate_dnssec_locally = g_dns_info.validate_dnssec_locally })
->when_rejected([this, request_id](auto const& error) {
->when_rejected([this, request_id, resume_request](auto const& error) {
dbgln("StartRequest: DNS lookup failed: {}", error);
// FIXME: Implement timing info for DNS lookup failure.
async_request_finished(request_id, 0, {}, Requests::NetworkError::UnableToResolveHost);
if (resume_request.has_value())
MUST(Core::System::close(resume_request->writer_fd));
})
.when_resolved([this, request_id, host = move(host), url = move(url), method = move(method), request_body = move(request_body), request_headers = move(request_headers), proxy_data](auto const& dns_result) mutable {
.when_resolved([this, request_id, host = move(host), url = move(url), method = move(method), request_body = move(request_body), request_headers = move(request_headers), proxy_data, resume_request](auto const& dns_result) mutable {
if (dns_result->is_empty() || !dns_result->has_cached_addresses()) {
dbgln("StartRequest: DNS lookup failed for '{}'", host);
// FIXME: Implement timing info for DNS lookup failure.
@ -538,16 +606,23 @@ void ConnectionFromClient::start_request(i32 request_id, ByteString method, URL:
return;
}
auto fds_or_error = Core::System::pipe2(O_NONBLOCK);
if (fds_or_error.is_error()) {
dbgln("StartRequest: Failed to create pipe: {}", fds_or_error.error());
return;
}
int writer_fd = 0;
auto fds = fds_or_error.release_value();
auto writer_fd = fds[1];
auto reader_fd = fds[0];
async_request_started(request_id, IPC::File::adopt_fd(reader_fd));
if (resume_request.has_value()) {
writer_fd = resume_request->writer_fd;
} else {
auto fds_or_error = Core::System::pipe2(O_NONBLOCK);
if (fds_or_error.is_error()) {
dbgln("StartRequest: Failed to create pipe: {}", fds_or_error.error());
return;
}
auto fds = fds_or_error.release_value();
auto reader_fd = fds[0];
writer_fd = fds[1];
async_request_started(request_id, IPC::File::adopt_fd(reader_fd));
}
auto request = make<ActiveRequest>(*this, m_curl_multi, easy, request_id, writer_fd);
request->url = url;
@ -614,6 +689,14 @@ void ConnectionFromClient::start_request(i32 request_id, ByteString method, URL:
request->curl_string_lists.append(curl_headers);
}
if (resume_request.has_value()) {
auto range = ByteString::formatted("{}-", resume_request->start_offset);
set_option(CURLOPT_RANGE, range.characters());
request->got_all_headers = true; // Don't re-send the headers for resumed requests.
request->start_offset_of_resumed_response = resume_request->start_offset;
}
// FIXME: Set up proxy if applicable
(void)proxy_data;

View file

@ -56,6 +56,12 @@ private:
virtual void websocket_close(i64 websocket_id, u16, ByteString) override;
virtual Messages::RequestServer::WebsocketSetCertificateResponse websocket_set_certificate(i64, ByteString, ByteString) override;
struct ResumeRequestForFailedCacheEntry {
size_t start_offset { 0 };
int writer_fd { 0 };
};
void issue_network_request(i32 request_id, ByteString, URL::URL, HTTP::HeaderMap, ByteBuffer, Core::ProxyData, Optional<ResumeRequestForFailedCacheEntry> = {});
HashMap<i32, RefPtr<WebSocket::WebSocket>> m_websockets;
struct ActiveRequest;

View file

@ -0,0 +1,6 @@
<script>
new DOMParser().parseFromString(
`<style>@font-face { font-family: a; src: url(); }</style>`,
"text/html"
);
</script>

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<body>
<style id="style">
/*
The below data url is equivalent to:
@font-face {
font-family: Icons;
src: url(does/not/matter.ttf);
font-weight: 400;
font-style: normal;
}
*/
@import url("data:text/css;charset=utf-8;base64,QGZvbnQtZmFjZSB7DQogICAgZm9udC1mYW1pbHk6IEljb25zOw0KICAgIHNyYzogdXJsKGRvZXMvbm90L21hdHRlci50dGYpOw0KICAgIGZvbnQtd2VpZ2h0OiA0MDA7DQogICAgZm9udC1zdHlsZTogbm9ybWFsOw0KfQ==");
</style>
<script>
style.remove();
</script>
</body>

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html class="reftest-wait">
<body>
<iframe
id="iframe"
width="500"
srcdoc="
<!DOCTYPE html>
<html>
<style>
@font-face {
font-family: test;
src: url(../data/pass.woff) format(woff);
}
#foo {
font-family: test;
}
</style>
<body>
<p>Test passes if the word PASS appears below.</p>
<p id='foo'>P</p>
</body>
</html>"
onload="waitForFonts()"
></iframe>
<script>
function waitForFonts() {
iframe.contentDocument.fonts.ready.then(() => {
document.documentElement.classList = "";
});
}
</script>
</body>
</html>

View file

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html class="reftest-wait">
<head>
<link
rel="match"
href="../../expected/font-face-descriptor-relative-length-iframe-ref.html"
/>
</head>
<body>
<iframe
id="iframe"
width="500"
srcdoc="
<!DOCTYPE html>
<html>
<style>
@font-face {
font-family: test;
src: url(../../data/fail.woff) format(woff);
font-weight: 400;
}
@font-face {
font-family: test;
src: url(../../data/pass.woff) format(woff);
/* Should compute to '100' because 100vw = 500px */
font-weight: calc(100 + (sign(100vw - 500px) * 1000));
}
#foo {
font-family: test;
font-weight: 100;
}
</style>
<body>
<p>Test passes if the word PASS appears below.</p>
<p id='foo'>P</p>
</body>
</html>"
onload="waitForFonts()"
></iframe>
<script>
function waitForFonts() {
iframe.contentDocument.fonts.ready.then(() => {
document.documentElement.classList = "";
});
}
</script>
</body>
</html>