diff --git a/docs/README.md b/docs/README.md index 8151cf46a8da..1a00997dd9f6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -37,6 +37,7 @@ an issue: * [Offline/Online Detection](tutorial/online-offline-events.md) * [Represented File for macOS BrowserWindows](tutorial/represented-file.md) * [Native File Drag & Drop](tutorial/native-file-drag-drop.md) + * [Navigation History](tutorial/navigation-history.md) * [Offscreen Rendering](tutorial/offscreen-rendering.md) * [Dark Mode](tutorial/dark-mode.md) * [Web embeds in Electron](tutorial/web-embeds.md) diff --git a/docs/fiddles/features/navigation-history/index.html b/docs/fiddles/features/navigation-history/index.html new file mode 100644 index 000000000000..584e04a26389 --- /dev/null +++ b/docs/fiddles/features/navigation-history/index.html @@ -0,0 +1,32 @@ + + + + + Enhanced Browser with Navigation History + + + + + +
+ + + + + + +
+ +
+ +
+

Navigation History Demo

+

This demo showcases Electron's NavigationHistory API functionality.

+

Back/Forward: Navigate through your browsing history.

+

Back History/Forward History: View and select from your browsing history.

+

URL Bar: Enter a URL and click 'Go' or press Enter to navigate.

+
+ + + + diff --git a/docs/fiddles/features/navigation-history/main.js b/docs/fiddles/features/navigation-history/main.js new file mode 100644 index 000000000000..61f240f29fc6 --- /dev/null +++ b/docs/fiddles/features/navigation-history/main.js @@ -0,0 +1,58 @@ +const { app, BrowserWindow, BrowserView, ipcMain } = require('electron') +const path = require('path') + +function createWindow () { + const mainWindow = new BrowserWindow({ + width: 1000, + height: 800, + webPreferences: { + preload: path.join(__dirname, 'preload.js'), + nodeIntegration: false, + contextIsolation: true + } + }) + + mainWindow.loadFile('index.html') + + const view = new BrowserView() + mainWindow.setBrowserView(view) + view.setBounds({ x: 0, y: 100, width: 1000, height: 800 }) + view.setAutoResize({ width: true, height: true }) + + const navigationHistory = view.webContents.navigationHistory + ipcMain.handle('nav:back', () => + navigationHistory.goBack() + ) + + ipcMain.handle('nav:forward', () => { + navigationHistory.goForward() + }) + + ipcMain.handle('nav:canGoBack', () => navigationHistory.canGoBack()) + ipcMain.handle('nav:canGoForward', () => navigationHistory.canGoForward()) + ipcMain.handle('nav:loadURL', (_, url) => + view.webContents.loadURL(url) + ) + ipcMain.handle('nav:getCurrentURL', () => view.webContents.getURL()) + ipcMain.handle('nav:getHistory', () => { + return navigationHistory.getAllEntries() + }) + + view.webContents.on('did-navigate', () => { + mainWindow.webContents.send('nav:updated') + }) + + view.webContents.on('did-navigate-in-page', () => { + mainWindow.webContents.send('nav:updated') + }) +} + +app.whenReady().then(createWindow) + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') app.quit() +}) + +app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) createWindow() +}) diff --git a/docs/fiddles/features/navigation-history/preload.js b/docs/fiddles/features/navigation-history/preload.js new file mode 100644 index 000000000000..6d5c38976cc9 --- /dev/null +++ b/docs/fiddles/features/navigation-history/preload.js @@ -0,0 +1,12 @@ +const { contextBridge, ipcRenderer } = require('electron') + +contextBridge.exposeInMainWorld('electronAPI', { + goBack: () => ipcRenderer.invoke('nav:back'), + goForward: () => ipcRenderer.invoke('nav:forward'), + canGoBack: () => ipcRenderer.invoke('nav:canGoBack'), + canGoForward: () => ipcRenderer.invoke('nav:canGoForward'), + loadURL: (url) => ipcRenderer.invoke('nav:loadURL', url), + getCurrentURL: () => ipcRenderer.invoke('nav:getCurrentURL'), + getHistory: () => ipcRenderer.invoke('nav:getHistory'), + onNavigationUpdate: (callback) => ipcRenderer.on('nav:updated', callback) +}) diff --git a/docs/fiddles/features/navigation-history/renderer.js b/docs/fiddles/features/navigation-history/renderer.js new file mode 100644 index 000000000000..741cc8093307 --- /dev/null +++ b/docs/fiddles/features/navigation-history/renderer.js @@ -0,0 +1,84 @@ +const backBtn = document.getElementById('backBtn') +const forwardBtn = document.getElementById('forwardBtn') +const backHistoryBtn = document.getElementById('backHistoryBtn') +const forwardHistoryBtn = document.getElementById('forwardHistoryBtn') +const urlInput = document.getElementById('urlInput') +const goBtn = document.getElementById('goBtn') +const historyPanel = document.getElementById('historyPanel') + +async function updateButtons () { + const canGoBack = await window.electronAPI.canGoBack() + const canGoForward = await window.electronAPI.canGoForward() + backBtn.disabled = !canGoBack + backHistoryBtn.disabled = !canGoBack + + forwardBtn.disabled = !canGoForward + forwardHistoryBtn.disabled = !canGoForward +} + +async function updateURL () { + urlInput.value = await window.electronAPI.getCurrentURL() +} + +function transformURL (url) { + if (!url.startsWith('http://') && !url.startsWith('https://')) { + const updatedUrl = 'https://' + url + return updatedUrl + } + return url +} + +async function navigate (url) { + const urlInput = transformURL(url) + + await window.electronAPI.loadURL(urlInput) +} + +async function showHistory (forward = false) { + const history = await window.electronAPI.getHistory() + const currentIndex = history.findIndex(entry => entry.url === transformURL(urlInput.value)) + + if (!currentIndex) { + return + } + + const relevantHistory = forward + ? history.slice(currentIndex + 1) + : history.slice(0, currentIndex).reverse() + + historyPanel.innerHTML = '' + relevantHistory.forEach(entry => { + const div = document.createElement('div') + div.textContent = `Title: ${entry.title}, URL: ${entry.url}` + div.onclick = () => navigate(entry.url) + historyPanel.appendChild(div) + }) + + historyPanel.style.display = 'block' +} + +backBtn.addEventListener('click', () => window.electronAPI.goBack()) +forwardBtn.addEventListener('click', () => window.electronAPI.goForward()) +backHistoryBtn.addEventListener('click', () => showHistory(false)) +forwardHistoryBtn.addEventListener('click', () => showHistory(true)) +goBtn.addEventListener('click', () => navigate(urlInput.value)) + +urlInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + navigate(urlInput.value) + } +}) + +document.addEventListener('click', (e) => { + if (e.target !== historyPanel && !historyPanel.contains(e.target) && + e.target !== backHistoryBtn && e.target !== forwardHistoryBtn) { + historyPanel.style.display = 'none' + } +}) + +window.electronAPI.onNavigationUpdate(() => { + updateButtons() + updateURL() +}) + +updateButtons() diff --git a/docs/fiddles/features/navigation-history/style.css b/docs/fiddles/features/navigation-history/style.css new file mode 100644 index 000000000000..955e8482eaea --- /dev/null +++ b/docs/fiddles/features/navigation-history/style.css @@ -0,0 +1,58 @@ +body { + margin: 0; + font-family: Arial, sans-serif; + background-color: #f0f0f0; +} +#controls { + display: flex; + align-items: center; + padding: 10px; + background-color: #ffffff; + border-bottom: 1px solid #ccc; +} +button { + margin-right: 10px; + padding: 8px 12px; + font-size: 14px; + background-color: #4CAF50; + color: white; + border: none; + cursor: pointer; + transition: background-color 0.3s; +} +button:hover { + background-color: #45a049; +} +button:disabled { + background-color: #cccccc; + cursor: not-allowed; +} +#urlInput { + flex-grow: 1; + margin: 0 10px; + padding: 8px; + font-size: 14px; +} + +#historyPanel { + display: none; + position: absolute; + top: 60px; + left: 10px; + background: white; + border: 1px solid #ccc; + padding: 10px; + max-height: 300px; + overflow-y: auto; + z-index: 1000; +} + #historyPanel div { + cursor: pointer; + padding: 5px; +} + +#description { + background-color: #f0f0f0; + padding: 10px; + margin-top: 150px; +} diff --git a/docs/tutorial/navigation-history.md b/docs/tutorial/navigation-history.md new file mode 100644 index 000000000000..1c8f32cb7582 --- /dev/null +++ b/docs/tutorial/navigation-history.md @@ -0,0 +1,76 @@ +--- +title: "Navigation History" +description: "The NavigationHistory API allows you to manage and interact with the browsing history of your Electron application." +slug: navigation-history +hide_title: false +--- + +# Navigation History + +## Overview + +The [NavigationHistory](../api/navigation-history.md) class allows you to manage and interact with the browsing history of your Electron application. This powerful feature enables you to create intuitive navigation experiences for your users. + +## Accessing NavigationHistory + +Navigation history is stored per [`WebContents`](../api/web-contents.md) instance. To access a specific instance of the NavigationHistory class, use the WebContents class's [`contents.navigationHistory` instance property](https://www.electronjs.org/docs/latest/api/web-contents#contentsnavigationhistory-readonly). + +```js +const { BrowserWindow } = require('electron') + +const mainWindow = new BrowserWindow() +const { navigationHistory } = mainWindow.webContents +``` + +## Navigating through history + +Easily implement back and forward navigation: + +```js @ts-type={navigationHistory:Electron.NavigationHistory} +// Go back +if (navigationHistory.canGoBack()) { + navigationHistory.goBack() +} + +// Go forward +if (navigationHistory.canGoForward()) { + navigationHistory.goForward() +} +``` + +## Accessing history entries + +Retrieve and display the user's browsing history: + +```js @ts-type={navigationHistory:Electron.NavigationHistory} +const entries = navigationHistory.getAllEntries() + +entries.forEach((entry) => { + console.log(`${entry.title}: ${entry.url}`) +}) +``` + +Each navigation entry corresponds to a specific page. The indexing system follows a sequential order: + +- Index 0: Represents the earliest visited page. +- Index N: Represents the most recent page visited. + +## Navigating to specific entries + +Allow users to jump to any point in their browsing history: + +```js @ts-type={navigationHistory:Electron.NavigationHistory} +// Navigate to the 5th entry in the history, if the index is valid +navigationHistory.goToIndex(4) + +// Navigate to the 2nd entry forward from the current position +if (navigationHistory.canGoToOffset(2)) { + navigationHistory.goToOffset(2) +} +``` + +Here's a full example that you can open with Electron Fiddle: + +```fiddle docs/fiddles/features/navigation-history + +```