LibWebView: Keep UI history authoritative during races
Keep UI process session history authoritative across overlapping fallback loads and traversals. WebContent can finish a superseded history load with a live document matching the UI seed URL while its local step, document state id, and Navigation API keys still describe a temporary partial list. Reconstruct the current entry around the UI-owned list in that case. This avoids making the UI process adopt WebContent's incomplete snapshot. Track UI-started fallback loads by URL so unrelated navigations cannot consume the pending seed state. Resolve deferred WebDriver completions through the view registry so callbacks queued before a process swap do not touch a destroyed view. Add WebDriver coverage that waits for explicit UI/WebContent history convergence after the relevant document events. The waits poll observable history state instead of depending on timing.
This commit is contained in:
parent
641404d6b3
commit
bbeb2eb69c
4 changed files with 159 additions and 22 deletions
|
|
@ -405,8 +405,20 @@ bool TraversableNavigable::replace_top_level_session_history_entries_from_ui_pro
|
|||
&& active_entry_is_latest_entry
|
||||
&& current_entry_url_matches_ui_seed;
|
||||
|
||||
// NB: UI-process fallback history loads can overlap when a newer traversal supersedes an older one before the
|
||||
// older load has finished. Other engines give pending history loads an identity so the latest traversal
|
||||
// stays authoritative; after the race has happened, WebContent can still have the latest live document in
|
||||
// an incomplete local top-level list, for example [b, c] while the UI process is restoring [a, b, c] at c.
|
||||
// If the live active entry has the UI seed's current URL, accept the UI-owned list around that document
|
||||
// instead of making the UI process adopt the incomplete WebContent list. The WebContent step, document state
|
||||
// id, and Navigation API identity are all process-local placeholders at this point, and are replaced by the
|
||||
// UI-owned values below.
|
||||
auto can_restore_current_entry_after_superseded_ui_history_load = entries_from_ui_process.size() > m_session_history_entries.size()
|
||||
&& active_entry_is_latest_entry
|
||||
&& current_entry_url_matches_ui_seed;
|
||||
|
||||
auto can_reconstruct_current_entry = allow_reconstructing_current_entry
|
||||
&& (can_restore_fresh_ui_history_load || can_restore_preseeded_ui_history_load);
|
||||
&& (can_restore_fresh_ui_history_load || can_restore_preseeded_ui_history_load || can_restore_current_entry_after_superseded_ui_history_load);
|
||||
if (!latest_entry_matches_ui_seed && !can_reconstruct_current_entry)
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ void ViewImplementation::create_new_process_for_cross_site_navigation(URL::URL c
|
|||
return false;
|
||||
if (!m_pending_web_content_session_history_seed.should_send_entries)
|
||||
return false;
|
||||
if (m_loading_session_history_entry_from_ui_process)
|
||||
if (m_session_history_entry_url_loading_from_ui_process.has_value())
|
||||
return false;
|
||||
if (m_session_history.current_step_to_restore_after_loading_top_level_entry().has_value())
|
||||
return false;
|
||||
|
|
@ -194,7 +194,7 @@ void ViewImplementation::create_new_process_for_cross_site_navigation(URL::URL c
|
|||
if (ui_session_history_already_points_to_url) {
|
||||
m_should_suppress_history_for_current_load = false;
|
||||
m_should_suppress_history_for_next_load = false;
|
||||
if (!m_loading_session_history_entry_from_ui_process)
|
||||
if (!m_session_history_entry_url_loading_from_ui_process.has_value())
|
||||
m_pending_web_content_session_history_seed.step_after_loading_top_level_entry = m_session_history.current_step_to_restore_after_loading_top_level_entry();
|
||||
set_url(url);
|
||||
auto seeded_replacement_process_before_load = seed_replacement_process_before_load_if_possible();
|
||||
|
|
@ -204,7 +204,7 @@ void ViewImplementation::create_new_process_for_cross_site_navigation(URL::URL c
|
|||
} else {
|
||||
m_should_suppress_history_for_current_load = false;
|
||||
m_should_suppress_history_for_next_load = false;
|
||||
if (should_manage_session_history_in_ui_process() && !m_loading_session_history_entry_from_ui_process) {
|
||||
if (should_manage_session_history_in_ui_process() && !m_session_history_entry_url_loading_from_ui_process.has_value()) {
|
||||
if (m_session_history.current_entry()) {
|
||||
m_pending_session_history_navigation = PendingSessionHistoryNavigation {
|
||||
url,
|
||||
|
|
@ -221,7 +221,7 @@ void ViewImplementation::create_new_process_for_cross_site_navigation(URL::URL c
|
|||
m_current_web_content_session_history_matches_mirror = false;
|
||||
update_navigation_action_state();
|
||||
}
|
||||
if (!m_loading_session_history_entry_from_ui_process)
|
||||
if (!m_session_history_entry_url_loading_from_ui_process.has_value())
|
||||
m_pending_web_content_session_history_seed.step_after_loading_top_level_entry = m_session_history.current_step_to_restore_after_loading_top_level_entry();
|
||||
set_url(url);
|
||||
auto seeded_replacement_process_before_load = seed_replacement_process_before_load_if_possible();
|
||||
|
|
@ -282,9 +282,9 @@ void ViewImplementation::load(URL::URL const& url, Web::Bindings::NavigationHist
|
|||
m_should_suppress_history_for_current_load = false;
|
||||
m_should_suppress_history_for_next_load = false;
|
||||
auto should_defer_ui_process_history_update = false;
|
||||
if (!m_loading_session_history_entry_from_ui_process)
|
||||
if (!m_session_history_entry_url_loading_from_ui_process.has_value())
|
||||
abandon_pending_web_content_session_history_seed();
|
||||
if (should_manage_session_history_in_ui_process() && !m_loading_session_history_entry_from_ui_process) {
|
||||
if (should_manage_session_history_in_ui_process() && !m_session_history_entry_url_loading_from_ui_process.has_value()) {
|
||||
m_pending_session_history_traversal.clear();
|
||||
auto const* current_entry = m_session_history.current_entry();
|
||||
auto is_javascript_navigation = url.scheme() == "javascript"sv;
|
||||
|
|
@ -428,7 +428,7 @@ ViewImplementation::HistoryTraversalOutcome ViewImplementation::traverse_the_his
|
|||
auto web_content_can_apply_traversal = !m_pending_web_content_session_history_seed.should_send_entries
|
||||
&& !m_pending_web_content_session_history_seed.ignore_updates_until_seed
|
||||
&& !m_pending_web_content_session_history_seed.waiting_for_ack
|
||||
&& !m_loading_session_history_entry_from_ui_process
|
||||
&& !m_session_history_entry_url_loading_from_ui_process.has_value()
|
||||
&& !m_pending_web_content_session_history_seed.step_after_loading_top_level_entry.has_value()
|
||||
&& m_session_history.web_content_can_traverse_to(*target);
|
||||
|
||||
|
|
@ -1416,9 +1416,18 @@ void ViewImplementation::did_start_navigation(URL::URL const& url, Variant<Empty
|
|||
if (m_should_suppress_history_for_next_load || m_should_suppress_history_for_current_load)
|
||||
return;
|
||||
|
||||
if (m_loading_session_history_entry_from_ui_process) {
|
||||
if (m_session_history_entry_url_loading_from_ui_process.has_value()) {
|
||||
if (*m_session_history_entry_url_loading_from_ui_process != url) {
|
||||
// NB: An earlier UI-process history fallback load can start after a newer back/forward request has
|
||||
// superseded it. Chromium, WebKit, and Firefox all give pending history loads an identity so stale
|
||||
// traversals cannot consume state belonging to the current one. Keep the UI-owned history authoritative
|
||||
// here and wait for the load matching the latest requested entry.
|
||||
dump_session_history("ignored-stale-ui-history-load-start"sv);
|
||||
return;
|
||||
}
|
||||
|
||||
auto should_keep_preseeded_web_content_history = m_pending_web_content_session_history_seed.waiting_for_ack || m_session_history.web_content_uses_ui_step_coordinates();
|
||||
m_loading_session_history_entry_from_ui_process = false;
|
||||
m_session_history_entry_url_loading_from_ui_process.clear();
|
||||
if (!should_keep_preseeded_web_content_history) {
|
||||
m_current_web_content_session_history_matches_mirror = false;
|
||||
m_session_history.forget_web_content_state();
|
||||
|
|
@ -1427,6 +1436,13 @@ void ViewImplementation::did_start_navigation(URL::URL const& url, Variant<Empty
|
|||
return;
|
||||
}
|
||||
|
||||
if (m_pending_web_content_session_history_seed.should_send_entries || m_pending_web_content_session_history_seed.ignore_updates_until_seed || m_pending_web_content_session_history_seed.waiting_for_ack) {
|
||||
if (auto const* current_entry = m_session_history.current_entry(); current_entry && current_entry->url != url) {
|
||||
dump_session_history("ignored-navigation-start-before-ui-history-seed"sv);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_is_showing_crash_page) {
|
||||
m_is_showing_crash_page = false;
|
||||
if (auto const* current_entry = m_session_history.current_entry(); current_entry && current_entry->url == url) {
|
||||
|
|
@ -1491,8 +1507,8 @@ void ViewImplementation::did_cancel_navigation(URL::URL const& url)
|
|||
return;
|
||||
}
|
||||
|
||||
if (m_loading_session_history_entry_from_ui_process) {
|
||||
m_loading_session_history_entry_from_ui_process = false;
|
||||
if (m_session_history_entry_url_loading_from_ui_process.has_value() && *m_session_history_entry_url_loading_from_ui_process == url) {
|
||||
m_session_history_entry_url_loading_from_ui_process.clear();
|
||||
abandon_pending_web_content_session_history_seed();
|
||||
m_current_web_content_session_history_matches_mirror = false;
|
||||
m_session_history.forget_web_content_state();
|
||||
|
|
@ -1561,7 +1577,7 @@ bool ViewImplementation::restore_pending_session_history_navigation(StringView r
|
|||
m_webdriver_pending_navigation_completes_with_session_history_update = true;
|
||||
load_current_session_history_entry_from_ui_process();
|
||||
} else {
|
||||
m_loading_session_history_entry_from_ui_process = false;
|
||||
m_session_history_entry_url_loading_from_ui_process.clear();
|
||||
abandon_pending_web_content_session_history_seed();
|
||||
m_current_web_content_session_history_matches_mirror = m_session_history.web_content_history_matches_mirror();
|
||||
m_webdriver_pending_navigation_url.clear();
|
||||
|
|
@ -1579,6 +1595,7 @@ bool ViewImplementation::restore_pending_session_history_navigation(StringView r
|
|||
|
||||
void ViewImplementation::abandon_pending_web_content_session_history_seed()
|
||||
{
|
||||
m_session_history_entry_url_loading_from_ui_process.clear();
|
||||
m_pending_web_content_session_history_seed.clear();
|
||||
}
|
||||
|
||||
|
|
@ -1686,9 +1703,13 @@ void ViewImplementation::complete_webdriver_pending_navigation_if_url_matches(UR
|
|||
for (auto const& request : m_pending_webdriver_navigation_completion_requests)
|
||||
request_ids.unchecked_append(request.key);
|
||||
|
||||
auto view_id = m_view_id;
|
||||
for (auto request_id : request_ids) {
|
||||
Core::EventLoop::current().deferred_invoke([this, request_id] {
|
||||
complete_webdriver_navigation_completion(request_id, JsonValue {});
|
||||
Core::EventLoop::current().deferred_invoke([view_id, request_id] {
|
||||
auto view = ViewImplementation::find_view_by_id(view_id);
|
||||
if (!view.has_value())
|
||||
return;
|
||||
view->complete_webdriver_navigation_completion(request_id, JsonValue {});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1701,7 +1722,7 @@ JsonValue ViewImplementation::webdriver_session_history() const
|
|||
serialized.set("backButtonEnabled"sv, m_navigate_back_action->enabled());
|
||||
serialized.set("forwardButtonEnabled"sv, m_navigate_forward_action->enabled());
|
||||
serialized.set("webContentHistoryMatchesUI"sv, m_current_web_content_session_history_matches_mirror);
|
||||
serialized.set("loadingSessionHistoryEntryFromUI"sv, m_loading_session_history_entry_from_ui_process);
|
||||
serialized.set("loadingSessionHistoryEntryFromUI"sv, m_session_history_entry_url_loading_from_ui_process.has_value());
|
||||
serialized.set("waitingToSeedWebContent"sv, m_pending_web_content_session_history_seed.should_send_entries);
|
||||
serialized.set("waitingForWebContentSeedAck"sv, m_pending_web_content_session_history_seed.waiting_for_ack);
|
||||
serialized.set("ignoringWebContentUpdatesUntilSeed"sv, m_pending_web_content_session_history_seed.ignore_updates_until_seed);
|
||||
|
|
@ -1848,14 +1869,14 @@ void ViewImplementation::restore_current_session_history_entry_from_ui_process()
|
|||
|
||||
void ViewImplementation::load_current_session_history_entry_from_ui_process()
|
||||
{
|
||||
m_loading_session_history_entry_from_ui_process = true;
|
||||
|
||||
auto const* current_entry = m_session_history.current_entry();
|
||||
if (!current_entry) {
|
||||
m_session_history_entry_url_loading_from_ui_process = m_url;
|
||||
client().async_load_url(page_id(), m_url, Web::Bindings::NavigationHistoryBehavior::Auto);
|
||||
return;
|
||||
}
|
||||
|
||||
m_session_history_entry_url_loading_from_ui_process = current_entry->url;
|
||||
auto history_handling = m_pending_web_content_session_history_seed.waiting_for_ack || m_session_history.web_content_uses_ui_step_coordinates()
|
||||
? Web::Bindings::NavigationHistoryBehavior::Replace
|
||||
: Web::Bindings::NavigationHistoryBehavior::Auto;
|
||||
|
|
@ -2156,7 +2177,7 @@ void ViewImplementation::did_reset_session_history_for_testing(Badge<WebContentC
|
|||
m_current_web_content_session_history_matches_mirror = false;
|
||||
m_pending_session_history_navigation.clear();
|
||||
m_pending_session_history_traversal.clear();
|
||||
m_loading_session_history_entry_from_ui_process = false;
|
||||
m_session_history_entry_url_loading_from_ui_process.clear();
|
||||
abandon_pending_web_content_session_history_seed();
|
||||
m_webdriver_pending_navigation_url.clear();
|
||||
m_webdriver_pending_navigation_completes_with_session_history_update = false;
|
||||
|
|
@ -2202,7 +2223,7 @@ void ViewImplementation::dump_session_history(StringView reason, SessionHistoryD
|
|||
m_url,
|
||||
m_current_web_content_session_history_matches_mirror,
|
||||
m_session_history.web_content_uses_ui_step_coordinates(),
|
||||
m_loading_session_history_entry_from_ui_process,
|
||||
m_session_history_entry_url_loading_from_ui_process.has_value(),
|
||||
m_pending_web_content_session_history_seed.should_send_entries,
|
||||
m_pending_web_content_session_history_seed.waiting_for_ack,
|
||||
m_pending_web_content_session_history_seed.ignore_updates_until_seed,
|
||||
|
|
@ -2254,7 +2275,7 @@ void ViewImplementation::handle_web_content_process_crash(LoadErrorPage load_err
|
|||
m_backup_shared_image_buffer = nullptr;
|
||||
|
||||
if (should_manage_session_history_in_ui_process()) {
|
||||
m_loading_session_history_entry_from_ui_process = false;
|
||||
m_session_history_entry_url_loading_from_ui_process.clear();
|
||||
prepare_to_seed_web_content_session_history_from_ui_process();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -572,7 +572,7 @@ protected:
|
|||
Optional<PendingSessionHistoryNavigation> m_pending_session_history_navigation;
|
||||
Optional<PendingSessionHistoryTraversal> m_pending_session_history_traversal;
|
||||
u64 m_next_traverse_history_step_cancelation_check_request_id { 0 };
|
||||
bool m_loading_session_history_entry_from_ui_process { false };
|
||||
Optional<URL::URL> m_session_history_entry_url_loading_from_ui_process;
|
||||
PendingWebContentSessionHistorySeed m_pending_web_content_session_history_seed;
|
||||
Optional<URL::URL> m_webdriver_pending_navigation_url;
|
||||
bool m_webdriver_pending_navigation_completes_with_session_history_update { false };
|
||||
|
|
|
|||
|
|
@ -1023,6 +1023,45 @@ def expect_ui_session_history(
|
|||
)
|
||||
|
||||
|
||||
def wait_for_ui_session_history(
|
||||
webdriver_port,
|
||||
session_id,
|
||||
label,
|
||||
expected_entry_urls,
|
||||
expected_used_steps,
|
||||
expected_current_used_step_index,
|
||||
expected_back_enabled,
|
||||
expected_forward_enabled,
|
||||
log,
|
||||
expected_web_content_known_used_steps,
|
||||
expected_web_content_current_step,
|
||||
):
|
||||
def matches_expected_history(snapshot):
|
||||
ui = snapshot["ui"]
|
||||
web_content = snapshot["webContent"]
|
||||
return (
|
||||
history_entry_urls(ui) == expected_entry_urls
|
||||
and history_used_steps(ui) == expected_used_steps
|
||||
and ui["currentUsedStepIndex"] == expected_current_used_step_index
|
||||
and ui["backButtonEnabled"] is expected_back_enabled
|
||||
and ui["forwardButtonEnabled"] is expected_forward_enabled
|
||||
and ui["webContentHistoryMatchesUI"]
|
||||
and history_step_values(ui["webContentKnownUsedSteps"]) == expected_web_content_known_used_steps
|
||||
and ui["webContentCurrentStep"] == expected_web_content_current_step
|
||||
and history_current_step(ui["webContentKnownUsedSteps"]) == expected_web_content_current_step
|
||||
and not ui["waitingToSeedWebContent"]
|
||||
and not ui["waitingForWebContentSeedAck"]
|
||||
and not ui["ignoringWebContentUpdatesUntilSeed"]
|
||||
and not ui["reseedAfterCurrentHistoryLoad"]
|
||||
and ui["pendingWebContentHistoryStepAfterFallbackLoad"] is None
|
||||
and ui["pendingSessionHistoryNavigation"] is None
|
||||
and ui["pendingSessionHistoryTraversal"] is None
|
||||
and comparable_history(ui) == comparable_history(web_content)
|
||||
)
|
||||
|
||||
return wait_for_session_history(webdriver_port, session_id, label, matches_expected_history, log)
|
||||
|
||||
|
||||
def expect_beforeunload_cancels_webdriver_navigation(
|
||||
webdriver_port,
|
||||
session_id,
|
||||
|
|
@ -2352,6 +2391,19 @@ return [Math.round(rect.left + rect.width / 2), Math.round(rect.top + rect.heigh
|
|||
)
|
||||
page_server.release_blocked_process_swap_back.set()
|
||||
wait_for_event(page_server.process_swap_back_document_ran, "process-swap UI back document")
|
||||
wait_for_ui_session_history(
|
||||
webdriver_port,
|
||||
session_id,
|
||||
"after blocked process-swap UI back converges",
|
||||
[url_process_swap_back_blocked, url_b],
|
||||
[0, 1],
|
||||
0,
|
||||
False,
|
||||
True,
|
||||
log,
|
||||
expected_web_content_known_used_steps=[0, 1],
|
||||
expected_web_content_current_step=0,
|
||||
)
|
||||
expect_url(
|
||||
webdriver_port,
|
||||
session_id,
|
||||
|
|
@ -3179,6 +3231,19 @@ return [location.href, window.scriptBeforeUnloadCount, navigator.userActivation.
|
|||
page_server.a_document_ran.clear()
|
||||
perform_browser_history_shortcut(webdriver_port, session_id, "left", log)
|
||||
wait_for_event(page_server.a_document_ran, "A document after browser shortcut back")
|
||||
wait_for_ui_session_history(
|
||||
webdriver_port,
|
||||
session_id,
|
||||
"after cross-site browser shortcut back to /a converges",
|
||||
[url_a, url_b],
|
||||
[0, 1],
|
||||
0,
|
||||
False,
|
||||
True,
|
||||
log,
|
||||
expected_web_content_known_used_steps=[0, 1],
|
||||
expected_web_content_current_step=0,
|
||||
)
|
||||
expect_url(webdriver_port, session_id, "after cross-site browser shortcut back to /a", url_a, log)
|
||||
expect_ui_session_history(
|
||||
webdriver_port,
|
||||
|
|
@ -3198,6 +3263,19 @@ return [location.href, window.scriptBeforeUnloadCount, navigator.userActivation.
|
|||
page_server.b_document_ran.clear()
|
||||
perform_browser_history_shortcut(webdriver_port, session_id, "right", log)
|
||||
wait_for_event(page_server.b_document_ran, "B document after browser shortcut forward")
|
||||
wait_for_ui_session_history(
|
||||
webdriver_port,
|
||||
session_id,
|
||||
"after cross-site browser shortcut forward to /b converges",
|
||||
[url_a, url_b],
|
||||
[0, 1],
|
||||
1,
|
||||
True,
|
||||
False,
|
||||
log,
|
||||
expected_web_content_known_used_steps=[0, 1],
|
||||
expected_web_content_current_step=1,
|
||||
)
|
||||
expect_url(webdriver_port, session_id, "after cross-site browser shortcut forward to /b", url_b, log)
|
||||
expect_ui_session_history(
|
||||
webdriver_port,
|
||||
|
|
@ -3235,6 +3313,19 @@ return [location.href, window.scriptBeforeUnloadCount, navigator.userActivation.
|
|||
page_server.b_document_ran.clear()
|
||||
perform_browser_history_shortcut(webdriver_port, session_id, "left", log)
|
||||
wait_for_event(page_server.b_document_ran, "B document after browser shortcut back")
|
||||
wait_for_ui_session_history(
|
||||
webdriver_port,
|
||||
session_id,
|
||||
"after browser shortcut back to /b converges",
|
||||
[url_a, url_b, url_c],
|
||||
[0, 1, 2],
|
||||
1,
|
||||
True,
|
||||
True,
|
||||
log,
|
||||
expected_web_content_known_used_steps=[0, 1, 2],
|
||||
expected_web_content_current_step=1,
|
||||
)
|
||||
expect_url(webdriver_port, session_id, "after browser shortcut back to /b", url_b, log)
|
||||
expect_ui_session_history(
|
||||
webdriver_port,
|
||||
|
|
@ -3254,6 +3345,19 @@ return [location.href, window.scriptBeforeUnloadCount, navigator.userActivation.
|
|||
page_server.c_document_ran.clear()
|
||||
perform_browser_history_shortcut(webdriver_port, session_id, "right", log)
|
||||
wait_for_event(page_server.c_document_ran, "C document after browser shortcut forward")
|
||||
wait_for_ui_session_history(
|
||||
webdriver_port,
|
||||
session_id,
|
||||
"after browser shortcut forward to /c converges",
|
||||
[url_a, url_b, url_c],
|
||||
[0, 1, 2],
|
||||
2,
|
||||
True,
|
||||
False,
|
||||
log,
|
||||
expected_web_content_known_used_steps=[0, 1, 2],
|
||||
expected_web_content_current_step=2,
|
||||
)
|
||||
expect_url(webdriver_port, session_id, "after browser shortcut forward to /c", url_c, log)
|
||||
expect_ui_session_history(
|
||||
webdriver_port,
|
||||
|
|
|
|||
Loading…
Reference in a new issue