diff --git a/app/src/components/contextMenu/contextMenu.js b/app/src/components/contextMenu/contextMenu.js index 3f23572..6cc7758 100644 --- a/app/src/components/contextMenu/contextMenu.js +++ b/app/src/components/contextMenu/contextMenu.js @@ -1,7 +1,7 @@ import { shell } from 'electron'; import contextMenu from 'electron-context-menu'; -function initContextMenu(createNewWindow) { +function initContextMenu(createNewWindow, createNewTab) { contextMenu({ prepend: (params) => { const items = []; @@ -18,6 +18,14 @@ function initContextMenu(createNewWindow) { createNewWindow(params.linkURL); }, }); + if (createNewTab) { + items.push({ + label: 'Open Link in New Tab', + click: () => { + createNewTab(params.linkURL, false); + }, + }); + } } return items; }, diff --git a/app/src/components/mainWindow/mainWindow.js b/app/src/components/mainWindow/mainWindow.js index badb09e..fb42c2a 100644 --- a/app/src/components/mainWindow/mainWindow.js +++ b/app/src/components/mainWindow/mainWindow.js @@ -7,7 +7,7 @@ import createMenu from './../menu/menu'; import initContextMenu from './../contextMenu/contextMenu'; const { - isOSX, linkIsInternal, getCssToInject, shouldInjectCss, getAppIcon, + isOSX, linkIsInternal, getCssToInject, shouldInjectCss, getAppIcon, nativeTabsSupported, } = helpers; const ZOOM_INTERVAL = 0.1; @@ -66,6 +66,7 @@ function createMainWindow(inpOptions, onAppQuit, setDockBadge) { const DEFAULT_WINDOW_OPTIONS = { // Convert dashes to spaces because on linux the app name is joined with dashes title: options.name, + tabbingIdentifier: nativeTabsSupported() ? options.name : undefined, webPreferences: { javascript: true, plugins: true, @@ -105,18 +106,29 @@ function createMainWindow(inpOptions, onAppQuit, setDockBadge) { fs.writeFileSync(path.join(__dirname, '..', 'nativefier.json'), JSON.stringify(options)); } + const withFocusedWindow = (block) => { + const focusedWindow = BrowserWindow.getFocusedWindow(); + if (focusedWindow) { block(focusedWindow); } + }; + const adjustWindowZoom = (window, adjustment) => { window.webContents.getZoomFactor((zoomFactor) => { window.webContents.setZoomFactor(zoomFactor + adjustment); }); }; - const onZoomIn = () => adjustWindowZoom(mainWindow, ZOOM_INTERVAL); + const onZoomIn = () => { + withFocusedWindow(focusedWindow => adjustWindowZoom(focusedWindow, ZOOM_INTERVAL)); + }; - const onZoomOut = () => adjustWindowZoom(mainWindow, -ZOOM_INTERVAL); + const onZoomOut = () => { + withFocusedWindow(focusedWindow => adjustWindowZoom(focusedWindow, -ZOOM_INTERVAL)); + }; const onZoomReset = () => { - mainWindow.webContents.setZoomFactor(options.zoom); + withFocusedWindow((focusedWindow) => { + focusedWindow.webContents.setZoomFactor(options.zoom); + }); }; const clearAppData = () => { @@ -140,24 +152,48 @@ function createMainWindow(inpOptions, onAppQuit, setDockBadge) { }; const onGoBack = () => { - mainWindow.webContents.goBack(); + withFocusedWindow((focusedWindow) => { + focusedWindow.webContents.goBack(); + }); }; const onGoForward = () => { - mainWindow.webContents.goForward(); + withFocusedWindow((focusedWindow) => { + focusedWindow.webContents.goForward(); + }); }; - const getCurrentUrl = () => mainWindow.webContents.getURL(); + const getCurrentUrl = () => { + withFocusedWindow((focusedWindow) => { + focusedWindow.webContents.getURL(); + }); + }; let createNewWindow; - const onNewWindow = (event, urlToGo) => { - if (mainWindow.useDefaultWindowBehaviour) { - mainWindow.useDefaultWindowBehaviour = false; - return; - } + const createNewTab = (url, foreground) => { + withFocusedWindow((focusedWindow) => { + const newTab = createNewWindow(url); + focusedWindow.addTabbedWindow(newTab); + if (!foreground) { + focusedWindow.focus(); + } + return newTab; + }); + return undefined; + }; + const onNewWindow = (event, urlToGo, _, disposition) => { event.preventDefault(); + if (nativeTabsSupported()) { + if (disposition === 'background-tab') { + createNewTab(urlToGo, false); + return; + } else if (disposition === 'foreground-tab') { + createNewTab(urlToGo, true); + return; + } + } if (!linkIsInternal(options.targetUrl, urlToGo, options.internalUrls)) { shell.openExternal(urlToGo); return; @@ -200,7 +236,7 @@ function createMainWindow(inpOptions, onAppQuit, setDockBadge) { createMenu(menuOptions); if (!options.disableContextMenu) { - initContextMenu(createNewWindow); + initContextMenu(createNewWindow, nativeTabsSupported() ? createNewTab : undefined); } if (options.userAgent) { @@ -233,10 +269,16 @@ function createMainWindow(inpOptions, onAppQuit, setDockBadge) { } mainWindow.webContents.on('new-window', onNewWindow); + mainWindow.loadURL(options.targetUrl); + mainWindow.on('new-tab', () => createNewTab(options.targetUrl, true)); + mainWindow.on('close', (event) => { if (mainWindow.isFullScreen()) { + if (nativeTabsSupported()) { + mainWindow.moveTabToNewWindow(); + } mainWindow.setFullScreen(false); mainWindow.once('leave-full-screen', maybeHideWindow.bind(this, mainWindow, event, options.fastQuit)); } @@ -246,12 +288,4 @@ function createMainWindow(inpOptions, onAppQuit, setDockBadge) { return mainWindow; } -ipcMain.on('cancelNewWindowOverride', () => { - const allWindows = BrowserWindow.getAllWindows(); - allWindows.forEach((window) => { - // eslint-disable-next-line no-param-reassign - window.useDefaultWindowBehaviour = false; - }); -}); - export default createMainWindow; diff --git a/app/src/helpers/helpers.js b/app/src/helpers/helpers.js index 5ce9164..2e8cb4b 100644 --- a/app/src/helpers/helpers.js +++ b/app/src/helpers/helpers.js @@ -59,6 +59,10 @@ function getAppIcon() { return path.join(__dirname, '../', `/icon.${isWindows() ? 'ico' : 'png'}`); } +function nativeTabsSupported() { + return isOSX(); +} + export default { isOSX, isLinux, @@ -68,4 +72,5 @@ export default { debugLog, shouldInjectCss, getAppIcon, + nativeTabsSupported, }; diff --git a/app/src/main.js b/app/src/main.js index 9edeff1..bbc136a 100644 --- a/app/src/main.js +++ b/app/src/main.js @@ -118,6 +118,10 @@ app.on('ready', () => { createTrayIcon(appArgs, mainWindow); }); +app.on('new-window-for-tab', () => { + mainWindow.emit('new-tab'); +}); + app.on('login', (event, webContents, request, authInfo, callback) => { // for http authentication event.preventDefault();