diff --git a/docs/api/navigation-history.md b/docs/api/navigation-history.md index 9b7c491e19..16d53ce37b 100644 --- a/docs/api/navigation-history.md +++ b/docs/api/navigation-history.md @@ -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. diff --git a/docs/api/structures/navigation-entry.md b/docs/api/structures/navigation-entry.md new file mode 100644 index 0000000000..6d1cd6734d --- /dev/null +++ b/docs/api/structures/navigation-entry.md @@ -0,0 +1,4 @@ +# NavigationEntry Object + +* `url` string +* `title` string diff --git a/filenames.auto.gni b/filenames.auto.gni index 574797c555..e4bbd4bd3c 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -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", diff --git a/lib/browser/api/web-contents.ts b/lib/browser/api/web-contents.ts index 0c5ff37d54..b69ec4a1d6 100644 --- a/lib/browser/api/web-contents.ts +++ b/lib/browser/api/web-contents.ts @@ -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 diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index 048d5e13a7..c64a7f31b5 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/shell/browser/api/electron_api_web_contents.cc @@ -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 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(); + + std::vector 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", diff --git a/shell/browser/api/electron_api_web_contents.h b/shell/browser/api/electron_api_web_contents.h index f1bd0a9e74..90fd58fb68 100644 --- a/shell/browser/api/electron_api_web_contents.h +++ b/shell/browser/api/electron_api_web_contents.h @@ -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 GetHistory() const; void ClearHistory(); int GetHistoryLength() const; const std::string GetWebRTCIPHandlingPolicy() const; diff --git a/spec/api-web-contents-spec.ts b/spec/api-web-contents-spec.ts index 9528b45521..5e699880b6 100644 --- a/spec/api-web-contents-spec.ts +++ b/spec/api-web-contents-spec.ts @@ -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', () => { diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index aad01e719c..3632b0118e 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -87,7 +87,7 @@ declare namespace Electron { _print(options: any, callback?: (success: boolean, failureReason: string) => void): void; _getPrintersAsync(): Promise; _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;