diff --git a/.github/workflows/ci-flatpak.yml b/.github/workflows/ci-flatpak.yml index 1c6b93e3bf6..305e6c46d23 100644 --- a/.github/workflows/ci-flatpak.yml +++ b/.github/workflows/ci-flatpak.yml @@ -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 diff --git a/.github/workflows/flatpak-template.yml b/.github/workflows/flatpak-template.yml index 494460a9bea..5453f81f8f6 100644 --- a/.github/workflows/flatpak-template.yml +++ b/.github/workflows/flatpak-template.yml @@ -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' diff --git a/.github/workflows/nightly-lagom.yml b/.github/workflows/nightly-lagom.yml index 201c5d3731e..09c0bd9aa2b 100644 --- a/.github/workflows/nightly-lagom.yml +++ b/.github/workflows/nightly-lagom.yml @@ -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 diff --git a/Libraries/LibJS/Runtime/AbstractOperations.h b/Libraries/LibJS/Runtime/AbstractOperations.h index 1b3e0daeac2..889834d4e5e 100644 --- a/Libraries/LibJS/Runtime/AbstractOperations.h +++ b/Libraries/LibJS/Runtime/AbstractOperations.h @@ -157,13 +157,19 @@ ALWAYS_INLINE ThrowCompletionOr> construct(VM& vm, FunctionObjec // 10.1.13 OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] ), https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor template -ThrowCompletionOr> ordinary_create_from_constructor(VM& vm, FunctionObject const& constructor, GC::Ref (Intrinsics::*intrinsic_default_prototype)(), Args&&... args) +ALWAYS_INLINE ThrowCompletionOr> ordinary_create_from_constructor(VM& vm, Realm& realm, FunctionObject const& constructor, GC::Ref (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(forward(args)..., *prototype); } +// 10.1.13 OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] ), https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor +template +ALWAYS_INLINE ThrowCompletionOr> ordinary_create_from_constructor(VM& vm, FunctionObject const& constructor, GC::Ref (Intrinsics::*intrinsic_default_prototype)(), Args&&... args) +{ + return ordinary_create_from_constructor(vm, *vm.current_realm(), constructor, intrinsic_default_prototype, forward(args)...); +} + // 7.3.35 AddValueToKeyedGroup ( groups, key, value ), https://tc39.es/ecma262/#sec-add-value-to-keyed-group template void add_value_to_keyed_group(VM& vm, GroupsType& groups, KeyType key, Value value) diff --git a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index 52cb283e6c7..6789ae3c9df 100644 --- a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -549,7 +549,7 @@ FLATTEN ThrowCompletionOr ECMAScriptFunctionObject::internal_call(Executi } // 10.2.2 [[Construct]] ( argumentsList, newTarget ), https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget -ThrowCompletionOr> ECMAScriptFunctionObject::internal_construct(ExecutionContext& callee_context, FunctionObject& new_target) +FLATTEN ThrowCompletionOr> ECMAScriptFunctionObject::internal_construct(ExecutionContext& callee_context, FunctionObject& new_target) { auto& vm = this->vm(); @@ -566,14 +566,14 @@ ThrowCompletionOr> 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(vm, new_target, &Intrinsics::object_prototype, ConstructWithPrototypeTag::Tag)); + this_argument = TRY(ordinary_create_from_constructor(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> 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 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; } diff --git a/Libraries/LibMedia/Color/ColorPrimaries.cpp b/Libraries/LibMedia/Color/ColorPrimaries.cpp index 3b777a5031b..c866501a715 100644 --- a/Libraries/LibMedia/Color/ColorPrimaries.cpp +++ b/Libraries/LibMedia/Color/ColorPrimaries.cpp @@ -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 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 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; diff --git a/Libraries/LibRequests/NetworkError.h b/Libraries/LibRequests/NetworkError.h index d2ca83bbe5c..5250d99e318 100644 --- a/Libraries/LibRequests/NetworkError.h +++ b/Libraries/LibRequests/NetworkError.h @@ -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; } diff --git a/Libraries/LibWeb/CSS/CSSImportRule.cpp b/Libraries/LibWeb/CSS/CSSImportRule.cpp index 61b2f541a1a..975c9215f56 100644 --- a/Libraries/LibWeb/CSS/CSSImportRule.cpp +++ b/Libraries/LibWeb/CSS/CSSImportRule.cpp @@ -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 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 diff --git a/Libraries/LibWeb/CSS/CSSStyleSheet.cpp b/Libraries/LibWeb/CSS/CSSStyleSheet.cpp index 577565ced71..34fe2e39e33 100644 --- a/Libraries/LibWeb/CSS/CSSStyleSheet.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleSheet.cpp @@ -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 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(this)->owner_node()) return element->document(); diff --git a/Libraries/LibWeb/CSS/CSSStyleSheet.h b/Libraries/LibWeb/CSS/CSSStyleSheet.h index da5e7e55ed2..638b3896984 100644 --- a/Libraries/LibWeb/CSS/CSSStyleSheet.h +++ b/Libraries/LibWeb/CSS/CSSStyleSheet.h @@ -66,6 +66,7 @@ public: bool evaluate_media_queries(DOM::Document const&); void for_each_effective_keyframes_at_rule(Function const& callback) const; + HashTable> 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); diff --git a/Libraries/LibWeb/CSS/Length.cpp b/Libraries/LibWeb/CSS/Length.cpp index 8c341a1e37c..acddf608b56 100644 --- a/Libraries/LibWeb/CSS/Length.cpp +++ b/Libraries/LibWeb/CSS/Length.cpp @@ -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, }; diff --git a/Libraries/LibWeb/CSS/ParsedFontFace.cpp b/Libraries/LibWeb/CSS/ParsedFontFace.cpp index 55950f3b5bb..c87511feffd 100644 --- a/Libraries/LibWeb/CSS/ParsedFontFace.cpp +++ b/Libraries/LibWeb/CSS/ParsedFontFace.cpp @@ -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 weight; diff --git a/Libraries/LibWeb/CSS/StyleSheetList.cpp b/Libraries/LibWeb/CSS/StyleSheetList.cpp index 02a6afee778..4f8a945b060 100644 --- a/Libraries/LibWeb/CSS/StyleSheetList.cpp +++ b/Libraries/LibWeb/CSS/StyleSheetList.cpp @@ -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); } diff --git a/Libraries/LibWeb/DOM/AdoptedStyleSheets.cpp b/Libraries/LibWeb/DOM/AdoptedStyleSheets.cpp index 5e221d71393..20f657706f5 100644 --- a/Libraries/LibWeb/DOM/AdoptedStyleSheets.cpp +++ b/Libraries/LibWeb/DOM/AdoptedStyleSheets.cpp @@ -31,7 +31,6 @@ GC::Ref 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 create_adopted_style_sheets_list(Node& document auto& style_sheet = static_cast(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 {}; diff --git a/Services/RequestServer/Cache/CacheEntry.cpp b/Services/RequestServer/Cache/CacheEntry.cpp index b975af67e94..4dc9dcceda1 100644 --- a/Services/RequestServer/Cache/CacheEntry.cpp +++ b/Services/RequestServer/Cache/CacheEntry.cpp @@ -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); diff --git a/Services/RequestServer/ConnectionFromClient.cpp b/Services/RequestServer/ConnectionFromClient.cpp index 95583e84509..02b62194fa0 100644 --- a/Services/RequestServer/ConnectionFromClient.cpp +++ b/Services/RequestServer/ConnectionFromClient.cpp @@ -117,18 +117,24 @@ struct ConnectionFromClient::ActiveRequest : public Weakable { i32 request_id { 0 }; WeakPtr 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 reason_phrase; ByteBuffer body; + AllocatingMemoryStream send_buffer; NonnullRefPtr write_notifier; bool done_fetching { false }; + Optional http_status_code; + HTTP::HeaderMap headers; + bool got_all_headers { false }; + + Optional start_offset_of_resumed_response; + size_t bytes_transferred_to_client { 0 }; + Optional cache_entry; UnixDateTime request_start_time; @@ -161,9 +167,42 @@ struct ConnectionFromClient::ActiveRequest : public Weakable { ErrorOr 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 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 { 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 { 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) +{ + 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 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(*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; diff --git a/Services/RequestServer/ConnectionFromClient.h b/Services/RequestServer/ConnectionFromClient.h index 7eafa4f1b0f..1fdabb46c5d 100644 --- a/Services/RequestServer/ConnectionFromClient.h +++ b/Services/RequestServer/ConnectionFromClient.h @@ -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 = {}); + HashMap> m_websockets; struct ActiveRequest; diff --git a/Tests/LibWeb/Crash/CSS/font-face-dom-parser.html b/Tests/LibWeb/Crash/CSS/font-face-dom-parser.html new file mode 100644 index 00000000000..7b83e3dc509 --- /dev/null +++ b/Tests/LibWeb/Crash/CSS/font-face-dom-parser.html @@ -0,0 +1,6 @@ + diff --git a/Tests/LibWeb/Crash/CSS/imported-style-sheet-loading-font-without-document.html b/Tests/LibWeb/Crash/CSS/imported-style-sheet-loading-font-without-document.html new file mode 100644 index 00000000000..ff13af77777 --- /dev/null +++ b/Tests/LibWeb/Crash/CSS/imported-style-sheet-loading-font-without-document.html @@ -0,0 +1,18 @@ + + + + + diff --git a/Tests/LibWeb/Ref/data/fail.woff b/Tests/LibWeb/Ref/data/fail.woff new file mode 100644 index 00000000000..33487cdbb4f Binary files /dev/null and b/Tests/LibWeb/Ref/data/fail.woff differ diff --git a/Tests/LibWeb/Ref/data/pass.woff b/Tests/LibWeb/Ref/data/pass.woff new file mode 100644 index 00000000000..839bb20b12f Binary files /dev/null and b/Tests/LibWeb/Ref/data/pass.woff differ diff --git a/Tests/LibWeb/Ref/expected/font-face-descriptor-relative-length-iframe-ref.html b/Tests/LibWeb/Ref/expected/font-face-descriptor-relative-length-iframe-ref.html new file mode 100644 index 00000000000..4a6155dd783 --- /dev/null +++ b/Tests/LibWeb/Ref/expected/font-face-descriptor-relative-length-iframe-ref.html @@ -0,0 +1,35 @@ + + + + + + + diff --git a/Tests/LibWeb/Ref/input/css/font-face-descriptor-relative-length-iframe.html b/Tests/LibWeb/Ref/input/css/font-face-descriptor-relative-length-iframe.html new file mode 100644 index 00000000000..44fc41d11c2 --- /dev/null +++ b/Tests/LibWeb/Ref/input/css/font-face-descriptor-relative-length-iframe.html @@ -0,0 +1,50 @@ + + + + + + + + + +