feat: Restore webContents navigation history and page state (#45433)

* feat: Working navigationHistory.restore with just title/url

* feat: Restore page state, too

* chore: Docs, lint, tests

* Implement feedback

* More magic

* Make _awaitNextLoad truly private

* Implement API group feedback

* One more round of feedback
This commit is contained in:
Felix Rieseberg 2025-02-11 15:09:38 -08:00 committed by GitHub
parent 6fdfca6e49
commit 9f47c9a051
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 259 additions and 10 deletions

View file

@ -54,6 +54,7 @@
#include "content/public/browser/keyboard_event_processing_result.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_entry_restore_context.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
@ -363,14 +364,60 @@ struct Converter<scoped_refptr<content::DevToolsAgentHost>> {
template <>
struct Converter<content::NavigationEntry*> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
content::NavigationEntry** out) {
gin_helper::Dictionary dict;
if (!gin::ConvertFromV8(isolate, val, &dict))
return false;
std::string url_str;
std::string title;
std::string encoded_page_state;
GURL url;
if (!dict.Get("url", &url) || !dict.Get("title", &title))
return false;
auto entry = content::NavigationEntry::Create();
entry->SetURL(url);
entry->SetTitle(base::UTF8ToUTF16(title));
// Handle optional page state
if (dict.Get("pageState", &encoded_page_state)) {
std::string decoded_page_state;
if (base::Base64Decode(encoded_page_state, &decoded_page_state)) {
auto restore_context = content::NavigationEntryRestoreContext::Create();
auto page_state =
blink::PageState::CreateFromEncodedData(decoded_page_state);
if (!page_state.IsValid())
return false;
entry->SetPageState(std::move(page_state), restore_context.get());
}
}
*out = entry.release();
return true;
}
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
content::NavigationEntry* entry) {
if (!entry) {
return v8::Null(isolate);
}
gin_helper::Dictionary dict(isolate, v8::Object::New(isolate));
gin_helper::Dictionary dict = gin_helper::Dictionary::CreateEmpty(isolate);
dict.Set("url", entry->GetURL().spec());
dict.Set("title", entry->GetTitleForDisplay());
// Page state saves scroll position and values of any form fields
const blink::PageState& page_state = entry->GetPageState();
if (page_state.IsValid()) {
std::string encoded_data = base::Base64Encode(page_state.ToEncodedData());
dict.Set("pageState", encoded_data);
}
return dict.GetHandle();
}
};
@ -2572,6 +2619,47 @@ std::vector<content::NavigationEntry*> WebContents::GetHistory() const {
return history;
}
void WebContents::RestoreHistory(
v8::Isolate* isolate,
gin_helper::ErrorThrower thrower,
int index,
const std::vector<v8::Local<v8::Value>>& entries) {
if (!web_contents()
->GetController()
.GetLastCommittedEntry()
->IsInitialEntry()) {
thrower.ThrowError(
"Cannot restore history on webContents that have previously loaded "
"a page.");
return;
}
auto navigation_entries = std::make_unique<
std::vector<std::unique_ptr<content::NavigationEntry>>>();
for (const auto& entry : entries) {
content::NavigationEntry* nav_entry = nullptr;
if (!gin::Converter<content::NavigationEntry*>::FromV8(isolate, entry,
&nav_entry) ||
!nav_entry) {
// Invalid entry, bail out early
thrower.ThrowError(
"Failed to restore navigation history: Invalid navigation entry at "
"index " +
std::to_string(index) + ".");
return;
}
navigation_entries->push_back(
std::unique_ptr<content::NavigationEntry>(nav_entry));
}
if (!navigation_entries->empty()) {
web_contents()->GetController().Restore(
index, content::RestoreType::kRestored, navigation_entries.get());
web_contents()->GetController().LoadIfNecessary();
}
}
void WebContents::ClearHistory() {
// In some rare cases (normally while there is no real history) we are in a
// state where we can't prune navigation entries
@ -4397,6 +4485,7 @@ void WebContents::FillObjectTemplate(v8::Isolate* isolate,
&WebContents::RemoveNavigationEntryAtIndex)
.SetMethod("_getHistory", &WebContents::GetHistory)
.SetMethod("_clearHistory", &WebContents::ClearHistory)
.SetMethod("_restoreHistory", &WebContents::RestoreHistory)
.SetMethod("isCrashed", &WebContents::IsCrashed)
.SetMethod("forcefullyCrashRenderer",
&WebContents::ForcefullyCrashRenderer)