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 * `index` Integer
Returns `Object`: Returns [`NavigationEntry`](structures/navigation-entry.md) - Navigation entry at the given index.
* `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.
If index is out of bounds (greater than history length or less than 0), null will be returned. 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()` #### `navigationHistory.length()`
Returns `Integer` - History 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/mime-typed-buffer.md",
"docs/api/structures/mouse-input-event.md", "docs/api/structures/mouse-input-event.md",
"docs/api/structures/mouse-wheel-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-action.md",
"docs/api/structures/notification-response.md", "docs/api/structures/notification-response.md",
"docs/api/structures/open-external-permission-request.md", "docs/api/structures/open-external-permission-request.md",

View file

@ -595,7 +595,9 @@ WebContents.prototype._init = function () {
goToOffset: this._goToOffset.bind(this), goToOffset: this._goToOffset.bind(this),
getActiveIndex: this._getActiveIndex.bind(this), getActiveIndex: this._getActiveIndex.bind(this),
length: this._historyLength.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, writable: false,
enumerable: true enumerable: true

View file

@ -2496,6 +2496,31 @@ content::NavigationEntry* WebContents::GetNavigationEntryAtIndex(
return web_contents()->GetController().GetEntryAtIndex(index); 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() { void WebContents::ClearHistory() {
// In some rare cases (normally while there is no real history) we are in a // In some rare cases (normally while there is no real history) we are in a
// state where we can't prune navigation entries // state where we can't prune navigation entries
@ -4295,6 +4320,9 @@ void WebContents::FillObjectTemplate(v8::Isolate* isolate,
.SetMethod("_getNavigationEntryAtIndex", .SetMethod("_getNavigationEntryAtIndex",
&WebContents::GetNavigationEntryAtIndex) &WebContents::GetNavigationEntryAtIndex)
.SetMethod("_historyLength", &WebContents::GetHistoryLength) .SetMethod("_historyLength", &WebContents::GetHistoryLength)
.SetMethod("_removeNavigationEntryAtIndex",
&WebContents::RemoveNavigationEntryAtIndex)
.SetMethod("_getHistory", &WebContents::GetHistory)
.SetMethod("_clearHistory", &WebContents::ClearHistory) .SetMethod("_clearHistory", &WebContents::ClearHistory)
.SetMethod("isCrashed", &WebContents::IsCrashed) .SetMethod("isCrashed", &WebContents::IsCrashed)
.SetMethod("forcefullyCrashRenderer", .SetMethod("forcefullyCrashRenderer",

View file

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

View file

@ -567,6 +567,39 @@ describe('webContents module', () => {
w = new BrowserWindow({ show: false }); w = new BrowserWindow({ show: false });
}); });
afterEach(closeAllWindows); 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', () => { describe('navigationHistory.canGoBack and navigationHistory.goBack API', () => {
it('should not be able to go back if history is empty', async () => { it('should not be able to go back if history is empty', async () => {
expect(w.webContents.navigationHistory.canGoBack()).to.be.false(); expect(w.webContents.navigationHistory.canGoBack()).to.be.false();
@ -706,6 +739,24 @@ describe('webContents module', () => {
expect(w.webContents.navigationHistory.length()).to.equal(1); 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', () => { describe('getFocusedWebContents() API', () => {

View file

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