diff --git a/js/tabState/tab.js b/js/tabState/tab.js index 16bfb6049..bbde289a5 100644 --- a/js/tabState/tab.js +++ b/js/tabState/tab.js @@ -6,7 +6,7 @@ class TabList { //tab properties that shouldn't be saved to disk - static temporaryProperties = ['hasAudio', 'previewImage', 'loaded', 'hasBrowserView'] + static temporaryProperties = ['hasAudio', 'previewImage', 'loaded', 'hasWebContents'] add (tab = {}, options = {}, emit=true) { var tabId = String(tab.id || Math.round(Math.random() * 100000000000000000)) // you can pass an id that will be used, or a random one will be generated. @@ -28,7 +28,7 @@ class TabList { hasAudio: false, previewImage: '', isFileView: false, - hasBrowserView: false, + hasWebContents: false, } if (options.atEnd) { diff --git a/js/util/settings/settingsMain.js b/js/util/settings/settingsMain.js index b67f50b46..0ffa8e595 100644 --- a/js/util/settings/settingsMain.js +++ b/js/util/settings/settingsMain.js @@ -63,7 +63,7 @@ var settings = { settings.runChangeCallbacks(key) windows.getAll().forEach(function (win) { - win.webContents.send('settingChanged', key, value) + getWindowWebContents(win).send('settingChanged', key, value) }) }, initialize: function (userDataPath) { @@ -86,8 +86,8 @@ var settings = { settings.runChangeCallbacks(key) windows.getAll().forEach(function (win) { - if (win.webContents.id !== e.sender.id) { - win.webContents.send('settingChanged', key, value) + if (getWindowWebContents(win).id !== e.sender.id) { + getWindowWebContents(win).send('settingChanged', key, value) } }) }) diff --git a/js/webviews.js b/js/webviews.js index 835f71624..df535a2ab 100644 --- a/js/webviews.js +++ b/js/webviews.js @@ -109,7 +109,7 @@ const webviews = { events: [], IPCEvents: [], hasViewForTab: function(tabId) { - return tabId && tasks.getTaskContainingTab(tabId) && tasks.getTaskContainingTab(tabId).tabs.get(tabId).hasBrowserView + return tabId && tasks.getTaskContainingTab(tabId) && tasks.getTaskContainingTab(tabId).tabs.get(tabId).hasWebContents }, bindEvent: function (event, fn) { webviews.events.push({ @@ -214,7 +214,7 @@ const webviews = { } tasks.getTaskContainingTab(tabId).tabs.update(tabId, { - hasBrowserView: true + hasWebContents: true }) }, setSelected: function (id, options) { // options.focus - whether to focus the view. Defaults to true. @@ -248,7 +248,7 @@ const webviews = { if (webviews.hasViewForTab(id)) { tasks.getTaskContainingTab(id).tabs.update(id, { - hasBrowserView: false + hasWebContents: false }) } //we may be destroying a view for which the tab object no longer exists, so this message should be sent unconditionally diff --git a/main/download.js b/main/download.js index fc4fe1bfa..0400bbb5a 100644 --- a/main/download.js +++ b/main/download.js @@ -11,11 +11,7 @@ function isAttachment (header) { } function downloadHandler (event, item, webContents) { - const sourceView = Object.values(viewMap).find(view => view.webContents.id === webContents.id) - let sourceWindow - if (sourceView) { - sourceWindow = BrowserWindow.fromBrowserView(sourceView) - } + let sourceWindow = windows.windowFromContents(webContents)?.win if (!sourceWindow) { sourceWindow = windows.getCurrent() } @@ -64,10 +60,7 @@ function listenForDownloadHeaders (ses) { if (details.resourceType === 'mainFrame' && details.responseHeaders) { let sourceWindow if (details.webContents) { - const sourceView = Object.values(viewMap).find(view => view.webContents.id === details.webContents.id) - if (sourceView) { - sourceWindow = BrowserWindow.fromBrowserView(sourceView) - } + sourceWindow = windows.windowFromContents(details.webContents)?.win } if (!sourceWindow) { sourceWindow = windows.getCurrent() diff --git a/main/main.js b/main/main.js index e651fb387..dfdc1c046 100644 --- a/main/main.js +++ b/main/main.js @@ -5,7 +5,8 @@ const path = require('path') const { app, // Module to control application life. protocol, // Module to control protocol handling - BrowserWindow, // Module to create native browser window. + BaseWindow, // Module to create native browser window. + BrowserWindow, webContents, session, ipcMain: ipc, @@ -14,7 +15,8 @@ const { dialog, nativeTheme, shell, - net + net, + WebContentsView } = electron crashReporter.start({ @@ -98,24 +100,24 @@ function sendIPCToWindow (window, action, data) { return } - if (window && window.webContents && window.webContents.isLoadingMainFrame()) { + if (window && getWindowWebContents(window).isLoadingMainFrame()) { // immediately after a did-finish-load event, isLoading can still be true, // so wait a bit to confirm that the page is really loading setTimeout(function() { - if (window.webContents.isLoadingMainFrame()) { - window.webContents.once('did-finish-load', function () { - window.webContents.send(action, data || {}) + if (getWindowWebContents(window).isLoadingMainFrame()) { + getWindowWebContents(window).once('did-finish-load', function () { + getWindowWebContents(window).send(action, data || {}) }) } else { - window.webContents.send(action, data || {}) + getWindowWebContents(window).send(action, data || {}) } }, 0) } else if (window) { - window.webContents.send(action, data || {}) + getWindowWebContents(window).send(action, data || {}) } else { var window = createWindow() - window.webContents.once('did-finish-load', function () { - window.webContents.send(action, data || {}) + getWindowWebContents(window).once('did-finish-load', function () { + getWindowWebContents(window).send(action, data || {}) }) } } @@ -188,7 +190,7 @@ function createWindow (customArgs = {}) { } function createWindowWithBounds (bounds, customArgs) { - const newWin = new BrowserWindow({ + const newWin = new BaseWindow({ width: bounds.width, height: bounds.height, x: bounds.x, @@ -201,6 +203,15 @@ function createWindowWithBounds (bounds, customArgs) { frame: settings.get('useSeparateTitlebar'), alwaysOnTop: settings.get('windowAlwaysOnTop'), backgroundColor: '#fff', // the value of this is ignored, but setting it seems to work around https://github.com/electron/electron/issues/10559 + }) + + // windows and linux always use a menu button in the upper-left corner instead + // if frame: false is set, this won't have any effect, but it does apply on Linux if "use separate titlebar" is enabled + if (process.platform !== 'darwin') { + newWin.setMenuBarVisibility(false) + } + + const mainView = new WebContentsView({ webPreferences: { nodeIntegration: true, contextIsolation: false, @@ -217,20 +228,19 @@ function createWindowWithBounds (bounds, customArgs) { ] } }) + mainView.webContents.loadURL(browserPage) + mainView.setBounds({x: 0, y: 0, width: bounds.width, height: bounds.height}) + newWin.contentView.addChildView(mainView) - // windows and linux always use a menu button in the upper-left corner instead - // if frame: false is set, this won't have any effect, but it does apply on Linux if "use separate titlebar" is enabled - if (process.platform !== 'darwin') { - newWin.setMenuBarVisibility(false) - } - - // and load the index.html of the app. - newWin.loadURL(browserPage) + newWin.on('resize', function() { + const winBounds = newWin.getBounds() + mainView.setBounds({x: 0, y: 0, width: winBounds.width, height: winBounds.height}) + }) if (bounds.maximized) { newWin.maximize() - newWin.webContents.once('did-finish-load', function () { + mainView.webContents.once('did-finish-load', function () { sendIPCToWindow(newWin, 'maximize') }) } @@ -269,7 +279,7 @@ function createWindowWithBounds (bounds, customArgs) { newWin.on('blur', function () { // if the devtools for this window are focused, this check will be false, and we keep the focused class on the window - if (BrowserWindow.getFocusedWindow() !== newWin) { + if (BaseWindow.getFocusedWindow() !== newWin) { sendIPCToWindow(newWin, 'blur') } }) @@ -311,7 +321,7 @@ function createWindowWithBounds (bounds, customArgs) { } // prevent remote pages from being loaded using drag-and-drop, since they would have node access - newWin.webContents.on('will-navigate', function (e, url) { + mainView.webContents.on('will-navigate', function (e, url) { if (url !== browserPage) { e.preventDefault() } @@ -340,7 +350,7 @@ app.on('ready', function () { const newWin = createWindow() - newWin.webContents.on('did-finish-load', function () { + getWindowWebContents(newWin).on('did-finish-load', function () { // if a URL was passed as a command line argument (probably because Min is set as the default browser on Linux), open it. handleCommandLineArguments(process.argv) @@ -403,8 +413,7 @@ app.on('activate', function (/* e, hasVisibleWindows */) { }) ipc.on('focusMainWebContents', function () { - //TODO fix - windows.getCurrent().webContents.focus() + getWindowWebContents(windows.getCurrent()).focus() }) ipc.on('showSecondaryMenu', function (event, data) { @@ -430,10 +439,15 @@ ipc.on('quit', function () { }) ipc.on('tab-state-change', function(e, events) { + const sourceWindowId = windows.windowFromContents(e.sender)?.id + if (!sourceWindowId) { + console.warn('warning: received tab state update from window after destruction, ignoring') + return + } windows.getAll().forEach(function(window) { - if (window.webContents.id !== e.sender.id) { - window.webContents.send('tab-state-change-receive', { - sourceWindowId: windows.windowFromContents(e.sender).id, + if (getWindowWebContents(window).id !== e.sender.id) { + getWindowWebContents(window).send('tab-state-change-receive', { + sourceWindowId, events }) } @@ -441,14 +455,14 @@ ipc.on('tab-state-change', function(e, events) { }) ipc.on('request-tab-state', function(e) { - const otherWindow = windows.getAll().find(w => w.webContents.id !== e.sender.id) + const otherWindow = windows.getAll().find(w => getWindowWebContents(w).id !== e.sender.id) if (!otherWindow) { throw new Error('secondary window doesn\'t exist as source for tab state') } ipc.once('return-tab-state', function(e2, data) { e.returnValue = data }) - otherWindow.webContents.send('read-tab-state') + getWindowWebContents(otherWindow).send('read-tab-state') }) /* places service */ @@ -472,4 +486,8 @@ app.once('ready', function() { ipc.on('places-connect', function (e) { placesWindow.webContents.postMessage('places-connect', null, e.ports) -}) \ No newline at end of file +}) + +function getWindowWebContents (win) { + return win.getContentView().children[0].webContents +} \ No newline at end of file diff --git a/main/menu.js b/main/menu.js index f48d3449e..4e5e3a6c5 100644 --- a/main/menu.js +++ b/main/menu.js @@ -337,7 +337,7 @@ function buildAppMenu (options = {}) { if (process.platform === 'darwin') { return 'Shift+Cmd+Alt+I' } else { return 'Ctrl+Shift+Alt+I' } })(), click: function (item, focusedWindow) { - if (focusedWindow) focusedWindow.toggleDevTools() + if (focusedWindow) getWindowWebContents(focusedWindow).toggleDevTools() } }, { diff --git a/main/viewManager.js b/main/viewManager.js index 7acfaa3bf..a971d997a 100644 --- a/main/viewManager.js +++ b/main/viewManager.js @@ -1,5 +1,3 @@ -const BrowserView = electron.BrowserView - var viewMap = {} // id: view var viewStateMap = {} // id: view state @@ -52,21 +50,21 @@ function createView (existingViewId, id, webPreferences, boundsString, events) { view.setBackgroundColor('#fff') viewStateMap[id].loadedInitialURL = true } else { - view = new BrowserView({ webPreferences: viewPrefs }) + view = new WebContentsView({ webPreferences: viewPrefs }) } events.forEach(function (event) { view.webContents.on(event, function (e) { var args = Array.prototype.slice.call(arguments).slice(1) - const eventTarget = BrowserWindow.fromBrowserView(view) || windows.getCurrent() + const eventTarget = getWindowFromViewContents(view) || windows.getCurrent() if (!eventTarget) { //this can happen during shutdown - windows can be destroyed before the corresponding views, and the view can emit an event during that time return } - eventTarget.webContents.send('view-event', { + getWindowWebContents(eventTarget).send('view-event', { tabId: id, event: event, args: args @@ -80,6 +78,12 @@ function createView (existingViewId, id, webPreferences, boundsString, events) { }) view.webContents.setWindowOpenHandler(function (details) { + if (details.url && !filterPopups(details.url)) { + return { + action: 'deny' + } + } + /* Opening a popup with window.open() generally requires features to be set So if there are no features, the event is most likely from clicking on a link, which should open a new tab. @@ -88,9 +92,9 @@ function createView (existingViewId, id, webPreferences, boundsString, events) { (https://github.com/minbrowser/min/issues/1835) */ if (!details.features) { - const eventTarget = BrowserWindow.fromBrowserView(view) || windows.getCurrent() + const eventTarget = getWindowFromViewContents(view) || windows.getCurrent() - eventTarget.webContents.send('view-event', { + getWindowWebContents(eventTarget).send('view-event', { tabId: id, event: 'new-tab', args: [details.url, !(details.disposition === 'background-tab')] @@ -101,29 +105,24 @@ function createView (existingViewId, id, webPreferences, boundsString, events) { } return { - action: 'allow' - } - }) + action: 'allow', + createWindow: function (options) { + const view = new WebContentsView({ webPreferences: getDefaultViewWebPreferences(), webContents: options.webContents }) - view.webContents.removeAllListeners('-add-new-contents') + var popupId = Math.random().toString() + temporaryPopupViews[popupId] = view - view.webContents.on('-add-new-contents', function (e, webContents, disposition, _userGesture, _left, _top, _width, _height, url, frameName, referrer, rawFeatures, postData) { - if (!filterPopups(url)) { - return - } + const eventTarget = getWindowFromViewContents(view) || windows.getCurrent() - var view = new BrowserView({ webPreferences: getDefaultViewWebPreferences(), webContents: webContents }) + getWindowWebContents(eventTarget).send('view-event', { + tabId: id, + event: 'did-create-popup', + args: [popupId, details.url] + }) - var popupId = Math.random().toString() - temporaryPopupViews[popupId] = view - - const eventTarget = BrowserWindow.fromBrowserView(view) || windows.getCurrent() - - eventTarget.webContents.send('view-event', { - tabId: id, - event: 'did-create-popup', - args: [popupId, url] - }) + return view.webContents + } + } }) view.webContents.on('ipc-message', function (e, channel, data) { @@ -136,14 +135,14 @@ function createView (existingViewId, id, webPreferences, boundsString, events) { return } - const eventTarget = BrowserWindow.fromBrowserView(view) || windows.getCurrent() + const eventTarget = getWindowFromViewContents(view) || windows.getCurrent() if (!eventTarget) { //this can happen during shutdown - windows can be destroyed before the corresponding views, and the view can emit an event during that time return } - eventTarget.webContents.send('view-ipc', { + getWindowWebContents(eventTarget).send('view-ipc', { id: id, name: channel, data: data, @@ -221,13 +220,13 @@ function createView (existingViewId, id, webPreferences, boundsString, events) { if (hasJS !== shouldHaveJS) { setTimeout(function () { view.webContents.stop() - const currentWindow = BrowserWindow.fromBrowserView(view) + const currentWindow = getWindowFromViewContents(view) destroyView(id) const newView = createView(existingViewId, id, Object.assign({}, webPreferences, { javascript: shouldHaveJS }), boundsString, events) loadURLInView(id, event.url, currentWindow) if (currentWindow) { - setView(id, currentWindow.webContents) + setView(id, getWindowWebContents(currentWindow)) focusView(id) } }, 0) @@ -248,9 +247,8 @@ function destroyView (id) { } windows.getAll().forEach(function (window) { - if (viewMap[id] === window.getBrowserView()) { - window.setBrowserView(null) - // TODO fix + if (windows.getState(window).selectedView === id) { + window.getContentView().removeChildView(viewMap[id]) windows.getState(window).selectedView = null } }) @@ -269,13 +267,15 @@ function destroyAllViews () { function setView (id, senderContents) { const win = windows.windowFromContents(senderContents).win - // setBrowserView causes flickering, so we only want to call it if the view is actually changing + // changing views can cause flickering, so we only want to call it if the view is actually changing // see https://github.com/minbrowser/min/issues/1966 - if (win.getBrowserView() !== viewMap[id]) { + if (windows.getState(win).selectedView !== viewMap[id]) { + //remove all prior views + win.getContentView().children.slice(1).forEach(child => win.getContentView().removeChildView(child)) if (viewStateMap[id].loadedInitialURL) { - win.setBrowserView(viewMap[id]) + win.getContentView().addChildView(viewMap[id]) } else { - win.setBrowserView(null) + win.getContentView().removeChildView(viewMap[id]) } windows.getState(win).selectedView = id } @@ -293,19 +293,21 @@ function focusView (id) { if (viewMap[id] && (viewMap[id].webContents.getURL() !== '' || viewMap[id].webContents.isLoading())) { viewMap[id].webContents.focus() return true - } else if (BrowserWindow.fromBrowserView(viewMap[id])) { - BrowserWindow.fromBrowserView(viewMap[id]).webContents.focus() + } else if (getWindowFromViewContents(viewMap[id])) { + getWindowWebContents(getWindowFromViewContents(viewMap[id])).focus() return true } } function hideCurrentView (senderContents) { const win = windows.windowFromContents(senderContents).win - - win.setBrowserView(null) - windows.getState(win).selectedView = null - if (win.isFocused()) { - win.webContents.focus() + const currentId = windows.getState(win).selectedView + if (currentId) { + win.getContentView().removeChildView(viewMap[currentId]) + windows.getState(win).selectedView = null + if (win.isFocused()) { + getWindowWebContents(win).focus() + } } } @@ -321,6 +323,11 @@ function getTabIDFromWebContents (contents) { } } +function getWindowFromViewContents (webContents) { + const viewId = Object.keys(viewMap).find(id => viewMap[id].webContents === webContents) + return windows.getAll().find(win => windows.getState(win).selectedView === viewId) +} + ipc.on('createView', function (e, args) { createView(args.existingViewId, args.id, args.webPreferences, args.boundsString, args.events) }) @@ -365,7 +372,7 @@ function loadURLInView (id, url, win) { }) // If the view has no URL, it won't be attached yet if (win && id === windows.getState(win).selectedView) { - win.setBrowserView(viewMap[id]) + win.getContentView().addChildView(viewMap[id]) } } viewMap[id].webContents.loadURL(url) diff --git a/main/windowManagement.js b/main/windowManagement.js index 37e2dda25..6f99c2318 100644 --- a/main/windowManagement.js +++ b/main/windowManagement.js @@ -3,7 +3,7 @@ const windows = { hasEverCreatedWindow: false, nextId: 1, windowFromContents: function (webContents) { - return windows.openWindows.find(w => w.win.webContents.id === webContents.id) + return windows.openWindows.find(w => getWindowWebContents(w.win).id === webContents.id) }, addWindow: function (window) { windows.hasEverCreatedWindow = true @@ -19,9 +19,9 @@ const windows = { }) window.on('close', function() { - //if the BrowserView is still attached to the window on close, Electron will destroy it automatically, but we want to manage it ourselves - window.setBrowserView(null) - windows.openWindows.find(w => w.win === window).closed = true; + // detach WebContentsViews to ensure they aren't destroyed when the window is closed + window.getContentView().children.slice(1).forEach(child => window.getContentView().removeChildView(child)) + windows.openWindows.find(w => w.win === window).closed = true }) window.on('closed', function() { @@ -38,7 +38,7 @@ const windows = { removeWindow: function (window) { windows.openWindows.splice(windows.openWindows.findIndex(w => w.win === window), 1) - //unload BrowserViews when all windows are closed + //unload WebContentsViews when all windows are closed if (windows.openWindows.length === 0) { destroyAllViews() } diff --git a/package.json b/package.json index c87485daf..f8a42181c 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "author": "PalmerAL", "version": "1.33.1", "description": "A fast, minimal browser that protects your privacy", - "electronVersion": "32.1.0", + "electronVersion": "33.3.0", "main": "main.build.js", "standard": { "globals": [ @@ -49,7 +49,7 @@ "browserify": "^16.5.1", "concurrently": "^5.2.0", "decomment": "^0.9.0", - "electron": "32.1.0", + "electron": "33.3.0", "electron-builder": "^24.13.3", "electron-installer-windows": "^3.0.0", "electron-renderify": "0.0.2",