macOS: Add native tabs (PR #579)

Electron supports using native tabs on macOS (API added in Electron 1.8.1). This change adds a context menu item on platforms that support it (macOS for now) to open links in new tabs, and also adds support for {command,middle}-clicking links to open them in a new tab.

Maintainer (@ronjouch) note: this feature is macOS-only. Windows/Linux patches welcome 🙂.
This commit is contained in:
David Kramer 2018-05-01 16:24:35 -07:00 committed by Ronan Jouchet
parent 025936e9c5
commit ac99c6424d
4 changed files with 73 additions and 22 deletions

View File

@ -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;
},

View File

@ -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;

View File

@ -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,
};

View File

@ -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();