feat: extend navigationHistory API (#42014)

* feat: extend navigationHistory API

* refactor: simplify index checking

* refactor: rename 'getHistory' and 'replaceHistory' methods of navigationHistory

* refactor: rename delete*() methods to remove*()

* feat: remove navigationHistory.replaceHistory()

* tests: add tests for removeEntryAtIndex and getAllEntries
This commit is contained in:
Vít Černý 2024-08-19 21:46:04 +02:00 committed by GitHub
parent 4c3014944c
commit 189675575c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 105 additions and 6 deletions

View file

@ -35,10 +35,7 @@ Returns `Integer` - The index of the current page, from which we would go back/f
* `index` Integer
Returns `Object`:
* `url` string - The URL of the navigation entry at the given index.
* `title` string - The page title of the navigation entry at the given index.
Returns [`NavigationEntry`](structures/navigation-entry.md) - Navigation entry at the given index.
If index is out of bounds (greater than history length or less than 0), null will be returned.
@ -65,3 +62,15 @@ Navigates to the specified offset from the current entry.
#### `navigationHistory.length()`
Returns `Integer` - History length.
#### `navigationHistory.removeEntryAtIndex(index)`
* `index` Integer
Removes the navigation entry at the given index. Can't remove entry at the "current active index".
Returns `boolean` - Whether the navigation entry was removed from the webContents history.
#### `navigationHistory.getAllEntries()`
Returns [`NavigationEntry[]`](structures/navigation-entry.md) - WebContents complete history.

View file

@ -0,0 +1,4 @@
# NavigationEntry Object
* `url` string
* `title` string

View file

@ -105,6 +105,7 @@ auto_filenames = {
"docs/api/structures/mime-typed-buffer.md",
"docs/api/structures/mouse-input-event.md",
"docs/api/structures/mouse-wheel-input-event.md",
"docs/api/structures/navigation-entry.md",
"docs/api/structures/notification-action.md",
"docs/api/structures/notification-response.md",
"docs/api/structures/open-external-permission-request.md",

View file

@ -595,7 +595,9 @@ WebContents.prototype._init = function () {
goToOffset: this._goToOffset.bind(this),
getActiveIndex: this._getActiveIndex.bind(this),
length: this._historyLength.bind(this),
getEntryAtIndex: this._getNavigationEntryAtIndex.bind(this)
getEntryAtIndex: this._getNavigationEntryAtIndex.bind(this),
removeEntryAtIndex: this._removeNavigationEntryAtIndex.bind(this),
getAllEntries: this._getHistory.bind(this)
},
writable: false,
enumerable: true

View file

@ -2496,6 +2496,31 @@ content::NavigationEntry* WebContents::GetNavigationEntryAtIndex(
return web_contents()->GetController().GetEntryAtIndex(index);
}
bool WebContents::RemoveNavigationEntryAtIndex(int index) {
if (!CanGoToIndex(index))
return false;
return web_contents()->GetController().RemoveEntryAtIndex(index);
}
std::vector<content::NavigationEntry*> WebContents::GetHistory() const {
const int history_length = GetHistoryLength();
auto& controller = web_contents()->GetController();
// If the history is empty, it contains only one entry and that is
// "InitialEntry"
if (history_length == 1 && controller.GetEntryAtIndex(0)->IsInitialEntry())
return std::vector<content::NavigationEntry*>();
std::vector<content::NavigationEntry*> history;
history.reserve(history_length);
for (int i = 0; i < history_length; i++)
history.push_back(controller.GetEntryAtIndex(i));
return history;
}
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
@ -4295,6 +4320,9 @@ void WebContents::FillObjectTemplate(v8::Isolate* isolate,
.SetMethod("_getNavigationEntryAtIndex",
&WebContents::GetNavigationEntryAtIndex)
.SetMethod("_historyLength", &WebContents::GetHistoryLength)
.SetMethod("_removeNavigationEntryAtIndex",
&WebContents::RemoveNavigationEntryAtIndex)
.SetMethod("_getHistory", &WebContents::GetHistory)
.SetMethod("_clearHistory", &WebContents::ClearHistory)
.SetMethod("isCrashed", &WebContents::IsCrashed)
.SetMethod("forcefullyCrashRenderer",

View file

@ -204,6 +204,8 @@ class WebContents : public ExclusiveAccessContext,
void GoToIndex(int index);
int GetActiveIndex() const;
content::NavigationEntry* GetNavigationEntryAtIndex(int index) const;
bool RemoveNavigationEntryAtIndex(int index);
std::vector<content::NavigationEntry*> GetHistory() const;
void ClearHistory();
int GetHistoryLength() const;
const std::string GetWebRTCIPHandlingPolicy() const;

View file

@ -567,6 +567,39 @@ describe('webContents module', () => {
w = new BrowserWindow({ show: false });
});
afterEach(closeAllWindows);
describe('navigationHistory.removeEntryAtIndex(index) API', () => {
it('should remove a navigation entry given a valid index', async () => {
await w.loadURL(urlPage1);
await w.loadURL(urlPage2);
await w.loadURL(urlPage3);
const initialLength = w.webContents.navigationHistory.length();
const wasRemoved = w.webContents.navigationHistory.removeEntryAtIndex(1); // Attempt to remove the second entry
const newLength = w.webContents.navigationHistory.length();
expect(wasRemoved).to.be.true();
expect(newLength).to.equal(initialLength - 1);
});
it('should not remove the current active navigation entry', async () => {
await w.loadURL(urlPage1);
await w.loadURL(urlPage2);
const activeIndex = w.webContents.navigationHistory.getActiveIndex();
const wasRemoved = w.webContents.navigationHistory.removeEntryAtIndex(activeIndex);
expect(wasRemoved).to.be.false();
});
it('should return false given an invalid index larger than history length', async () => {
await w.loadURL(urlPage1);
const wasRemoved = w.webContents.navigationHistory.removeEntryAtIndex(5); // Index larger than history length
expect(wasRemoved).to.be.false();
});
it('should return false given an invalid negative index', async () => {
await w.loadURL(urlPage1);
const wasRemoved = w.webContents.navigationHistory.removeEntryAtIndex(-1); // Negative index
expect(wasRemoved).to.be.false();
});
});
describe('navigationHistory.canGoBack and navigationHistory.goBack API', () => {
it('should not be able to go back if history is empty', async () => {
expect(w.webContents.navigationHistory.canGoBack()).to.be.false();
@ -706,6 +739,24 @@ describe('webContents module', () => {
expect(w.webContents.navigationHistory.length()).to.equal(1);
});
});
describe('navigationHistory.getAllEntries() API', () => {
it('should return all navigation entries as an array of NavigationEntry objects', async () => {
await w.loadURL(urlPage1);
await w.loadURL(urlPage2);
await w.loadURL(urlPage3);
const entries = w.webContents.navigationHistory.getAllEntries();
expect(entries.length).to.equal(3);
expect(entries[0]).to.deep.equal({ url: urlPage1, title: 'Page 1' });
expect(entries[1]).to.deep.equal({ url: urlPage2, title: 'Page 2' });
expect(entries[2]).to.deep.equal({ url: urlPage3, title: 'Page 3' });
});
it('should return an empty array when there is no navigation history', async () => {
const entries = w.webContents.navigationHistory.getAllEntries();
expect(entries.length).to.equal(0);
});
});
});
describe('getFocusedWebContents() API', () => {

View file

@ -87,7 +87,7 @@ declare namespace Electron {
_print(options: any, callback?: (success: boolean, failureReason: string) => void): void;
_getPrintersAsync(): Promise<Electron.PrinterInfo[]>;
_init(): void;
_getNavigationEntryAtIndex(index: number): Electron.EntryAtIndex | null;
_getNavigationEntryAtIndex(index: number): Electron.NavigationEntry | null;
_getActiveIndex(): number;
_historyLength(): number;
_canGoBack(): boolean;
@ -97,6 +97,8 @@ declare namespace Electron {
_goForward(): void;
_goToOffset(index: number): void;
_goToIndex(index: number): void;
_removeNavigationEntryAtIndex(index: number): boolean;
_getHistory(): Electron.NavigationEntry[];
_clearHistory():void
canGoToIndex(index: number): boolean;
destroy(): void;