| ページ一覧 | ブログ | twitter |  書式 | 書式(表) |

MyMemoWiki

「Electron」の版間の差分

提供: MyMemoWiki
ナビゲーションに移動 検索に移動
513行目: 513行目:
 
[[File:electron_menu_role.png|300px]]
 
[[File:electron_menu_role.png|300px]]
  
 +
====コンテキストメニュー====
 +
----
 +
*contextBridgeを利用する
 +
[[File:electron_contextmenu.png|400px]]
 +
 +
*index.html
 +
<pre>
 +
        function openContextMenu(e) {
 +
            e.preventDefault();
 +
            window.api.openContextMenu("hoge");
 +
        }
 +
        window.addEventListener('contextmenu', openContextMenu, false);
 +
</pre>
 +
*preload.js
 +
<pre>
 +
contextBridge.exposeInMainWorld(
 +
    "api",
 +
    {
 +
      openContextMenu: (type) => {
 +
            return ipcRenderer.invoke('open-context-menu', type);
 +
        }
 +
    }
 +
);
 +
</pre>
 +
*main.js
 +
<pre>
 +
const { ipcMain, BrowserWindow, Menu } = require('electron');
 +
 +
ipcMain.handle('open-context-menu', (ev, msg) => {
 +
    var win = BrowserWindow.getFocusedWindow();
 +
    let contextmenuTemplate = [
 +
        {
 +
            label: msg, click(m ,w) {
 +
                alert(m + w);
 +
            }
 +
        },
 +
        { type: 'separator' },
 +
        { label: '切り取り', role: 'cut' },
 +
        { label: 'コピー', role: 'copy' },
 +
        { label: '貼り付け', role: 'paste' },
 +
    ];
 +
    const contextMenu = Menu.buildFromTemplate(contextmenuTemplate);
 +
    contextMenu.popup({window : win});
 +
});
 +
</pre>
 
===Dialog===
 
===Dialog===
 
----
 
----

2021年10月4日 (月) 14:55時点における版

| Node.js | JavaScript | TypeScript | npm | Flutter |

目次

Electron

Fiddle


API Document


Required

基本的なアプリの作成


  • Electronアプリケーションは本質的にNode.jsアプリケーション
  • Electronアプリケーションは、package.json から開始される

プロジェクトの作成とElectronのインストール

  1. mkdir my-electron-app && cd my-electron-app
  2. npm init -y
  3. npm i --save-dev electron
  • グローバルにインストール

npm

  1. npm -g i electron

mainスクリプトファイル(main.js)の作成


  • mainスクリプトは、Electronアプリケーションのエントリーポイント
  • Mainプロセスを開始し、Mainプロセスはアプリケーションのライフサイクルをコントロールする
  1. const { app, BrowserWindow } = require('electron')
  2. const path = require('path')
  3.  
  4. function createWindow() {
  5. const win = new BrowserWindow({
  6. width:400,
  7. height:300,
  8. webPreferences:{
  9. preload: path.join(__dirname, 'preload.js')
  10. }
  11. })
  12. win.loadFile('index.html')
  13. }
  14.  
  15. app.whenReady().then(() => {
  16. createWindow()
  17.  
  18. app.on('activate', () =>{
  19. if (BrowserWindow.getAllWindows().length == 0) {
  20. createWindow()
  21. }
  22. })
  23. })
  24.  
  25. app.on('window-all-closed', () => {
  26. if (process.platform !== 'darwin') {
  27. app.quit()
  28. }
  29. })

Webページ(index.html)の作成


  • index.html
  • アプリケーション初期化時に一度だけ表示されるページ
  • このページがレンダープロセスを表現する
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta carhset="UTF-8">
  5. <title>Electron Sample</title>
  6. <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
  7. </head>
  8. <body style="background: white;">
  9. <h2>Version</h2>
  10. <div>
  11. We are using Node.js <span id="node-version"></span>
  12. </div>
  13. <div>
  14. Chromium <span id="chrome-version"></span>,
  15. </div>
  16. <div>
  17. Electron <span id="electron-version"></span>.
  18. </div>
  19. </body>
  20. </html>

プレロードスクリプト(preload.js)


  • Node.jsとWebページのブリッジ
  • Node.js全体を安全に公開するのではなく、特定のAPIや動作をWebページに公開することができる
  • 以下ではprocessオブジェクトからバージョン情報を読み取りページを更新する
  1. window.addEventListener('DOMContentLoaded', () => {
  2. const replaceText = (selector, text) => {
  3. const element = document.getElementById(selector);
  4. if (element) {
  5. element.innerText = text;
  6. }
  7. }
  8. for (const type of ['chrome', 'node', 'electron']) {
  9. replaceText(`${type}-version`, process.versions[type])
  10. }
  11. })

package.json


  1. {
  2. "name": "electron_sample",
  3. "version": "1.0.0",
  4. "main": "main.js",
  5. "scripts": {
  6. "start": "electron .",
  7. },
  8. :
  9. }

.gitignore


起動


  1. npm start

Electron sample start.png

Visual Studio Codeでのデバッグ


launch.json Node を Visual Studio Code でデバッグするときにグローバルにインストールしたモジュールが読み込まれない

  1. {
  2. "version": "0.2.0",
  3. "configurations": [
  4. {
  5. "name": "Debug Main Process",
  6. "type": "node",
  7. "request": "launch",
  8. "cwd": "${workspaceFolder}",
  9. "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
  10. "windows": {
  11. "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
  12. },
  13. "args" : ["."],
  14. "outputCapture": "std"
  15. }
  16. ]
  17. }

パッケージングと配布


Electron Forge


  • もっともシンプルで素早く配布するには、Electron Forgeを利用する
Electron ForgeをアプリケーションフォルダにImport
  1. $ npm install --save-dev @electron-forge/cl
  2. $ npx electron-forge import
  3. Checking your system
  4. Initializing Git Repository
  5. Writing modified package.json file
  6. Installing dependencies
  7. Writing modified package.json file
  8. Fixing .gitignore
  9.  
  10.  
  11. We have ATTEMPTED to convert your app to be in a format that electron-forge understands.
  12.  
  13. Thanks for using "electron-forge"!!!
Mac 配布パッケージを作成

  • out フォルダに出力される
  1. $ npm run make
  2.  
  3. > fx_sample@1.0.0 make
  4. > electron-forge make
  5.  
  6. Checking your system
  7. Resolving Forge Config
  8. We need to package your application before we can make it
  9. Preparing to Package Application for arch: x64
  10. Preparing native dependencies
  11. Packaging Application
  12. Making for the following targets: zip
  13. Making for target: zip - On platform: darwin - For arch: x64

Electron forge mac.png

Windows配布パッケージの作成


  • MacWindows用のパッケージ出力には、monoなどインストールが必要なようなので、Windows同様の手順でパッケージを作成。

Electron forge win.png

Ubuntu配布パッケージの作成


  1. $ sudo apt install dpkg-dev
  2. $ sudo apt install rpm
  1. $ npm run make

Electron forge ubuntu.png

Electronの知識

プロセス


メインプロセスとレンダラープロセス


  • main.js がメインプロセスを担い、GUIは持たない
  • レンダーラープロセスは、Electronに内臓のWebブラウザを利用する
状況

2021/10現在、contextBridge利用が推奨

  • nodeIntegration: true -> これで、Renderer 側で、Node api が使えていたが、今は使えない、remoteも使えない
  • nodeIntegration: false, contextIsolation: false -> これで、Main と Rendererで同一のコンテキストとなるので、windowにipcReaderを登録して使える
  • nodeIntegration: false, contextIsolation: true -> contextBridge を使うことで、IPC通信ができる(contextIsolation true必須


remoteオブジェクト

【Electron】remote moduleがdeprecatedになっている背景

  • レンダラープロセスから、appやBrowserWindowなどのメインプロセス専用の機能を利用したい場合に用意されている
  • remoteは内部にまるでメインプロセスのモジュールが用意されているかのように振る舞う
  1. const { remote } = require('electron');

IPC(プロセス間通信)


  • IPCについて
  • メインプロセスとレンダラープロセスで情報を授受する場合、IPCを利用する
  • ページAからページBを操作したい場合など、メッセージを ページA->メインプロセス->ページBと連携させる必要がある
ipcMain,ipcRenderer

Electron ipc.png

  • main.js Window生成
    • preload.js
    • contextIsolation: false
  1. let win = new BrowserWindow({
  2. width: 600,
  3. height: 400,
  4. webPreferences:{
  5. contextIsolation: false, // window object共有
  6. preload: path.join(__dirname, 'preload.js')
  7. // nodeIntegration: true,
  8. // enableRemoteModule: true
  9. }
  10. });
  11. // win.loadURL('https://service.typea.info/blogwiki');
  12. win.loadFile('index.html');
  • main.js 通信
  1. const { ipcMain } = require('electron');
  2. ipcMain.handle('invoke-test', (ev, msg) => {
  3. console.log("Message From Renderer:" + msg);
  4. return "Main response!";
  5. });
  • preload.js
    • contextIsolation: false, // window object共有
  1. const { ipcRenderer } = require('electron');
  2. window.ipcRenderer = ipcRenderer;
  • index.html
  1. <script>
  2. ipcRenderer.invoke('invoke-test','sendmessage').then((data) => {
  3. console.log("Response from main:" + data);
  4. });
  5. </script>
contextBridge

contextBridgeを使えば、nodeIntegration: false,contextIsolation: trueでもIPC通信できる

Context Isolationは、プリロードスクリプトとElectronの内部ロジックが、webContentsでロードするWebサイトとは別のコンテキストで実行されるようにする機能です。これは、WebサイトがElectronの内部やプリロードスクリプトがアクセスできる強力なAPIにアクセスできないようにするためのセキュリティ上の重要な機能です。

つまり、プリロードスクリプトがアクセスできるウィンドウオブジェクトは、実際にはWebサイトがアクセスできるオブジェクトとは異なるものです。例えば、プリロードスクリプトでwindow.hello = 'wave'と設定し、コンテキストアイソレーションを有効にした場合、ウェブサイトがwindow.helloにアクセスしようとすると、window.helloは未定義となります。

コンテキスト分離はElectron 12からデフォルトで有効になっており、すべてのアプリケーションで推奨されるセキュリティ設定です。

  • main.js Window生成
    • contextBridgeを使う場合、contextIsolation:true とする必要あり
  1. let win = new BrowserWindow({
  2. width: 600,
  3. height: 400,
  4. webPreferences:{
  5. contextIsolation: true, // false -> window object共有、contextBridge利用時はtrue
  6. preload: path.join(__dirname, 'preload.js'),
  7. // enableRemoteModule: false
  8. // nodeIntegration: true,
  9. }
  10. });
  11. // win.loadURL('https://service.typea.info/blogwiki');
  12. win.loadFile('index.html');
  • main.js 通信
  1. const { ipcMain } = require('electron');
  2. ipcMain.handle('invoke-test', (ev, msg) => {
  3. console.log("Message From Renderer:" + msg);
  4. return "Main response!";
  5. });
  • preload.js
  1. const electron = require('electron');
  2. const { ipcRenderer, contextBridge } = electron;
  3.  
  4. contextBridge.exposeInMainWorld(
  5. "api",
  6. {
  7. openWinWitMessage: (message) => {
  8. ipcRenderer.invoke('invoke-test', message).then((data) => {
  9. console.log("Response from main:" + data);
  10. });
  11. }
  12. }
  13. );
  • index.tml
  1. function openWinContextBridge() {
  2. window.api.openWinWitMessage("use contextBridge!!");
  3. }

Electron contextbridge.png

contextBridge(main から rendelerの呼び出し)

  • main.js
  • 1秒ごとに時間を送信
  1. let win = new BrowserWindow({
  2. width: 600,
  3. height: 400,
  4. webPreferences:{
  5. preload: path.join(__dirname, 'preload.js'),
  6. }
  7. });
  8. win.loadFile('index.html');
  9.  
  10. setInterval(() => {
  11. var now = new Date().toISOString();
  12. console.log(now);
  13. win.webContents.send('timer', now);
  14. }, 1000);
  • preload.js
  1. const electron = require('electron');
  2. const { ipcRenderer, contextBridge /*remote*/ } = electron;
  3.  
  4. contextBridge.exposeInMainWorld(
  5. "api",
  6. {
  7. on: (channel, callback) => {
  8. ipcRenderer.on(channel, (event, argv)=>callback(event, argv))
  9. }
  10. }
  11. );
  • index.html
  1. window.api.on('timer', (event, time)=>{
  2. document.getElementById('timer').innerText = time;
  3. });

Electron contextbridge frommain.png

オブジェクト

app


  • アプリケーション本体
  • 起動/終了、Windowオープン/クローズなどの管理

BrowserWindow


  • Electronアプリで表示されるウィンドウオブジェクト
  • HTMLファイルを読み込むことでウィンドウを表示する

WebContents


  • BrowserWindowに含まれ、Webコンテンツの状態管理
  • Webコンテンツに関するイベント

BrowserView


  • BrowserWindowでloadFile、、loadURLを使って表示するコンテンツに、さらにWebコンテンツを埋め込む
  • BrowserWindow内部に小さな内部Windowのようなものを追加し、別コンテンツを表示できる
  1. const view = new BrowserView();
  2. view.webContents .loadURL('https://service.typea.info/blogwiki');
  3. win.setBrowserView(view);
  4. view.setBounds({
  5. : 100,
  6. : 150,
  7. width : 300,
  8. height : 150
  9. });

Electron browser view.png

Menu


  • Menu,MenuItem
  • clickイベント
  1. let menu = new Menu();
  2.  
  3. let menuFile = new MenuItem({
  4. label: 'ファイル',
  5. submenu: [
  6. { label: '新規2', click: () => { console.log('新規2'); } },
  7. new MenuItem({ label: '開く' }),
  8. new MenuItem({ label: '終了' }),
  9. ]
  10. });
  11. menu.append(menuFile);
  12.  
  13. Menu.setApplicationMenu(menu);

Electron menu.png

テンプレートからメニューを作成する


  • セパレータは、type: 'separator'
  1. let menuFileTemplate = [
  2. {
  3. label: 'ファイル',
  4. submenu: [
  5. { label: '新規2', click: () => { console.log('新規2'); } },
  6. { label: '開く2' },
  7. { type: 'separator' },
  8. { label: '終了2' },
  9. ]
  10. }
  11. ];
  12. menu = Menu.buildFromTemplate(menuFileTemplate);
  13. Menu.setApplicationMenu(menu);

role


  • roleを指定するとロールの機能が組み込まれる
    • about
    • undo
    • redo
    • cut
    • copy
    • paste
    • pasteAndMatchStyle
    • selectAll
    • delete
    • minimize
    • close
    • quit
    • reload
    • forceReload
    • toggleDevTools
    • togglefullscreen
    • resetZoom
    • zoomIn
    • zoomOut
    • fileMenu
    • editMenu
    • viewMenu
    • windowMenu
  1. {
  2. label: '編集',
  3. submenu: [
  4. { label: '切り取り', role: 'cut' },
  5. { label: 'コピー', role: 'copy' },
  6. { label: '貼り付け', role: 'paste' },
  7. ]
  8. }

Electron menu role.png

コンテキストメニュー


  • contextBridgeを利用する

Electron contextmenu.png

  • index.html
  1. function openContextMenu(e) {
  2. e.preventDefault();
  3. window.api.openContextMenu("hoge");
  4. }
  5. window.addEventListener('contextmenu', openContextMenu, false);
  • preload.js
  1. contextBridge.exposeInMainWorld(
  2. "api",
  3. {
  4. openContextMenu: (type) => {
  5. return ipcRenderer.invoke('open-context-menu', type);
  6. }
  7. }
  8. );
  • main.js
  1. const { ipcMain, BrowserWindow, Menu } = require('electron');
  2.  
  3. ipcMain.handle('open-context-menu', (ev, msg) => {
  4. var win = BrowserWindow.getFocusedWindow();
  5. let contextmenuTemplate = [
  6. {
  7. label: msg, click(m ,w) {
  8. alert(m + w);
  9. }
  10. },
  11. { type: 'separator' },
  12. { label: '切り取り', role: 'cut' },
  13. { label: 'コピー', role: 'copy' },
  14. { label: '貼り付け', role: 'paste' },
  15. ];
  16. const contextMenu = Menu.buildFromTemplate(contextmenuTemplate);
  17. contextMenu.popup({window : win});
  18. });

Dialog


contextBridge を使用してファイル選択ダイアログを表示する

  1. const { ipcMain, dialog } = require('electron');
  2. ipcMain.handle('open-file-dialog', async (ev, msg) => {
  3. var win = BrowserWindow.getFocusedWindow();
  4. var result = await dialog.showOpenDialog(win, { properties: ['openFile', 'multiSelections'] });
  5. if (result.canceld) {
  6. return [];
  7. }
  8. return result.filePaths;
  9. });
  • preload.js
  1. const electron = require('electron');
  2. const { ipcRenderer, contextBridge } = electron;
  3.  
  4. contextBridge.exposeInMainWorld(
  5. "api",
  6. {
  7. openFileDialog: (message) => {
  8. return ipcRenderer.invoke('open-file-dialog', message);
  9. }
  10. }
  11. );
  • index.html
  1. async function openWinFileDialog() {
  2. var filePaths = await window.api.openFileDialog("");
  3. alert(filePaths[0]);
  4. }

構成

main.js


  1. const { app, BrowserWindow} = require('electron');
  2.  
  3. function createWindow() {
  4. let win = new BrowserWindow({
  5. width: 400,
  6. height: 200,
  7. webPreferences:{
  8. contextIsolation: false, // window object共有
  9. preload: path.join(__dirname, 'preload.js')
  10. // nodeIntegration: true,
  11. // enableRemoteModule: true
  12. }
  13. });
  14. win.loadFile('index.html');
  15. }
  16.  
  17. app.whenReady().then(createWindow);

オブジェクトの分割代入


  1. const { app, BrowserWindow} = require('electron');

Preloadスクリプト


プリロードスクリプトには、ウェブコンテンツの読み込み開始前にレンダラープロセスで実行されるコードが含まれています。これらのスクリプトはレンダラーのコンテキスト内で実行されますが、Node.jsのAPIにアクセスできるため、より多くの権限が与えられています。

Node.js機能の統合


  • trueでNode.jsの機能(通常のWebで使用できないrequireなど)を利用できるようになる
  1. nodeIntegration: true

remoteモジュールの有効化


  • remoteモジュールの有効化
  1. enableRemoteModule: true

リモートコンテンツを読み込むレンダラー(BrowserWindow、BrowserView、<webview>)では、Node.jsの統合を有効にしないことが最も重要です。この目的は、リモートコンテンツに与える権限を制限することで、攻撃者がウェブサイト上でJavaScriptを実行できるようになった場合に、ユーザーに危害を加えることを劇的に難しくすることです。

この後、特定のホストに対して追加の権限を付与することができます。例えば、https://example.com/ に向けてBrowserWindowを開いている場合、そのWebサイトが必要とする能力を正確に与えることができますが、それ以上はできません。

Webページをロード


  • loadURLとすることで、外部ページをロードできる
  1. // win.loadFile('index.html');
  2. win.loadURL('https://service.typea.info/blogwiki');

Electron web app.png

モーダルダイアログ


  1. function createWindow() {
  2. let win = new BrowserWindow({
  3. width: 600,
  4. height: 400,
  5. webPreference: {
  6. nodeIntegration: true,
  7. enableRemoteModule
  8. }
  9. });
  10. // win.loadURL('https://service.typea.info/blogwiki');
  11. win.loadFile('index.html');
  12.  
  13. let child = new BrowserWindow({
  14. width: 400,
  15. height: 200,
  16. parent: win,
  17. frame: false,
  18. modal: true,
  19. transparent: true,
  20. opacity: 0.5
  21. });
  22. child.loadFile('dialog.html');
  23. }

Electron modal dialog.png

デベロッパーツールを開く


  1. win.webContents.openDevTools();

Electron devtools.png

appオブジェクトのイベント

起動処理の完了

will-finish-launching

初期化処理完了

ready

BrowserWindowの生成

browser-window-created

Webコンテンツの生成

web-contents-created

全てのWindowが閉じられた

window-all-closed

全てのWindowを閉じる前

before-quit

終了する前

will-quit

終了時

quit

BrowserWindowのイベント

Windowの表示

show

Windowの非表示

hide

Window表示準備完了

ready-to-show

Windowを閉じる

close

Windowが閉じられた

closed

その他

  • focus
  • blur
  • maximize
  • unmaximize
  • minimize
  • restore
  • will-resize
  • resize
  • will-move
  • move
  • enter-full-screen
  • leave-full-screen
  • enter-html-full-screen
  • leave-html-full-screen
  • always-on-top-changed

BrowseWindow操作

  • destory
  • close()
  • focus()
  • blur()
  • isFocuced()
  • isDestoryed()
  • show()
  • showInactive()
  • hide()
  • isVisible()
  • isModal()
  • maximize()
  • unmaximize()
  • isMaximized()
  • minimize()
  • restore
  • isMinimized()
  • setFullScreen()
  • isFullScreen()
  • isNormal()
  • SetBounds()
  • GetBounds()
  • SetContentBounds()
  • GetContentsBound()
  • SetSize()
  • GetSize()
  • SetContaentSize()
  • GetContentSize()
  • SetMinimumSize()
  • GetMinimumSize
  • SetMaximumSize()
  • GetMaximumSize()
  • SetPosition()
  • GetPosition()
  • moveTop()
  • center()
  • settitle()
  • getTitle()

WebContentsのイベント

コンテンツロード完了

did-finish-load

フレームのコンテンツロード

did-frame-finish-load

コンテンツ読み込み開始

did-start-loading

コンテンツ読み込み中止

did-stop-loading

DOM生成完了

dom-ready

新しいWindow作成

new-window

URLアクセス時

will-navigate

URLアクセス開始

did-start-navigation

その他

  • will-redirect
  • did-redirect-navigation
  • did-navigate
  • will-prevent-unload
  • destroyed
  • enter-html-full-screen
  • leave-html-full-screen
  • zoom-changed
  • devtools-opend
  • devtools-closed
  • devtools-focused
  • console-message