Electron加载插件支持Flash

创建项目

1
2
3
4
5
6
7
8
9
git clone https://gitee.com/psvmc/electron-quick-start.git
# 重命名
ren electron-quick-start electron-flash-demo
cd electron-flash-demo
# 删除原来的git文件夹
rmdir /s/q .git
rmdir /s/q .github
npm install
npm start

下载 32 位的 Electron

项目根目录中添加.npmrc

1
2
arch=ia32
registry=https://registry.npm.taobao.org

设置 Flash 插件

下载插件

下载 pepflashplayer 插件

注意

这个插件一定要用老版本,新版本的 flash 由于中国代理商要赚钱,会检测一个服务是否启动,不启动就会报错,强行让更新新版本。

如图

image-20220317135705691

老版本的 Chrome 下载 里面带有 pepflashplayer 插件

https://www.slimjet.com/chrome/google-chrome-old-version.php

这里推荐下载 32 位的最老的版本

Version Size Date
48.0.2564.97 40.76 MB 2020-04-29

这个插件已经很难下载到了,我的方法是下载个 360 浏览器带极速内核的版本,打开一个带有 flash 的网页,它就会自动下载插件

在浏览器的安装目录下搜索pepflashplayer,就会找到对应的 dll 文件。

或者下载下面的 DLL

pepflashplayer32_20_0_0_286.dll

链接:https://pan.baidu.com/s/1_eMRkJ8m6jILi40BHSH-4Q
提取码:psvm

注意

这个插件是 32 位的,一定要保证 Electron 是 32 位的。

配置插件

把下载的插件放在项目根目录下libs文件夹下,如图所示:

image-20220315231050276

main.js 中添加以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let pluginName;
switch (process.platform) {
case "win32":
pluginName = "pepflashplayer.dll";
break;
case "darwin":
pluginName = "PepperFlashPlayer.plugin";
break;
case "linux":
pluginName = "libpepflashplayer.so";
break;
}

let plugins_path = path.join(__dirname, "libs", "ppflash", pluginName);
if (__dirname.includes(".asar")) {
plugins_path = path.join(
process.resourcesPath,
"libs",
"ppflash",
pluginName
);
}

app.commandLine.appendSwitch("ppapi-flash-path", plugins_path);

注意:

这里一定要进行判断,因为打包前后的路径是不一致的。

不显示菜单栏

1
2
3
4
5
const electron = require("electron");
/*获取electron窗体的菜单栏*/
const Menu = electron.Menu;
/*隐藏electron创听的菜单栏*/
Menu.setApplicationMenu(null);

页面配置

官方文档:

可用于测试 Flash 的页面:https://sc.chinaz.com/donghua/220315391630.htm

方式 1

这种方式最为简单。

主进程 BrowserWindow

BrowserWindow 添加 webPreferences 配置

1
2
3
4
5
6
7
8
9
10
const mainWindow = new BrowserWindow({
width: 1366,
height: 768,
webPreferences: {
webviewTag: true,
javascript: true,
plugins: true,
webSecurity: false
}
});

plugins: true 这个配置项是必须的。

如果使用的是webview,在标签里添加 plugins 属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Flash</title>
</head>
<body>
<webview
src="https://sc.chinaz.com/donghua/220315391630.htm"
allowpopups
plugins
></webview>

<style lang="text/css">
body {
margin: 0;
padding: 0;
}

webview {
display: flex;
width: 100vw;
height: 100vh;

}
</style>
</body>
</html>

请注意,webview 标签的样式使用 display:flex; 来确保 iframe在传统和 flex 布局一起使用的情况下填充其 webview 容器的全部高度和宽度。

在 devtools 的控制台输入以下命令检查 Pepper Flash 插件是否被加载。

1
navigator.plugins;

注意

这个只能判断是否加载插件,不能判断插件是否可用,比如没有 dll 就不可用,但是插件列表中已经存在。

方式 2

这种方式能控制访问的连接。

首先我们看四种打开新页面的方式

1
2
<a href="https://www.psvmc.cn" target="_blank">_blank</a>
<span onclick="javascript:window.open('https://www.psvmc.cn')">open</span>

分别是:

  1. _blank
  2. .open

实际运行情况是:

在正常的浏览器中,这两种情况都是能新开窗口的。但是,部分浏览器里面可能会拦截.open这种方式。但是绝对没有任何浏览器会拦截_blank这种。

electronwebview中,

  • 对于_blank是默认拦截的,不会自动打开。

  • 对于.open, 添加allowpopups 就会自动用新窗口打开。

所以添加allowpopups 属性,就可以解决面的情况,

_blank的页面添加allowpopups后也无法打开

为了保证两种方式都能正常打开,页面中添加 JS,注意

这时候不要添加allowpopups,否则会打开两个页面。

因为new-window能同时监听到这两种方式。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<webview src="https://sc.chinaz.com/donghua/220315391630.htm" plugins></webview>
<script>
onload = () => {
const webview = document.querySelector('webview')

const new_window = (e) => {
const protocol = (new URL(e.url)).protocol;
if (protocol === 'http:' || protocol === 'https:') {
//webview.loadURL(e.url)
if(e.url.indexOf("sc.chinaz.com")!==-1){
window.open(e.url)
}
}
}

const will_navigate = (e) => {
const protocol = (new URL(e.url)).protocol;
if (protocol === 'http:' || protocol === 'https:') {
console.info("will_navigate",e.url);
if(e.url.indexOf("sc.chinaz.com")===-1){
webview.reload();
}
}
}

webview.addEventListener('new-window', new_window);
webview.addEventListener('will-navigate', will_navigate);
}
</script>

注意

new-window只能监听到页面内_blank.open,页面的重定向是监听不到的。

方式 3

这种方式不但能够控制访问的连接,还能设置窗口属性。

默认的方式其实也是新的进程,和下面的方式一样,但是这种方式我们可以做一些窗口属性的设置。

渲染进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<script>
const ipcRenderer = window.require("electron").ipcRenderer;
onload = () => {
const webview = document.querySelector("webview");
const loadstart = () => {
console.log("开始加载");
};

const loadstop = () => {
console.log("加载完成");
};

const new_window = (e) => {
const protocol = new URL(e.url).protocol;
if (protocol === "http:" || protocol === "https:") {
//window.open(e.url)
ipcRenderer.send("open_url", e.url);
}
};

webview.addEventListener("did-start-loading", loadstart);
webview.addEventListener("did-stop-loading", loadstop);
webview.addEventListener("new-window", new_window);
};
</script>

主进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
let mainWindow;
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 1366,
height: 768,
webPreferences: {
webviewTag: true,
javascript: true,
plugins: true,
webSecurity: false,
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false
}
});

// and load the index.html of the app.
mainWindow.loadFile("index.html");

// Open the DevTools.
//mainWindow.webContents.openDevTools()
}

const ipcMain = require("electron").ipcMain;
let new_win;
ipcMain.on("open_url", (event, arg) => {
new_win = new BrowserWindow({
width: 1366,
height: 768,
title: "",
webPreferences: {
webviewTag: true,
javascript: true,
plugins: true,
webSecurity: false
},
frame: true,
parent: mainWindow
});
new_win.loadURL(arg);
new_win.on("closed", () => {
new_win = null;
});
});

注意

主窗口要设置 Node 环境nodeIntegration: true,

禁止外链跳转

方式 1

按照逻辑我们要限制跳转,只要监听will-navigate事件,阻止它e.preventDefault();就行了,但是实际上这并没有用。

如下(这样写并不生效)

1
2
3
4
5
6
7
8
9
10
11
const will_navigate = (e) => {
const protocol = new URL(e.url).protocol;
if (protocol === "http:" || protocol === "https:") {
console.info("will_navigate", e.url);
if (e.url.indexOf("sc.chinaz.com") === -1) {
e.preventDefault();
}
}
};

webview.addEventListener("will-navigate", will_navigate);

原因是

所有的 event.preventDefault() 都应该从主进程中呼叫而不是渲染进程。

所以我们就要在主进程中做如下操作

  1. 最外层 BrowserWindowwebContents 上监听 did-attach-webview 事件,获取新挂上去的 <webview>webContents
  2. 使用获取到的 webContents 监听 will-navigate事件。

这时候,我们就可以在 will-navigate 事件中使用 e.preventDefault() 阻止 <webview> 导航至其他网页了

代码如下:

1
2
3
4
5
6
7
8
9
10
11
mainWindow.webContents.on("did-attach-webview", (e, wc) => {
const will_navigate = (e, url) => {
console.info(url);
if (url.indexOf("sc.chinaz.com") === -1) {
e.preventDefault();
wc.reload();
}
};

wc.on("will-navigate", will_navigate);
});

方式 2

当然换个思路,虽然我们阻止不了,我们直接重新加载不就行了吗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
onload = () => {
const webview = document.querySelector("webview");

const new_window = (e) => {
const protocol = new URL(e.url).protocol;
if (protocol === "http:" || protocol === "https:") {
//webview.loadURL(e.url)
if (e.url.indexOf("10.88.8.90:9080") !== -1) {
window.open(e.url);
}
}
};

const will_navigate = (e) => {
const protocol = new URL(e.url).protocol;
if (protocol === "http:" || protocol === "https:") {
console.info("will_navigate", e.url);
if (e.url.indexOf("10.88.8.90:9080") === -1) {
webview.reload();
}
}
};

webview.addEventListener("new-window", new_window);
webview.addEventListener("will-navigate", will_navigate);
};

我的方案

下面的两种方案 我最终的选择是

上面页面配置的方式 3 和禁止跳转的方式 1 相结合,这样就可以同时处理主页面和子页面的跳转限制。

主进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
let mainWindow;
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 1366,
height: 768,
webPreferences: {
webviewTag: true,
javascript: true,
plugins: true,
webSecurity: false,
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false
}
});

// and load the index.html of the app.
mainWindow.loadFile("index.html");

// Open the DevTools.
//mainWindow.webContents.openDevTools()
addListener(mainWindow);
}

const ipcMain = require("electron").ipcMain;
let new_win;
ipcMain.on("open_url", (event, arg) => {
new_win = new BrowserWindow({
width: 1366,
height: 768,
title: "",
webPreferences: {
webviewTag: true,
javascript: true,
plugins: true,
webSecurity: false
},
frame: true,
parent: mainWindow
});
new_win.loadURL(arg);
new_win.on("closed", () => {
new_win = null;
});
addListener(new_win);
});

function addListener(win) {
//主页面中的重载
win.webContents.on("did-attach-webview", (e, wc) => {
const will_navigate = (e, url) => {
if (url.indexOf("10.88.8.90:9080") === -1) {
e.preventDefault();
wc.reload();
}
};
wc.on("will-navigate", will_navigate);
});

// 子页面中的重载
const will_navigate = (e, url) => {
if (url.indexOf("10.88.8.90:9080") === -1) {
e.preventDefault();
win.webContents.reload();
}
};
win.webContents.on("will-navigate", will_navigate);
}

渲染进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<webview
src="http://10.88.8.90:9080/AtomLocal/common/login.faces"
disablewebsecurity
plugins
></webview>
<script>
const ipcRenderer = window.require("electron").ipcRenderer;
onload = () => {
const webview = document.querySelector("webview");
let lastUrl = "";
const new_window = (e) => {
// 防止同样的地址,多次调用
if (!lastUrl) {
const protocol = new URL(e.url).protocol;
if (protocol === "http:" || protocol === "https:") {
if (e.url.indexOf("10.88.8.90:9080") !== -1) {
ipcRenderer.send("open_url", e.url);
lastUrl = e.url;
setTimeout(() => {
lastUrl = "";
}, 1000);
}
}
}
};
webview.addEventListener("new-window", new_window);
};
</script>

打包

添加依赖

1
npm install electron-builder@22.9.1 --save-dev

在 pakage.json 中,我们 build 的配置下面内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
"name": "flashapp",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"start": "electron .",
"dist": "electron-builder --win --ia32",
"dist_dir": "electron-builder --win --ia32 --dir"
},
"build": {
"appId": "cn.psvmc.flashapp",
"productName": "Flash加载",
"icon": "app.ico",
"asar": true,
"files": ["main.js", "*.html", "app.ico", "node_modules/**/*"],
"mac": {
"icon": "app.ico",
"target": ["dmg", "zip"]
},
"win": {
"icon": "app.ico",
"target": ["zip"],
"extraResources": "./libs/**/*"
},
"nsis": {
"oneClick": false,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"installerIcon": "app.ico",
"uninstallerIcon": "app.ico",
"installerHeaderIcon": "app.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"license": "LICENSE.txt"
}
},
"devDependencies": {
"electron": "^11.1.1",
"electron-builder": "22.9.1"
}
}

其中最重要的配置就是 "extraResources": "./libs/**/*" 这样的话我们的所有配置就算完成了。

注意 NodeJS 的版本要在 14 以上

依赖版本号

1
2
3
"electron-builder": "22.9.1"
"electron-builder": "~22.9.1"
"electron-builder": "^22.9.1"

其中

  1. 前面不带符号则版本号的 3 个数字都匹配
  2. ~则版本号的前 2 个数字匹配
  3. ^则版本号的第一个数字匹配

建议

使用~+版本号