2019/10/26

Electron のデフォルトでは Spectron が正しく動作しない

@Electron 7.0.0
@Spectron 9.0.0

[electron-quick-start](https://github.com/electron/electron-quick-start) に [mocha](https://mochajs.org/) と [spectron](https://electronjs.org/spectron) をインストールして、簡単なテストを走らせてみました。
すると、以下のようなエラーが。

```
  1) Application launch
       shows an initial window:
     TypeError: waitUntilWindowLoaded Cannot read property 'isLoading' of undefined
      at waitUntil(, ) - application.js:263:17
```

実際に走らせたのは以下のコードです。


```js
`highlight: 25;
const Application = require('spectron').Application
const assert = require('assert')
const electronPath = require('electron')
const path = require('path')

describe('Application launch', function () {
  this.timeout(10000)

  beforeEach(function () {
    this.app = new Application({
      path: electronPath,
      args: [path.join(__dirname, '..')]
    })
    return this.app.start()
  })

  afterEach(function () {
    if (this.app && this.app.isRunning()) {
      return this.app.stop()
    }
  })

  it('shows an initial window', function () {
    // 以下の waitUntilWindowLoaded() 内で落ちてる
    return this.app.client.waitUntilWindowLoaded().getWindowCount().then(function (count) {
      assert.equal(count, 1)
    })
  })
})
```

`waitUntilWindowLoaded()` 内で問題が起こっているようですが、このコードは `Spectron` の公式ページに書かれているものです。
`waitUntilWindowLoaded()` の使用方法に問題があるとは思えません公式に書いてあっても、ドキュメントが更新されてなく使えないということは多々あるけど…。


### Electron の設定が原因
この問題、実は [Spectron の README.md](https://github.com/electron-userland/spectron#node-integration) にしれっと書いてありました。

> The Electron helpers provided by Spectron require accessing the core Electron APIs in the renderer processes of your application. So, either your Electron application has nodeIntegration set to true or you'll need to expose a require window global to Spectron so it can access the core Electron APIs.

`nodeIntegration` が `false` だと Spectron は正しく動かないとのこと。

そして、この `nodeIntegration`、Electron 5 以降ではデフォルト `false` なのです。(理由は後述)

このことは、公式の [Issue#174](https://github.com/electron-userland/spectron/issues/174) にもあがっていて、他にも `app.electron` や `app.webContents` が `undefined` になってしまう問題があるようです。
実際、`app.webContents.executeJavaScript('1 + 2')` を呼んでみたところ、エラーになりました。

```
  1) Application launch
       shows an initial window:
     TypeError: Cannot read property 'executeJavaScript' of undefined
      at Context. (test/spec.js:24:35)
      at 
      at process._tickDomainCallback (internal/process/next_tick.js:228:7)
```


### 対策
`nodeIntegration` を `true` にすれば動作します。
しかし、常に `true` にしてしまうとセキュリティリスクがあるので、以下のようにテストの時だけ `true` になるようにするのが良いかと思います。

```js
  mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: (process.env.NODE_ENV === 'test')
    }
  })
```

私の環境では `NODE_ENV` 環境変数は自動で `test` に設定されました。
ただ、これは環境依存だと思うので `package.json` に以下のように書いておくと安心です。
```js
`title: "package.json";
  "scripts": {
    "test": "NODE_ENV=test mocha"
  },
```

私の環境では公式に載っている `preload.js` 内で `window.electronRequire` を設定する方法は動きませんでした。何でだろう…


### なぜ、デフォルト false になったのか
`nodeIntegration` が `true` だと Renderer プロセスの中から Node.js の API にアクセス出来るようになります。
つまり、Renderer からローカルのファイルやその他のローカルリソースに直接アクセスできてしまうのです。

もし、この状態で悪意のある外部コンテンツを実行すると、とても危険です。
そのため、`nodeIntegration` はデフォルト `false` になったのだと思われます。

セキュリティを高めたのは良いけれど、テスト時と実行時の動作環境があまりに異なってしまうのはどうなんだろう…

0 件のコメント: