Background
Chrome ExtensionのContent Scriptではcontextがページ上で実行されているJSとは違うためwindow
経由で変数取得や関数実行ができません。
window
コンテキストで関数を実行する方法は
http://qiita.com/suin/items/5e1aa942e654bce442f7
や
http://stackoverflow.com/questions/20499994/access-window-variable-from-content-script
などいろいろありますが、結果を取得する例は見つからなかったので自分で作ってみました。
Solution
Gist: https://gist.github.com/dtaniwaki/b2b29a95b98f593ffd57
関数を実行した後でその結果を挿入したscript
タグのdataset
にJSONにシリアライズして格納し、content script側から同じIDのscript
を探してデシリアライズしてコールバックに結果を渡しています。
windowContextRunner.js
'use strict'
let waitScriptResult = function (id, timeout) {
return new Promise(function (resolve, reject) {
let timerId
timerId = setInterval(function () {
let elem = document.getElementById(id)
if (typeof elem !== 'undefined' && typeof elem.dataset.done !== 'undefined') {
clearInterval(timerId)
timerId = null
if (typeof elem.dataset.error !== 'undefined' && elem.dataset.error !== '') {
reject(Error(elem.dataset.error))
} else {
resolve(JSON.parse(elem.dataset.result))
}
document.body.removeChild(elem)
}
}, 100)
setTimeout(function () {
if (timerId) {
clearInterval(timerId)
reject(Error('The injected script was timeout'))
}
}, timeout || 5000)
})
}
let injectScript = function (promise) {
let n = Math.floor(Math.random() * 1000000)
let id = `kzaex_run_in_page_${n}`
let s = document.createElement('script')
s.id = id
s.text = `
(function() {
var promise = ${promise.toString()};
var script = document.getElementById("${id}");
promise.then(function(result, reject) {
script.dataset.result = JSON.stringify(result);
script.dataset.done = 'true';
}).catch(function(e) {
script.dataset.error = e.message;
script.dataset.done = 'true';
})
})();
`
document.body.appendChild(s)
return id
}
export function run (script, timeout) {
let id = injectScript(`
new Promise(function(resolve, reject) {
try {
resolve((${script.toString()})());
} catch(e) {
reject(e);
}
});
`)
return waitScriptResult(id, timeout)
}
export function runWithCallback (script, timeout) {
let id = injectScript(`
new Promise(function(resolve, reject) {
try {
(${script.toString()})(function(result) {
resolve(result);
});
} catch(e) {
reject(e);
}
});
`)
return waitScriptResult(id, timeout)
}
使い方は以下のようになります。
import * as windowContextRunner from './windowContextRunner.js'
windowContextRunner.run(function() {
// Do something and return the result
var result = 1;
return result;
}).then(function(result) {
// Use the result of the function above
result == 1
});
エラーハンドリングなどは特にここでは深く実装していませんが、簡単に追加できると思うので用途に応じて追加してください!