From 59c5d663f10504f81523ac6e01a9891457bb3346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B5=A9=E8=BF=9C?= Date: Tue, 30 Jun 2020 19:15:37 +0800 Subject: [PATCH] enable context isolation --- .vscode/launch.json | 19 ++++++++++++ dist/article/article.html | 4 +-- dist/article/article.js | 24 +++++++++------- dist/article/preload.js | 6 ++-- src/bridges/settings.ts | 7 +++++ src/bridges/utils.ts | 7 +++++ src/components/article.tsx | 43 ++++++++++------------------ src/containers/article-container.tsx | 5 ++-- src/electron.ts | 12 ++++++++ src/main/settings.ts | 8 ++++++ src/main/utils.ts | 14 +++++++-- src/main/window.ts | 4 +-- src/preload.ts | 4 +-- src/scripts/settings.ts | 6 +--- webpack.config.js | 6 +++- 15 files changed, 111 insertions(+), 58 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..9c7fedb --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Main Process", + "type": "node", + "request": "launch", + "cwd": "${workspaceRoot}", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", + "windows": { + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" + }, + "program": "${workspaceRoot}/dist/electron.js", + "args" : ["."], + "outputCapture": "std", + "sourceMaps": true + } + ] + } \ No newline at end of file diff --git a/dist/article/article.html b/dist/article/article.html index a1ffc1e..8d63893 100644 --- a/dist/article/article.html +++ b/dist/article/article.html @@ -3,14 +3,14 @@ + content="default-src 'none'; script-src-elem 'sha256-prYLVBOTCtLoXJ5JJGBEADdvxnqlbKVTWQs/C8BrYsQ='; img-src http://* https://*; style-src 'self' 'unsafe-inline'; frame-src http://* https://*; media-src http://* https://*"> Article
- + \ No newline at end of file diff --git a/dist/article/article.js b/dist/article/article.js index 5cbdc95..99b1216 100644 --- a/dist/article/article.js +++ b/dist/article/article.js @@ -17,18 +17,20 @@ for (let s of dom.querySelectorAll("script")) { } let main = document.getElementById("main") main.innerHTML = dom.body.innerHTML -document.addEventListener("click", event => { - event.preventDefault() - let target = event.target - while (target.nodeName !== "#document") { - if (target.href) { - window.renderer.requestNavigation(target.href) - break - } - target = target.parentNode + +let contextOn = false +const dismissListener = () => { + if (contextOn) { + contextOn = false + window.renderer.dismissContextMenu() } -}) +} +document.addEventListener("mousedown", dismissListener) +document.addEventListener("scroll", dismissListener) document.addEventListener("contextmenu", event => { let text = document.getSelection().toString() - if (text) window.renderer.contextMenu([event.clientX, event.clientY], text) + if (text) { + contextOn = true + window.renderer.contextMenu([event.clientX, event.clientY], text) + } }) \ No newline at end of file diff --git a/dist/article/preload.js b/dist/article/preload.js index 5840d5d..2287d8c 100644 --- a/dist/article/preload.js +++ b/dist/article/preload.js @@ -1,10 +1,10 @@ const { contextBridge, ipcRenderer } = require("electron") contextBridge.exposeInMainWorld("renderer",{ - requestNavigation: (href) => { - ipcRenderer.sendToHost("request-navigation", href) + dismissContextMenu: () => { + ipcRenderer.invoke("webview-context-menu", null, null) }, contextMenu: (pos, text) => { - ipcRenderer.sendToHost("context-menu", pos, text) + ipcRenderer.invoke("webview-context-menu", pos, text) } }) \ No newline at end of file diff --git a/src/bridges/settings.ts b/src/bridges/settings.ts index 5ac5ed5..614a22e 100644 --- a/src/bridges/settings.ts +++ b/src/bridges/settings.ts @@ -61,6 +61,13 @@ const settingsBridge = { return ipcRenderer.sendSync("get-locale") }, + getFontSize: (): number => { + return ipcRenderer.sendSync("get-font-size") + }, + setFontSize: (size: number) => { + ipcRenderer.invoke("set-font-size", size) + }, + getAll: () => { return ipcRenderer.sendSync("get-all-settings") as Object }, diff --git a/src/bridges/utils.ts b/src/bridges/utils.ts index e8b9c7c..c53f785 100644 --- a/src/bridges/utils.ts +++ b/src/bridges/utils.ts @@ -40,6 +40,13 @@ const utilsBridge = { await ipcRenderer.invoke("clear-cache") }, + addWebviewContextListener: (callback: (pos: [number, number], text: string) => any) => { + ipcRenderer.removeAllListeners("webview-context-menu") + ipcRenderer.on("webview-context-menu", (_, pos, text) => { + callback(pos, text) + }) + }, + addWebviewKeydownListener: (id: number, callback: (event: Electron.Input) => any) => { ipcRenderer.invoke("add-webview-keydown-listener", id) ipcRenderer.removeAllListeners("webview-keydown") diff --git a/src/components/article.tsx b/src/components/article.tsx index 9046ed6..87e2e3b 100644 --- a/src/components/article.tsx +++ b/src/components/article.tsx @@ -4,9 +4,7 @@ import { renderToString } from "react-dom/server" import { RSSItem } from "../scripts/models/item" import { Stack, CommandBarButton, IContextualMenuProps, FocusZone } from "@fluentui/react" import { RSSSource, SourceOpenTarget } from "../scripts/models/source" -import { store } from "../scripts/settings" -const FONT_SIZE_STORE_KEY = "fontSize" const FONT_SIZE_OPTIONS = [12, 13, 14, 15, 16, 17, 18, 19, 20] type ArticleProps = { @@ -20,6 +18,7 @@ type ArticleProps = { toggleStarred: (item: RSSItem) => void toggleHidden: (item: RSSItem) => void textMenu: (text: string, position: [number, number]) => void + dismissContextMenu: () => void } type ArticleState = { @@ -36,13 +35,14 @@ class Article extends React.Component { fontSize: this.getFontSize(), loadWebpage: this.props.source.openTarget === SourceOpenTarget.Webpage } + window.utils.addWebviewContextListener(this.contextMenuHandler) } getFontSize = () => { - return store.get(FONT_SIZE_STORE_KEY, 16) + return window.settings.getFontSize() } setFontSize = (size: number) => { - store.set(FONT_SIZE_STORE_KEY, size) + window.settings.setFontSize(size) this.setState({fontSize: size}) } @@ -79,27 +79,16 @@ class Article extends React.Component { ] }) - ipcHandler = event => { - switch (event.channel) { - case "request-navigation": { - window.utils.openExternal(event.args[0]) - break - } - case "context-menu": { - let articlePos = document.getElementById("article").getBoundingClientRect() - let [x, y] = event.args[0] - this.props.textMenu(event.args[1], [x + articlePos.x, y + articlePos.y]) - break - } + contextMenuHandler = (pos: [number, number], text: string) => { + if (pos) { + let articlePos = document.getElementById("article").getBoundingClientRect() + let [x, y] = pos + this.props.textMenu(text, [x + articlePos.x, y + articlePos.y]) + } else { + this.props.dismissContextMenu() } } - popUpHandler = event => { - window.utils.openExternal(event.url) - } - navigationHandler = event => { - window.utils.openExternal(event.url) - this.props.dismiss() - } + keyDownHandler = (input: Electron.Input) => { if (input.type === "keyDown") { switch (input.key) { @@ -134,11 +123,9 @@ class Article extends React.Component { componentDidMount = () => { let webview = document.getElementById("article") as Electron.WebviewTag if (webview != this.webview) { - webview.addEventListener("ipc-message", this.ipcHandler) - webview.addEventListener("new-window", this.popUpHandler) - webview.addEventListener("will-navigate", this.navigationHandler) webview.addEventListener("dom-ready", () => { - window.utils.addWebviewKeydownListener(webview.getWebContentsId(), this.keyDownHandler) + let id = webview.getWebContentsId() + window.utils.addWebviewKeydownListener(id, this.keyDownHandler) }) this.webview = webview webview.focus() @@ -227,7 +214,7 @@ class Article extends React.Component { key={this.props.item._id + (this.state.loadWebpage ? "_" : "")} src={this.state.loadWebpage ? this.props.item.link : this.articleView()} preload={this.state.loadWebpage ? null : "article/preload.js"} - webpreferences="contextIsolation,sandbox,disableDialogs,autoplayPolicy=document-user-activation-required" + webpreferences="contextIsolation,disableDialogs,autoplayPolicy=document-user-activation-required" partition="sandbox" /> ) diff --git a/src/containers/article-container.tsx b/src/containers/article-container.tsx index 90b35f4..4f0cd7e 100644 --- a/src/containers/article-container.tsx +++ b/src/containers/article-container.tsx @@ -5,7 +5,7 @@ import { RSSItem, markUnread, markRead, toggleStarred, toggleHidden, itemShortcu import { AppDispatch } from "../scripts/utils" import { dismissItem, showOffsetItem } from "../scripts/models/page" import Article from "../components/article" -import { openTextMenu } from "../scripts/models/app" +import { openTextMenu, closeContextMenu } from "../scripts/models/app" type ArticleContainerProps = { itemId: string @@ -34,7 +34,8 @@ const mapDispatchToProps = (dispatch: AppDispatch) => { toggleHasRead: (item: RSSItem) => dispatch(item.hasRead ? markUnread(item) : markRead(item)), toggleStarred: (item: RSSItem) => dispatch(toggleStarred(item)), toggleHidden: (item: RSSItem) => dispatch(toggleHidden(item)), - textMenu: (text: string, position: [number, number]) => dispatch(openTextMenu(text, position)) + textMenu: (text: string, position: [number, number]) => dispatch(openTextMenu(text, position)), + dismissContextMenu: () => dispatch(closeContextMenu()) } } diff --git a/src/electron.ts b/src/electron.ts index 62223d0..8fdedd5 100644 --- a/src/electron.ts +++ b/src/electron.ts @@ -3,6 +3,7 @@ import { ThemeSettings, SchemaTypes } from "./schema-types" import { store } from "./main/settings" import performUpdate from "./main/update-scripts" import { WindowManager } from "./main/window" +import { openExternal } from "./main/utils" if (!process.mas) { const locked = app.requestSingleInstanceLock() @@ -71,3 +72,14 @@ ipcMain.handle("import-all-settings", (_, configs: SchemaTypes) => { nativeTheme.themeSource = store.get("theme", ThemeSettings.Default) winManager.mainWindow.close() }) + +app.on("web-contents-created", (_, contents) => { + contents.on("new-window", (event, url) => { + if (winManager.hasWindow()) event.preventDefault() + if (contents.getType() === "webview") openExternal(url) + }) + contents.on("will-navigate", (event, url) => { + event.preventDefault() + if (contents.getType() === "webview") openExternal(url) + }) +}) diff --git a/src/main/settings.ts b/src/main/settings.ts index eef870d..5c43701 100644 --- a/src/main/settings.ts +++ b/src/main/settings.ts @@ -105,6 +105,14 @@ ipcMain.on("get-locale", (event) => { event.returnValue = locale }) +const FONT_SIZE_STORE_KEY = "fontSize" +ipcMain.on("get-font-size", (event) => { + event.returnValue = store.get(FONT_SIZE_STORE_KEY, 16) +}) +ipcMain.handle("set-font-size", (_, size: number) => { + store.set(FONT_SIZE_STORE_KEY, size) +}) + ipcMain.on("get-all-settings", (event) => { let output = {} for (let [key, value] of store) { diff --git a/src/main/utils.ts b/src/main/utils.ts index 6c3364f..0d0ad4a 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -2,14 +2,18 @@ import { ipcMain, shell, dialog, app, session, webContents, clipboard } from "el import { WindowManager } from "./window" import fs = require("fs") +export function openExternal(url: string) { + if (url.startsWith("https://") || url.startsWith("http://")) + shell.openExternal(url) +} + export function setUtilsListeners(manager: WindowManager) { ipcMain.on("get-version", (event) => { event.returnValue = app.getVersion() }) ipcMain.handle("open-external", (_, url: string) => { - if (url.startsWith("https://") || url.startsWith("http://")) - shell.openExternal(url) + openExternal(url) }) ipcMain.handle("show-error-box", (_, title, content) => { @@ -76,6 +80,12 @@ export function setUtilsListeners(manager: WindowManager) { await session.defaultSession.clearCache() }) + ipcMain.handle("webview-context-menu", (_, pos, text) => { + if (manager.hasWindow()) { + manager.mainWindow.webContents.send("webview-context-menu", pos, text) + } + }) + ipcMain.handle("add-webview-keydown-listener", (_, id) => { let contents = webContents.fromId(id) contents.on("before-input-event", (_, input) => { diff --git a/src/main/window.ts b/src/main/window.ts index f9cd027..ce1ec2a 100644 --- a/src/main/window.ts +++ b/src/main/window.ts @@ -57,9 +57,9 @@ export class WindowManager { fullscreenable: false, show: false, webPreferences: { - nodeIntegration: true, webviewTag: true, - enableRemoteModule: true, + enableRemoteModule: false, + contextIsolation: true, preload: path.join(app.getAppPath(), (app.isPackaged ? "dist/" : "") + "preload.js") } }) diff --git a/src/preload.ts b/src/preload.ts index afbad90..1b13bbf 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -2,5 +2,5 @@ import { contextBridge } from "electron" import settingsBridge from "./bridges/settings" import utilsBridge from "./bridges/utils" -window.settings = settingsBridge -window.utils = utilsBridge \ No newline at end of file +contextBridge.exposeInMainWorld("settings", settingsBridge) +contextBridge.exposeInMainWorld("utils", utilsBridge) diff --git a/src/scripts/settings.ts b/src/scripts/settings.ts index 1bff9ce..d17f1d1 100644 --- a/src/scripts/settings.ts +++ b/src/scripts/settings.ts @@ -1,11 +1,8 @@ import { IPartialTheme, loadTheme } from "@fluentui/react" import locales from "./i18n/_locales" -import Store = require("electron-store") -import { ThemeSettings, SchemaTypes } from "../schema-types" +import { ThemeSettings } from "../schema-types" import intl from "react-intl-universal" -export const store = new Store() - const lightTheme: IPartialTheme = { defaultFontStyle: { fontFamily: '"Segoe UI", "Source Han Sans SC Regular", "Microsoft YaHei", sans-serif' } } @@ -39,7 +36,6 @@ const darkTheme: IPartialTheme = { } } -const THEME_STORE_KEY = "theme" export function setThemeSettings(theme: ThemeSettings) { window.settings.setThemeSettings(theme) applyThemeSettings() diff --git a/webpack.config.js b/webpack.config.js index 7f4c360..86f4fe1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -16,6 +16,7 @@ module.exports = [ }] }, output: { + devtoolModuleFilenameTemplate: '[absolute-resource-path]', path: __dirname + '/dist', filename: 'electron.js' } @@ -42,8 +43,11 @@ module.exports = [ { mode: 'production', entry: './src/index.tsx', - target: 'electron-renderer', + target: 'web', devtool: 'source-map', + performance: { + hints: false + }, module: { rules: [{ test: /\.ts(x?)$/,