enable context isolation

This commit is contained in:
刘浩远 2020-06-30 19:15:37 +08:00
parent 394643b95e
commit 59c5d663f1
15 changed files with 111 additions and 58 deletions

19
.vscode/launch.json vendored Normal file
View File

@ -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
}
]
}

View File

@ -3,14 +3,14 @@
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src-elem 'sha256-XUiRvcvPhKzBU50B7nIFBLySVn2CJ3cNFgqtfeiRcTA='; img-src http://* https://*; style-src 'self' 'unsafe-inline'; frame-src http://* https://*; media-src http://* https://*">
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://*">
<title>Article</title>
<link rel="stylesheet" href="scroll.css" />
<link rel="stylesheet" href="article.css" />
</head>
<body>
<div id="main"></div>
<script integrity="sha256-XUiRvcvPhKzBU50B7nIFBLySVn2CJ3cNFgqtfeiRcTA=" src="article.js"></script>
<script integrity="sha256-prYLVBOTCtLoXJ5JJGBEADdvxnqlbKVTWQs/C8BrYsQ=" src="article.js"></script>
<!-- Run "cat article.js | openssl dgst -sha256 -binary | openssl enc -base64 -A" for hash -->
</body>
</html>

View File

@ -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)
}
})

View File

@ -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)
}
})

View File

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

View File

@ -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")

View File

@ -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<ArticleProps, ArticleState> {
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<ArticleProps, ArticleState> {
]
})
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<ArticleProps, ArticleState> {
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<ArticleProps, ArticleState> {
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" />
</FocusZone>
)

View File

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

View File

@ -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)
})
})

View File

@ -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) {

View File

@ -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) => {

View File

@ -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")
}
})

View File

@ -2,5 +2,5 @@ import { contextBridge } from "electron"
import settingsBridge from "./bridges/settings"
import utilsBridge from "./bridges/utils"
window.settings = settingsBridge
window.utils = utilsBridge
contextBridge.exposeInMainWorld("settings", settingsBridge)
contextBridge.exposeInMainWorld("utils", utilsBridge)

View File

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

View File

@ -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?)$/,