mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-05-11 01:22:43 +02:00
When the regular HTML parser is blocked on an external script, the speculative parser scans ahead and pre-fetches discoverable sub-resources. Previously those fetches were tracked only in the parser's own URL list and never registered in the document's preload map, so when the regular parser later reached each element fetch()'s consume_a_preloaded_resource() lookup found nothing and issued a duplicate request — every parser-blocked sub-resource was fetched twice. issue_speculative_fetch now creates a PreloadEntry, registers it under create_a_preload_key(request) in the document's preload map, and supplies a processResponseConsumeBody callback that populates the entry. The map insertion happens after fetch() starts because fetch() runs consume_a_preloaded_resource() synchronously, so registering the entry beforehand would short-circuit the speculative fetch itself. The body-handling steps (1, 2, 5 of the preload algorithm's processResponseConsumeBody) are factored into a shared deliver_preload_response helper used by both the speculative parser and HTMLLinkElement::preload.
110 lines
3.8 KiB
C++
110 lines
3.8 KiB
C++
/*
|
|
* Copyright (c) 2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/HashMap.h>
|
|
#include <LibGC/Function.h>
|
|
#include <LibGC/Ptr.h>
|
|
#include <LibJS/Heap/Cell.h>
|
|
#include <LibURL/URL.h>
|
|
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
|
|
#include <LibWeb/Forward.h>
|
|
|
|
namespace Web::HTML {
|
|
|
|
// https://html.spec.whatwg.org/multipage/links.html#preload-key
|
|
struct PreloadKey {
|
|
// URL
|
|
// A URL
|
|
URL::URL url;
|
|
|
|
// destination
|
|
// A preload destination
|
|
Optional<Fetch::Infrastructure::Request::Destination> destination;
|
|
|
|
// mode
|
|
// A request mode, either "same-origin", "cors", or "no-cors"
|
|
Fetch::Infrastructure::Request::Mode mode;
|
|
|
|
// credentials mode
|
|
// A credentials mode
|
|
Fetch::Infrastructure::Request::CredentialsMode credentials_mode;
|
|
|
|
[[nodiscard]] bool operator==(PreloadKey const& other) const = default;
|
|
};
|
|
|
|
// https://html.spec.whatwg.org/multipage/links.html#create-a-preload-key
|
|
PreloadKey create_a_preload_key(Fetch::Infrastructure::Request const&);
|
|
|
|
// https://html.spec.whatwg.org/multipage/links.html#translate-a-preload-destination
|
|
Variant<Empty, Optional<Fetch::Infrastructure::Request::Destination>> translate_a_preload_destination(Optional<String> const& destination);
|
|
|
|
// https://html.spec.whatwg.org/multipage/links.html#preload-entry
|
|
class PreloadEntry final : public JS::Cell {
|
|
GC_CELL(PreloadEntry, JS::Cell);
|
|
GC_DECLARE_ALLOCATOR(PreloadEntry);
|
|
|
|
public:
|
|
virtual void visit_edges(Cell::Visitor&) override;
|
|
|
|
// integrity metadata
|
|
// A string
|
|
String integrity_metadata;
|
|
|
|
// response
|
|
// Null or a response
|
|
GC::Ptr<Fetch::Infrastructure::Response> response;
|
|
|
|
// on response available
|
|
// Null, or an algorithm accepting a response or null
|
|
// The callback is always invoked with a non-null response — either entry's resolved response,
|
|
// or the response delivered by the preload's fetch.
|
|
GC::Ptr<GC::Function<void(GC::Ref<Fetch::Infrastructure::Response>)>> on_response_available;
|
|
};
|
|
|
|
// https://html.spec.whatwg.org/multipage/links.html#consume-a-preloaded-resource
|
|
bool consume_a_preloaded_resource(
|
|
Window&,
|
|
URL::URL const& url,
|
|
Optional<Fetch::Infrastructure::Request::Destination> destination,
|
|
Fetch::Infrastructure::Request::Mode mode,
|
|
Fetch::Infrastructure::Request::CredentialsMode credentials_mode,
|
|
String const& integrity_metadata,
|
|
GC::Ref<GC::Function<void(GC::Ref<Fetch::Infrastructure::Response>)>> on_response_available);
|
|
|
|
// Implements steps 1, 2, and 5 of the processResponseConsumeBody callback in the preload algorithm:
|
|
// https://html.spec.whatwg.org/multipage/links.html#preload (step 11)
|
|
// Returns the (possibly replaced) response so callers can pass it to a processResponse callback (step 6).
|
|
GC::Ref<Fetch::Infrastructure::Response> deliver_preload_response(
|
|
JS::Realm&,
|
|
PreloadEntry&,
|
|
GC::Ref<Fetch::Infrastructure::Response> response,
|
|
ByteBuffer const* body_bytes);
|
|
|
|
}
|
|
|
|
namespace AK {
|
|
|
|
template<>
|
|
struct Traits<Web::HTML::PreloadKey> : public DefaultTraits<Web::HTML::PreloadKey> {
|
|
static unsigned hash(Web::HTML::PreloadKey const& key)
|
|
{
|
|
u32 url_hash = Traits<URL::URL>::hash(key.url);
|
|
// Offset the destination by 1 so that "absent" hashes differently from Destination::Audio (value 0).
|
|
u32 destination_hash = key.destination.has_value() ? static_cast<u32>(*key.destination) + 1 : 0;
|
|
u32 mode_hash = static_cast<u32>(key.mode);
|
|
u32 credentials_mode_hash = static_cast<u32>(key.credentials_mode);
|
|
return pair_int_hash(pair_int_hash(url_hash, destination_hash), pair_int_hash(mode_hash, credentials_mode_hash));
|
|
}
|
|
static bool equals(Web::HTML::PreloadKey const& a, Web::HTML::PreloadKey const& b)
|
|
{
|
|
return a == b;
|
|
}
|
|
};
|
|
|
|
}
|