mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-07 21:59:54 +00:00
LibWeb: Change SessionHistoryTraversalQueue to use Promises
If multiple cross-document navigations are queued on SessionHistoryTraversalQueue, running the next entry before the current document load is finished may result in a deadlock. If the new document has a navigable element of its own, it will append steps to SHTQ and hang in nested spin_until. This change uses promises to ensure that the current document loads before the next entry is executed. Fixes timeouts in the imported tests. Co-authored-by: Sam Atkins <sam@ladybird.org>
This commit is contained in:
parent
eed4dd3745
commit
50a79c6af8
Notes:
github-actions[bot]
2025-11-26 11:28:29 +00:00
Author: https://github.com/AtkinsSJ
Commit: 50a79c6af8
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6929
20 changed files with 781 additions and 82 deletions
|
|
@ -61,7 +61,7 @@ bool build_xml_document(DOM::Document& document, ByteBuffer const& data, Optiona
|
|||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-lifecycle.html#navigate-html
|
||||
static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_html_document(HTML::NavigationParams const& navigation_params)
|
||||
static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_html_document(HTML::NavigationParams const& navigation_params, NonnullRefPtr<Core::Promise<Empty>> signal_to_continue_session_history_processing)
|
||||
{
|
||||
// To load an HTML document, given navigation params navigationParams:
|
||||
|
||||
|
|
@ -74,7 +74,8 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_html_document(HTML::Navi
|
|||
if (document->url_string() == "about:blank"_string
|
||||
&& navigation_params.response->body()->length().value_or(0) == 0) {
|
||||
TRY(document->populate_with_html_head_and_body());
|
||||
// Nothing else is added to the document, so mark it as loaded.
|
||||
// NB: Nothing else is added to the document, so mark it as loaded and resolve the signal_to_continue_session_history_processing.
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
HTML::HTMLParser::the_end(document);
|
||||
}
|
||||
|
||||
|
|
@ -92,8 +93,10 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_html_document(HTML::Navi
|
|||
// causes a load event to be fired.
|
||||
else {
|
||||
// FIXME: Parse as we receive the document data, instead of waiting for the whole document to be fetched first.
|
||||
auto process_body = GC::create_function(document->heap(), [document, url = navigation_params.response->url().value(), mime_type = navigation_params.response->header_list()->extract_mime_type()](ByteBuffer data) {
|
||||
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(document->heap(), [document = document, data = move(data), url = url, mime_type] {
|
||||
auto process_body = GC::create_function(document->heap(), [document, signal_to_continue_session_history_processing, url = navigation_params.response->url().value(), mime_type = navigation_params.response->header_list()->extract_mime_type()](ByteBuffer data) {
|
||||
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(document->heap(), [signal_to_continue_session_history_processing, document = document, data = move(data), url = url, mime_type] {
|
||||
// NB: If document is part of a session history entry's traversal, resolve the signal_to_continue_session_history_processing.
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
auto parser = HTML::HTMLParser::create_with_uncertain_encoding(document, data, mime_type);
|
||||
parser->run(url);
|
||||
}));
|
||||
|
|
@ -112,7 +115,7 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_html_document(HTML::Navi
|
|||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-lifecycle.html#read-xml
|
||||
static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_xml_document(HTML::NavigationParams const& navigation_params, MimeSniff::MimeType type)
|
||||
static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_xml_document(HTML::NavigationParams const& navigation_params, MimeSniff::MimeType type, NonnullRefPtr<Core::Promise<Empty>> signal_to_continue_session_history_processing)
|
||||
{
|
||||
// When faced with displaying an XML file inline, provided navigation params navigationParams and a string type, user agents
|
||||
// must follow the requirements defined in XML and Namespaces in XML, XML Media Types, DOM, and other relevant specifications
|
||||
|
|
@ -147,7 +150,7 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_xml_document(HTML::Navig
|
|||
if (auto maybe_encoding = type.parameters().get("charset"sv); maybe_encoding.has_value())
|
||||
content_encoding = maybe_encoding.value();
|
||||
|
||||
auto process_body = GC::create_function(document->heap(), [document, url = navigation_params.response->url().value(), content_encoding = move(content_encoding), mime = type](ByteBuffer data) {
|
||||
auto process_body = GC::create_function(document->heap(), [document, signal_to_continue_session_history_processing, url = navigation_params.response->url().value(), content_encoding = move(content_encoding), mime = type](ByteBuffer data) {
|
||||
Optional<TextCodec::Decoder&> decoder;
|
||||
// The actual HTTP headers and other metadata, not the headers as mutated or implied by the algorithms given in this specification,
|
||||
// are the ones that must be used when determining the character encoding according to the rules given in the above specifications.
|
||||
|
|
@ -164,8 +167,10 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_xml_document(HTML::Navig
|
|||
dbgln("XML Document contains improperly-encoded characters");
|
||||
convert_to_xml_error_document(document, "XML Document contains improperly-encoded characters"_utf16);
|
||||
|
||||
// NOTE: This ensures that the `load` event gets fired for the frame loading this document.
|
||||
// NB: This ensures that the `load` event gets fired for the frame loading this document.
|
||||
document->completely_finish_loading();
|
||||
// NB: If document is part of a session history entry's traversal, resolve the signal_to_continue_session_history_processing.
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return;
|
||||
}
|
||||
auto source = decoder->to_utf8(data);
|
||||
|
|
@ -174,10 +179,14 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_xml_document(HTML::Navig
|
|||
dbgln("Failed to decode XML document: {}", source.error());
|
||||
convert_to_xml_error_document(document, Utf16String::formatted("Failed to decode XML document: {}", source.error()));
|
||||
|
||||
// NOTE: This ensures that the `load` event gets fired for the frame loading this document.
|
||||
// NB: This ensures that the `load` event gets fired for the frame loading this document.
|
||||
document->completely_finish_loading();
|
||||
// NB: If document is part of session history traversal, resolve the signal_to_continue_session_history_processing.
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return;
|
||||
}
|
||||
// NB: If document is part of session history traversal, resolve the signal_to_continue_session_history_processing.
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
XML::Parser parser(source.value(), { .preserve_cdata = true, .preserve_comments = true, .resolve_external_resource = resolve_xml_resource });
|
||||
XMLDocumentBuilder builder { document };
|
||||
auto result = parser.parse_with_listener(builder);
|
||||
|
|
@ -186,7 +195,7 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_xml_document(HTML::Navig
|
|||
dbgln("Failed to parse XML document: {}", result.error());
|
||||
convert_to_xml_error_document(document, Utf16String::formatted("Failed to parse XML document: {}", result.error()));
|
||||
|
||||
// NOTE: XMLDocumentBuilder ensures that the `load` event gets fired. We don't need to do anything else here.
|
||||
// NB: XMLDocumentBuilder ensures that the `load` event gets fired. We don't need to do anything else here.
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -201,7 +210,7 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_xml_document(HTML::Navig
|
|||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-lifecycle.html#navigate-text
|
||||
static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_text_document(HTML::NavigationParams const& navigation_params, MimeSniff::MimeType type)
|
||||
static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_text_document(HTML::NavigationParams const& navigation_params, MimeSniff::MimeType type, NonnullRefPtr<Core::Promise<Empty>> signal_to_continue_session_history_processing)
|
||||
{
|
||||
// To load a text document, given a navigation params navigationParams and a string type:
|
||||
|
||||
|
|
@ -228,10 +237,12 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_text_document(HTML::Navi
|
|||
// document's relevant global object to have the parser to process the implied EOF character, which eventually causes a
|
||||
// load event to be fired.
|
||||
// FIXME: Parse as we receive the document data, instead of waiting for the whole document to be fetched first.
|
||||
auto process_body = GC::create_function(document->heap(), [document, url = navigation_params.response->url().value(), mime = type](ByteBuffer data) {
|
||||
auto process_body = GC::create_function(document->heap(), [document, signal_to_continue_session_history_processing, url = navigation_params.response->url().value(), mime = type](ByteBuffer data) {
|
||||
auto encoding = run_encoding_sniffing_algorithm(document, data, mime);
|
||||
dbgln_if(HTML_PARSER_DEBUG, "The encoding sniffing algorithm returned encoding '{}'", encoding);
|
||||
|
||||
// NB: If document is part of session history traversal, resolve the signal_to_continue_session_history_processing.
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
auto parser = HTML::HTMLParser::create_for_scripting(document);
|
||||
parser->tokenizer().update_insertion_point();
|
||||
|
||||
|
|
@ -266,7 +277,7 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_text_document(HTML::Navi
|
|||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-lifecycle.html#navigate-media
|
||||
static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_media_document(HTML::NavigationParams const& navigation_params, MimeSniff::MimeType type)
|
||||
static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_media_document(HTML::NavigationParams const& navigation_params, MimeSniff::MimeType type, NonnullRefPtr<Core::Promise<Empty>> signal_to_continue_session_history_processing)
|
||||
{
|
||||
// To load a media document, given navigationParams and a string type:
|
||||
|
||||
|
|
@ -351,7 +362,10 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_media_document(HTML::Nav
|
|||
auto& realm = document->realm();
|
||||
navigation_params.response->body()->fully_read(
|
||||
realm,
|
||||
GC::create_function(document->heap(), [document](ByteBuffer) { HTML::HTMLParser::the_end(document); }),
|
||||
GC::create_function(document->heap(), [document, signal_to_continue_session_history_processing](ByteBuffer) {
|
||||
// NB: If document is part of session history traversal, resolve the signal_to_continue_session_history_processing.
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
HTML::HTMLParser::the_end(document); }),
|
||||
GC::create_function(document->heap(), [](JS::Value) {}),
|
||||
GC::Ref { realm.global_object() });
|
||||
|
||||
|
|
@ -396,11 +410,13 @@ bool can_load_document_with_type(MimeSniff::MimeType const& type)
|
|||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#loading-a-document
|
||||
GC::Ptr<DOM::Document> load_document(HTML::NavigationParams const& navigation_params)
|
||||
GC::Ptr<DOM::Document> load_document(HTML::NavigationParams const& navigation_params, NonnullRefPtr<Core::Promise<Empty>> signal_to_continue_session_history_processing)
|
||||
{
|
||||
// To load a document given navigation params navigationParams, source snapshot params sourceSnapshotParams,
|
||||
// and origin initiatorOrigin, perform the following steps. They return a Document or null.
|
||||
|
||||
// NB: Use Core::Promise to signal SessionHistoryTraversalQueue that it can continue to execute next entry.
|
||||
|
||||
// 1. Let type be the computed type of navigationParams's response.
|
||||
auto supplied_type = navigation_params.response->header_list()->extract_mime_type();
|
||||
auto type = MimeSniff::Resource::sniff(
|
||||
|
|
@ -421,14 +437,14 @@ GC::Ptr<DOM::Document> load_document(HTML::NavigationParams const& navigation_pa
|
|||
// -> an HTML MIME type
|
||||
if (type.is_html()) {
|
||||
// Return the result of loading an HTML document, given navigationParams.
|
||||
return load_html_document(navigation_params).release_value_but_fixme_should_propagate_errors();
|
||||
return load_html_document(navigation_params, signal_to_continue_session_history_processing).release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
|
||||
// -> an XML MIME type that is not an explicitly supported XML MIME type
|
||||
// FIXME: that is not an explicitly supported XML MIME type
|
||||
if (type.is_xml()) {
|
||||
// Return the result of loading an XML document given navigationParams and type.
|
||||
return load_xml_document(navigation_params, type).release_value_but_fixme_should_propagate_errors();
|
||||
return load_xml_document(navigation_params, type, signal_to_continue_session_history_processing).release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
|
||||
// -> a JavaScript MIME type
|
||||
|
|
@ -442,7 +458,7 @@ GC::Ptr<DOM::Document> load_document(HTML::NavigationParams const& navigation_pa
|
|||
|| type.essence() == "text/plain"_string
|
||||
|| type.essence() == "text/vtt"_string) {
|
||||
// Return the result of loading a text document given navigationParams and type.
|
||||
return load_text_document(navigation_params, type).release_value_but_fixme_should_propagate_errors();
|
||||
return load_text_document(navigation_params, type, signal_to_continue_session_history_processing).release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
|
||||
// -> "multipart/x-mixed-replace"
|
||||
|
|
@ -455,7 +471,7 @@ GC::Ptr<DOM::Document> load_document(HTML::NavigationParams const& navigation_pa
|
|||
if (type.is_image()
|
||||
|| type.is_audio_or_video()) {
|
||||
// Return the result of loading a media document given navigationParams and type.
|
||||
return load_media_document(navigation_params, type).release_value_but_fixme_should_propagate_errors();
|
||||
return load_media_document(navigation_params, type, signal_to_continue_session_history_processing).release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
|
||||
// -> "application/pdf"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
namespace Web {
|
||||
|
||||
bool build_xml_document(DOM::Document& document, ByteBuffer const& data, Optional<String> content_encoding);
|
||||
GC::Ptr<DOM::Document> load_document(HTML::NavigationParams const& navigation_params);
|
||||
GC::Ptr<DOM::Document> load_document(HTML::NavigationParams const& navigation_params, NonnullRefPtr<Core::Promise<Empty>> signal_to_continue_session_history_processing);
|
||||
bool can_load_document_with_type(MimeSniff::MimeType const&);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-lifecycle.html#read-ua-inline
|
||||
|
|
|
|||
|
|
@ -125,7 +125,11 @@ void HTMLIFrameElement::post_connection()
|
|||
if (auto navigable = content_navigable()) {
|
||||
auto traversable = navigable->traversable_navigable();
|
||||
traversable->append_session_history_traversal_steps(GC::create_function(heap(), [this] {
|
||||
// NB: Use Core::Promise to signal SessionHistoryTraversalQueue that it can continue to execute next entry.
|
||||
auto signal_to_continue_session_history_processing = Core::Promise<Empty>::construct();
|
||||
set_content_navigable_has_session_history_entry_and_ready_for_navigation();
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return signal_to_continue_session_history_processing;
|
||||
}));
|
||||
}
|
||||
})));
|
||||
|
|
|
|||
|
|
@ -1325,8 +1325,9 @@ void Navigable::populate_session_history_entry_document(
|
|||
SourceSnapshotParams const& source_snapshot_params,
|
||||
TargetSnapshotParams const& target_snapshot_params,
|
||||
UserNavigationInvolvement user_involvement,
|
||||
NonnullRefPtr<Core::Promise<Empty>> signal_to_continue_session_history_processing,
|
||||
Optional<String> navigation_id,
|
||||
Navigable::NavigationParamsVariant navigation_params,
|
||||
NavigationParamsVariant navigation_params,
|
||||
ContentSecurityPolicy::Directives::Directive::NavigationType csp_navigation_type,
|
||||
bool allow_POST,
|
||||
GC::Ptr<GC::Function<void()>> completion_steps)
|
||||
|
|
@ -1344,21 +1345,24 @@ void Navigable::populate_session_history_entry_document(
|
|||
// 3. Let documentResource be entry's document state's resource.
|
||||
auto document_resource = entry->document_state()->resource();
|
||||
|
||||
auto received_navigation_params = GC::create_function(heap(), [this, entry, navigation_id, user_involvement, completion_steps, csp_navigation_type](NavigationParamsVariant received_navigation_params) {
|
||||
auto received_navigation_params = GC::create_function(heap(), [this, entry, navigation_id, user_involvement, completion_steps, csp_navigation_type, signal_to_continue_session_history_processing](NavigationParamsVariant received_navigation_params) {
|
||||
// AD-HOC: Not in the spec but subsequent steps will fail if the navigable doesn't have an active window.
|
||||
if (!active_window())
|
||||
return;
|
||||
|
||||
// 5. Queue a global task on the navigation and traversal task source, given navigable's active window, to run these steps:
|
||||
queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), GC::create_function(heap(), [this, entry, received_navigation_params = move(received_navigation_params), navigation_id, user_involvement, completion_steps, csp_navigation_type]() mutable {
|
||||
queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), GC::create_function(heap(), [this, entry, received_navigation_params = move(received_navigation_params), navigation_id, user_involvement, completion_steps, csp_navigation_type, signal_to_continue_session_history_processing]() mutable {
|
||||
// NOTE: This check is not in the spec but we should not continue navigation if navigable has been destroyed.
|
||||
if (has_been_destroyed())
|
||||
return;
|
||||
|
||||
// 1. If navigable's ongoing navigation no longer equals navigationId, then run completionSteps and abort these steps.
|
||||
if (navigation_id.has_value() && (!ongoing_navigation().has<String>() || ongoing_navigation().get<String>() != *navigation_id)) {
|
||||
if (completion_steps)
|
||||
if (completion_steps) {
|
||||
// NB: Use Core::Promise to signal SessionHistoryTraversalQueue that it can continue to execute next entry.
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
completion_steps->function()();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1442,7 +1446,7 @@ void Navigable::populate_session_history_entry_document(
|
|||
// 6. Otherwise, if navigationParams's response's status is not 204 and is not 205, then set entry's document state's document to the result of
|
||||
// loading a document given navigationParams, sourceSnapshotParams, and entry's document state's initiator origin.
|
||||
else if (auto const& response = received_navigation_params.get<GC::Ref<NavigationParams>>()->response; response->status() != 204 && response->status() != 205) {
|
||||
auto document = load_document(received_navigation_params.get<GC::Ref<NavigationParams>>());
|
||||
auto document = load_document(received_navigation_params.get<GC::Ref<NavigationParams>>(), signal_to_continue_session_history_processing);
|
||||
entry->document_state()->set_document(document);
|
||||
}
|
||||
|
||||
|
|
@ -1873,20 +1877,32 @@ void Navigable::begin_navigation(NavigateParams params)
|
|||
// 9. Attempt to populate the history entry's document for historyEntry, given navigable, "navigate",
|
||||
// sourceSnapshotParams, targetSnapshotParams, userInvolvement, navigationId, navigationParams,
|
||||
// cspNavigationType, with allowPOST set to true and completionSteps set to the following step:
|
||||
populate_session_history_entry_document(history_entry, source_snapshot_params, target_snapshot_params, user_involvement, navigation_id, navigation_params, csp_navigation_type, true, GC::create_function(heap(), [this, history_entry, history_handling, navigation_id, user_involvement] {
|
||||
|
||||
// NB: Use Core::Promise to signal SessionHistoryTraversalQueue that it can continue to execute next entry.
|
||||
auto signal_to_continue_session_history_processing = Core::Promise<Empty>::construct();
|
||||
populate_session_history_entry_document(history_entry, source_snapshot_params, target_snapshot_params, user_involvement, signal_to_continue_session_history_processing, navigation_id, navigation_params, csp_navigation_type, true, GC::create_function(heap(), [this, signal_to_continue_session_history_processing, history_entry, history_handling, navigation_id, user_involvement] {
|
||||
// 1. Append session history traversal steps to navigable's traversable to finalize a cross-document navigation given navigable, historyHandling, userInvolvement, and historyEntry.
|
||||
traversable_navigable()->append_session_history_traversal_steps(GC::create_function(heap(), [this, history_entry, history_handling, navigation_id, user_involvement] {
|
||||
traversable_navigable()->append_session_history_traversal_steps(GC::create_function(heap(), [this, history_entry, history_handling, navigation_id, user_involvement, signal_to_continue_session_history_processing] {
|
||||
if (this->has_been_destroyed()) {
|
||||
// NOTE: This check is not in the spec but we should not continue navigation if navigable has been destroyed.
|
||||
// AD-HOC: This check is not in the spec but we should not continue navigation if navigable has been destroyed.
|
||||
set_delaying_load_events(false);
|
||||
return;
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return signal_to_continue_session_history_processing;
|
||||
}
|
||||
if (this->ongoing_navigation() != navigation_id) {
|
||||
// NOTE: This check is not in the spec but we should not continue navigation if ongoing navigation id has changed.
|
||||
// AD-HOC: This check is not in the spec but we should not continue navigation if ongoing navigation id has changed.
|
||||
set_delaying_load_events(false);
|
||||
return;
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return signal_to_continue_session_history_processing;
|
||||
}
|
||||
finalize_a_cross_document_navigation(*this, to_history_handling_behavior(history_handling), user_involvement, history_entry);
|
||||
|
||||
// AD-HOC: If the document isn't active or is still loading session history traversal queue will wait
|
||||
// for it to load else resolve the signal_to_continue_session_history_processing.
|
||||
if (history_entry->document() && (!history_entry->document()->is_active() || history_entry->document()->ready_state() != "loading")) {
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
}
|
||||
return signal_to_continue_session_history_processing;
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
|
|
@ -1968,12 +1984,16 @@ void Navigable::navigate_to_a_fragment(URL::URL const& url, HistoryHandlingBehav
|
|||
|
||||
// 17. Append the following session history synchronous navigation steps involving navigable to traversable:
|
||||
traversable->append_session_history_synchronous_navigation_steps(*this, GC::create_function(heap(), [this, traversable, history_entry, entry_to_replace, navigation_id, history_handling, user_involvement] {
|
||||
// NB: Use Core::Promise to signal SessionHistoryTraversalQueue that it can continue to execute next entry.
|
||||
auto signal_to_continue_session_history_processing = Core::Promise<Empty>::construct();
|
||||
// 1. Finalize a same-document navigation given traversable, navigable, historyEntry, entryToReplace, historyHandling, and userInvolvement.
|
||||
finalize_a_same_document_navigation(*traversable, *this, history_entry, entry_to_replace, history_handling, user_involvement);
|
||||
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
// FIXME: 2. Invoke WebDriver BiDi fragment navigated with navigable and a new WebDriver BiDi
|
||||
// navigation status whose id is navigationId, url is url, and status is "complete".
|
||||
(void)navigation_id;
|
||||
return signal_to_continue_session_history_processing;
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -2081,7 +2101,7 @@ GC::Ptr<DOM::Document> Navigable::evaluate_javascript_url(URL::URL const& url, U
|
|||
user_involvement);
|
||||
|
||||
// 17. Return the result of loading an HTML document given navigationParams.
|
||||
return load_document(navigation_params);
|
||||
return load_document(navigation_params, Core::Promise<Empty>::construct());
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate-to-a-javascript:-url
|
||||
|
|
@ -2167,7 +2187,11 @@ void Navigable::navigate_to_a_javascript_url(URL::URL const& url, HistoryHandlin
|
|||
|
||||
// 13. Append session history traversal steps to targetNavigable's traversable to finalize a cross-document navigation with targetNavigable, historyHandling, userInvolvement, and historyEntry.
|
||||
traversable_navigable()->append_session_history_traversal_steps(GC::create_function(heap(), [this, history_entry, history_handling, user_involvement] {
|
||||
// NB: Use Core::Promise to signal SessionHistoryTraversalQueue that it can continue to execute next entry.
|
||||
auto signal_to_continue_session_history_processing = Core::Promise<Empty>::construct();
|
||||
finalize_a_cross_document_navigation(*this, history_handling, user_involvement, history_entry);
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return signal_to_continue_session_history_processing;
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -2182,8 +2206,12 @@ void Navigable::reload(UserNavigationInvolvement user_involvement)
|
|||
|
||||
// 3. Append the following session history traversal steps to traversable:
|
||||
traversable->append_session_history_traversal_steps(GC::create_function(heap(), [traversable, user_involvement] {
|
||||
// NB: Use Core::Promise to signal SessionHistoryTraversalQueue that it can continue to execute next entry.
|
||||
auto signal_to_continue_session_history_processing = Core::Promise<Empty>::construct();
|
||||
// 1. Apply the reload history step to traversable given userInvolvement.
|
||||
traversable->apply_the_reload_history_step(user_involvement);
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return signal_to_continue_session_history_processing;
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -2406,10 +2434,13 @@ void perform_url_and_history_update_steps(DOM::Document& document, URL::URL new_
|
|||
|
||||
// 13. Append the following session history synchronous navigation steps involving navigable to traversable:
|
||||
traversable->append_session_history_synchronous_navigation_steps(*navigable, GC::create_function(document.realm().heap(), [traversable, navigable, new_entry, entry_to_replace, history_handling] {
|
||||
// NB: Use Core::Promise to signal SessionHistoryTraversalQueue that it can continue to execute next entry.
|
||||
auto signal_to_continue_session_history_processing = Core::Promise<Empty>::construct();
|
||||
// 1. Finalize a same-document navigation given traversable, navigable, newEntry, entryToReplace, historyHandling, and "none".
|
||||
finalize_a_same_document_navigation(*traversable, *navigable, new_entry, entry_to_replace, history_handling, UserNavigationInvolvement::None);
|
||||
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
// 2. FIXME: Invoke WebDriver BiDi history updated with navigable.
|
||||
return signal_to_continue_session_history_processing;
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ public:
|
|||
SourceSnapshotParams const& source_snapshot_params,
|
||||
TargetSnapshotParams const& target_snapshot_params,
|
||||
UserNavigationInvolvement user_involvement,
|
||||
NonnullRefPtr<Core::Promise<Empty>> signal_to_continue_session_history_processing,
|
||||
Optional<String> navigation_id = {},
|
||||
NavigationParamsVariant navigation_params = Navigable::NullOrError {},
|
||||
ContentSecurityPolicy::Directives::Directive::NavigationType csp_navigation_type = ContentSecurityPolicy::Directives::Directive::NavigationType::Other,
|
||||
|
|
|
|||
|
|
@ -114,6 +114,8 @@ WebIDL::ExceptionOr<void> NavigableContainer::create_new_child_navigable(GC::Ptr
|
|||
|
||||
// 12. Append the following session history traversal steps to traversable:
|
||||
traversable->append_session_history_traversal_steps(GC::create_function(heap(), [traversable, navigable, parent_navigable, history_entry, after_session_history_update] {
|
||||
// NB: Use Core::Promise to signal SessionHistoryTraversalQueue that it can continue to execute next entry.
|
||||
auto signal_to_continue_session_history_processing = Core::Promise<Empty>::construct();
|
||||
// 1. Let parentDocState be parentNavigable's active session history entry's document state.
|
||||
auto parent_doc_state = parent_navigable->active_session_history_entry()->document_state();
|
||||
|
||||
|
|
@ -143,6 +145,8 @@ WebIDL::ExceptionOr<void> NavigableContainer::create_new_child_navigable(GC::Ptr
|
|||
if (after_session_history_update) {
|
||||
after_session_history_update->function()();
|
||||
}
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return signal_to_continue_session_history_processing;
|
||||
}));
|
||||
|
||||
return {};
|
||||
|
|
@ -315,8 +319,12 @@ void NavigableContainer::destroy_the_child_navigable()
|
|||
|
||||
// 9. Append the following session history traversal steps to traversable:
|
||||
traversable->append_session_history_traversal_steps(GC::create_function(heap(), [traversable] {
|
||||
// NB: Use Core::Promise to signal SessionHistoryTraversalQueue that it can continue to execute next entry.
|
||||
auto signal_to_continue_session_history_processing = Core::Promise<Empty>::construct();
|
||||
// 1. Update for navigable creation/destruction given traversable.
|
||||
traversable->update_for_navigable_creation_or_destruction();
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return signal_to_continue_session_history_processing;
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -664,6 +664,8 @@ WebIDL::ExceptionOr<NavigationResult> Navigation::perform_a_navigation_api_trave
|
|||
|
||||
// 12. Append the following session history traversal steps to traversable:
|
||||
traversable->append_session_history_traversal_steps(GC::create_function(heap(), [key, api_method_tracker, navigable, source_snapshot_params, traversable, this] {
|
||||
// NB: Use Core::Promise to signal SessionHistoryTraversalQueue that it can continue to execute next entry.
|
||||
auto signal_to_continue_session_history_processing = Core::Promise<Empty>::construct();
|
||||
// 1. Let navigableSHEs be the result of getting session history entries given navigable.
|
||||
auto navigable_shes = navigable->get_session_history_entries();
|
||||
|
||||
|
|
@ -685,15 +687,18 @@ WebIDL::ExceptionOr<NavigationResult> Navigation::perform_a_navigation_api_trave
|
|||
}));
|
||||
|
||||
// 2. Abort these steps.
|
||||
return;
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return signal_to_continue_session_history_processing;
|
||||
}
|
||||
auto target_she = *it;
|
||||
|
||||
// 3. If targetSHE is navigable's active session history entry, then abort these steps.
|
||||
// NOTE: This can occur if a previously queued traversal already took us to this session history entry.
|
||||
// In that case the previous traversal will have dealt with apiMethodTracker already.
|
||||
if (target_she == navigable->active_session_history_entry())
|
||||
return;
|
||||
if (target_she == navigable->active_session_history_entry()) {
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return signal_to_continue_session_history_processing;
|
||||
}
|
||||
|
||||
// 4. Let result be the result of applying the traverse history step given by targetSHE's step to traversable,
|
||||
// given sourceSnapshotParams, navigable, and "none".
|
||||
|
|
@ -725,6 +730,8 @@ WebIDL::ExceptionOr<NavigationResult> Navigation::perform_a_navigation_api_trave
|
|||
reject_the_finished_promise(api_method_tracker, WebIDL::SecurityError::create(realm, "Navigation disallowed from this origin"_utf16));
|
||||
}));
|
||||
}
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return signal_to_continue_session_history_processing;
|
||||
}));
|
||||
|
||||
// 13. Return a navigation API method tracker-derived result for apiMethodTracker.
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace Web::HTML {
|
|||
GC_DEFINE_ALLOCATOR(SessionHistoryTraversalQueue);
|
||||
GC_DEFINE_ALLOCATOR(SessionHistoryTraversalQueueEntry);
|
||||
|
||||
GC::Ref<SessionHistoryTraversalQueueEntry> SessionHistoryTraversalQueueEntry::create(JS::VM& vm, GC::Ref<GC::Function<void()>> steps, GC::Ptr<HTML::Navigable> target_navigable)
|
||||
GC::Ref<SessionHistoryTraversalQueueEntry> SessionHistoryTraversalQueueEntry::create(JS::VM& vm, GC::Ref<GC::Function<NonnullRefPtr<Core::Promise<Empty>>()>> steps, GC::Ptr<HTML::Navigable> target_navigable)
|
||||
{
|
||||
return vm.heap().allocate<SessionHistoryTraversalQueueEntry>(steps, target_navigable);
|
||||
}
|
||||
|
|
@ -31,12 +31,20 @@ SessionHistoryTraversalQueue::SessionHistoryTraversalQueue()
|
|||
m_timer->start();
|
||||
return;
|
||||
}
|
||||
|
||||
while (m_queue.size() > 0) {
|
||||
if (m_current_promise && !m_current_promise->is_resolved() && !m_current_promise->is_rejected()) {
|
||||
m_timer->start();
|
||||
return;
|
||||
}
|
||||
|
||||
m_is_task_running = true;
|
||||
auto entry = m_queue.take_first();
|
||||
entry->execute_steps();
|
||||
m_current_promise = entry->execute_steps();
|
||||
m_is_task_running = false;
|
||||
}
|
||||
|
||||
m_current_promise = {};
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -46,7 +54,7 @@ void SessionHistoryTraversalQueue::visit_edges(JS::Cell::Visitor& visitor)
|
|||
visitor.visit(m_queue);
|
||||
}
|
||||
|
||||
void SessionHistoryTraversalQueue::append(GC::Ref<GC::Function<void()>> steps)
|
||||
void SessionHistoryTraversalQueue::append(GC::Ref<GC::Function<NonnullRefPtr<Core::Promise<Empty>>()>> steps)
|
||||
{
|
||||
m_queue.append(SessionHistoryTraversalQueueEntry::create(vm(), steps, nullptr));
|
||||
if (!m_timer->is_active()) {
|
||||
|
|
@ -54,7 +62,7 @@ void SessionHistoryTraversalQueue::append(GC::Ref<GC::Function<void()>> steps)
|
|||
}
|
||||
}
|
||||
|
||||
void SessionHistoryTraversalQueue::append_sync(GC::Ref<GC::Function<void()>> steps, GC::Ptr<Navigable> target_navigable)
|
||||
void SessionHistoryTraversalQueue::append_sync(GC::Ref<GC::Function<NonnullRefPtr<Core::Promise<Empty>>()>> steps, GC::Ptr<Navigable> target_navigable)
|
||||
{
|
||||
m_queue.append(SessionHistoryTraversalQueueEntry::create(vm(), steps, target_navigable));
|
||||
if (!m_timer->is_active()) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/Vector.h>
|
||||
#include <LibCore/Promise.h>
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibGC/CellAllocator.h>
|
||||
#include <LibGC/Function.h>
|
||||
|
|
@ -23,13 +24,13 @@ struct SessionHistoryTraversalQueueEntry : public JS::Cell {
|
|||
GC_DECLARE_ALLOCATOR(SessionHistoryTraversalQueueEntry);
|
||||
|
||||
public:
|
||||
static GC::Ref<SessionHistoryTraversalQueueEntry> create(JS::VM& vm, GC::Ref<GC::Function<void()>> steps, GC::Ptr<HTML::Navigable> target_navigable);
|
||||
static GC::Ref<SessionHistoryTraversalQueueEntry> create(JS::VM& vm, GC::Ref<GC::Function<NonnullRefPtr<Core::Promise<Empty>>()>> steps, GC::Ptr<HTML::Navigable> target_navigable);
|
||||
|
||||
GC::Ptr<HTML::Navigable> target_navigable() const { return m_target_navigable; }
|
||||
void execute_steps() const { m_steps->function()(); }
|
||||
NonnullRefPtr<Core::Promise<Empty>> execute_steps() const { return m_steps->function()(); }
|
||||
|
||||
private:
|
||||
SessionHistoryTraversalQueueEntry(GC::Ref<GC::Function<void()>> steps, GC::Ptr<HTML::Navigable> target_navigable)
|
||||
SessionHistoryTraversalQueueEntry(GC::Ref<GC::Function<NonnullRefPtr<Core::Promise<Empty>>()>> steps, GC::Ptr<HTML::Navigable> target_navigable)
|
||||
: m_steps(steps)
|
||||
, m_target_navigable(target_navigable)
|
||||
{
|
||||
|
|
@ -37,7 +38,7 @@ private:
|
|||
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
GC::Ref<GC::Function<void()>> m_steps;
|
||||
GC::Ref<GC::Function<NonnullRefPtr<Core::Promise<Empty>>()>> m_steps;
|
||||
GC::Ptr<HTML::Navigable> m_target_navigable;
|
||||
};
|
||||
|
||||
|
|
@ -49,8 +50,8 @@ class WEB_API SessionHistoryTraversalQueue : public JS::Cell {
|
|||
public:
|
||||
SessionHistoryTraversalQueue();
|
||||
|
||||
void append(GC::Ref<GC::Function<void()>> steps);
|
||||
void append_sync(GC::Ref<GC::Function<void()>> steps, GC::Ptr<Navigable> target_navigable);
|
||||
void append(GC::Ref<GC::Function<NonnullRefPtr<Core::Promise<Empty>>()>> steps);
|
||||
void append_sync(GC::Ref<GC::Function<NonnullRefPtr<Core::Promise<Empty>>()>> steps, GC::Ptr<Navigable> target_navigable);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#sync-navigations-jump-queue
|
||||
GC::Ptr<SessionHistoryTraversalQueueEntry> first_synchronous_navigation_steps_with_target_navigable_not_contained_in(HashTable<GC::Ref<Navigable>> const&);
|
||||
|
|
@ -61,6 +62,7 @@ private:
|
|||
Vector<GC::Ref<SessionHistoryTraversalQueueEntry>> m_queue;
|
||||
RefPtr<Core::Timer> m_timer;
|
||||
bool m_is_task_running { false };
|
||||
WeakPtr<Core::Promise<Empty>> m_current_promise;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -273,12 +273,15 @@ Vector<GC::Root<Navigable>> TraversableNavigable::get_all_navigables_whose_curre
|
|||
// 1. Let targetEntry be the result of getting the target history entry given navigable and targetStep.
|
||||
auto target_entry = navigable->get_the_target_history_entry(target_step);
|
||||
|
||||
// 2. If targetEntry is not navigable's current session history entry or targetEntry's document state's reload pending is true, then append navigable to results.
|
||||
if (target_entry != navigable->current_session_history_entry() || target_entry->document_state()->reload_pending()) {
|
||||
// 2. If targetEntry is not navigable's current session history entry or targetEntry's document state's reload
|
||||
// pending is true, then append navigable to results.
|
||||
// AD-HOC: We don't want to choose a navigable that has ongoing traversal.
|
||||
if ((target_entry != navigable->current_session_history_entry() || target_entry->document_state()->reload_pending()) && !navigable->ongoing_navigation().has<Traversal>()) {
|
||||
results.append(*navigable);
|
||||
}
|
||||
|
||||
// 3. If targetEntry's document is navigable's document, and targetEntry's document state's reload pending is false, then extend navigablesToCheck with the child navigables of navigable.
|
||||
// 3. If targetEntry's document is navigable's document, and targetEntry's document state's reload pending is
|
||||
// false, then extend navigablesToCheck with the child navigables of navigable.
|
||||
if (target_entry->document() == navigable->active_document() && !target_entry->document_state()->reload_pending()) {
|
||||
navigables_to_check.extend(navigable->child_navigables());
|
||||
}
|
||||
|
|
@ -651,12 +654,23 @@ TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_history_
|
|||
// queue a global task on the navigation and traversal task source given navigable's active window to
|
||||
// run afterDocumentPopulated.
|
||||
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(this->heap(), [populated_target_entry, potentially_target_specific_source_snapshot_params, target_snapshot_params, this, allow_POST, navigable, after_document_populated = GC::create_function(this->heap(), move(after_document_populated)), user_involvement] {
|
||||
navigable->populate_session_history_entry_document(populated_target_entry, *potentially_target_specific_source_snapshot_params, target_snapshot_params, user_involvement, {}, Navigable::NullOrError {}, ContentSecurityPolicy::Directives::Directive::NavigationType::Other, allow_POST, GC::create_function(this->heap(), [this, after_document_populated, populated_target_entry]() mutable {
|
||||
VERIFY(active_window());
|
||||
queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), GC::create_function(this->heap(), [after_document_populated, populated_target_entry]() mutable {
|
||||
after_document_populated->function()(true, populated_target_entry);
|
||||
auto signal_to_continue_session_history_processing = Core::Promise<Empty>::construct();
|
||||
navigable->populate_session_history_entry_document(
|
||||
populated_target_entry,
|
||||
*potentially_target_specific_source_snapshot_params,
|
||||
target_snapshot_params,
|
||||
user_involvement,
|
||||
signal_to_continue_session_history_processing,
|
||||
{},
|
||||
Navigable::NullOrError {},
|
||||
ContentSecurityPolicy::Directives::Directive::NavigationType::Other,
|
||||
allow_POST,
|
||||
GC::create_function(this->heap(), [this, after_document_populated, populated_target_entry]() mutable {
|
||||
VERIFY(active_window());
|
||||
queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), GC::create_function(this->heap(), [after_document_populated, populated_target_entry]() mutable {
|
||||
after_document_populated->function()(true, populated_target_entry);
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}
|
||||
// Otherwise, run afterDocumentPopulated immediately.
|
||||
|
|
@ -701,7 +715,7 @@ TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_history_
|
|||
m_running_nested_apply_history_step = true;
|
||||
|
||||
// 4. Run steps.
|
||||
entry->execute_steps();
|
||||
entry->execute_steps()->await().release_value_but_fixme_should_propagate_errors();
|
||||
|
||||
// 5. Set traversable's running nested apply history step to false.
|
||||
m_running_nested_apply_history_step = false;
|
||||
|
|
@ -1153,6 +1167,8 @@ void TraversableNavigable::traverse_the_history_by_delta(int delta, GC::Ptr<DOM:
|
|||
|
||||
// 4. Append the following session history traversal steps to traversable:
|
||||
append_session_history_traversal_steps(GC::create_function(heap(), [this, delta, source_snapshot_params, initiator_to_check, user_involvement] {
|
||||
// NB: Use Core::Promise to signal SessionHistoryTraversalQueue that it can continue to execute next entry.
|
||||
auto signal_to_continue_session_history_processing = Core::Promise<Empty>::construct();
|
||||
// 1. Let allSteps be the result of getting all used history steps for traversable.
|
||||
auto all_steps = get_all_used_history_steps();
|
||||
|
||||
|
|
@ -1164,12 +1180,15 @@ void TraversableNavigable::traverse_the_history_by_delta(int delta, GC::Ptr<DOM:
|
|||
|
||||
// 4. If allSteps[targetStepIndex] does not exist, then abort these steps.
|
||||
if (target_step_index >= all_steps.size()) {
|
||||
return;
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return signal_to_continue_session_history_processing;
|
||||
}
|
||||
|
||||
// 5. Apply the traverse history step allSteps[targetStepIndex] to traversable, given sourceSnapshotParams,
|
||||
// initiatorToCheck, and userInvolvement.
|
||||
apply_the_traverse_history_step(all_steps[target_step_index], source_snapshot_params, initiator_to_check, user_involvement);
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return signal_to_continue_session_history_processing;
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -1232,6 +1251,8 @@ void TraversableNavigable::definitely_close_top_level_traversable()
|
|||
|
||||
// 3. Append the following session history traversal steps to traversable:
|
||||
append_session_history_traversal_steps(GC::create_function(heap(), [this] {
|
||||
// NB: Use Core::Promise to signal SessionHistoryTraversalQueue that it can continue to execute next entry.
|
||||
auto signal_to_continue_session_history_processing = Core::Promise<Empty>::construct();
|
||||
// 1. Let afterAllUnloads be an algorithm step which destroys traversable.
|
||||
auto after_all_unloads = GC::create_function(heap(), [this] {
|
||||
destroy_top_level_traversable();
|
||||
|
|
@ -1239,6 +1260,8 @@ void TraversableNavigable::definitely_close_top_level_traversable()
|
|||
|
||||
// 2. Unload a document and its descendants given traversable's active document, null, and afterAllUnloads.
|
||||
active_document()->unload_a_document_and_its_descendants({}, after_all_unloads);
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return signal_to_continue_session_history_processing;
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,12 +87,12 @@ public:
|
|||
void definitely_close_top_level_traversable();
|
||||
void destroy_top_level_traversable();
|
||||
|
||||
void append_session_history_traversal_steps(GC::Ref<GC::Function<void()>> steps)
|
||||
void append_session_history_traversal_steps(GC::Ref<GC::Function<NonnullRefPtr<Core::Promise<Empty>>()>> steps)
|
||||
{
|
||||
m_session_history_traversal_queue->append(steps);
|
||||
}
|
||||
|
||||
void append_session_history_synchronous_navigation_steps(GC::Ref<Navigable> target_navigable, GC::Ref<GC::Function<void()>> steps)
|
||||
void append_session_history_synchronous_navigation_steps(GC::Ref<Navigable> target_navigable, GC::Ref<GC::Function<NonnullRefPtr<Core::Promise<Empty>>()>> steps)
|
||||
{
|
||||
m_session_history_traversal_queue->append_sync(steps, target_navigable);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue