mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-02-17 20:21:05 +01:00
enable context isolation
This commit is contained in:
parent
394643b95e
commit
59c5d663f1
19
.vscode/launch.json
vendored
Normal file
19
.vscode/launch.json
vendored
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
4
dist/article/article.html
vendored
4
dist/article/article.html
vendored
@ -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>
|
24
dist/article/article.js
vendored
24
dist/article/article.js
vendored
@ -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)
|
||||
}
|
||||
})
|
6
dist/article/preload.js
vendored
6
dist/article/preload.js
vendored
@ -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)
|
||||
}
|
||||
})
|
@ -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
|
||||
},
|
||||
|
@ -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")
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
@ -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) {
|
||||
|
@ -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) => {
|
||||
|
@ -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")
|
||||
}
|
||||
})
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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?)$/,
|
||||
|
Loading…
x
Reference in New Issue
Block a user