3

ブラウザのアクションポップアップから実行しているchrome.tabs.executeScriptを使用して、このページで実行したい機能があります。パーミッションが正しく設定されており、それが同期コールバックで正常に動作します:chrome.tabs.executeScriptを使用して非同期関数を実行する

chrome.tabs.executeScript(
    tab.id, 
    { code: `(function() { 
     // Do lots of things 
     return true; 
    })()` }, 
    r => console.log(r[0])); // Logs true 

問題は、私は呼びたい関数は、いくつかのコールバックを通過するということですので、私はasyncawait使用したい:

chrome.tabs.executeScript(
    tab.id, 
    { code: `(async function() { 
     // Do lots of things with await 
     return true; 
    })()` }, 
    async r => { 
     console.log(r); // Logs array with single value [Object] 
     console.log(await r[0]); // Logs empty Object {} 
    }); 

問題は、コールバックの結果がrです。スクリプトの結果の配列でなければならないので、r[0]は、スクリプトが終了したときに解決する約束であると思っています。

Promise構文(.then()を使用)も機能しません。

私がページ内で全く同じ機能を実行すると、予想どおりに約束を返し、待つことができます。

私が間違っていることを知っていて、周りには何か考えがありますか?

+1

これがうまくいけば面白かったでしょうが、私はそれが期待できませんでした。コンテンツスクリプトのコードとバックグラウンドコンテキスト(ポップアップ)のコードは、完全に別々のプロセスで実行されています。私はそれが '非同期'コンテンツスクリプトからの応答を '待っている'ことに驚いたでしょう。 [Message Passing](https://developer.chrome.com/extensions/messaging)を使用する必要があります。 – Makyen

+1

async/awaitは、イベントループベースのjsエンジンの動作を変更しないので、動作しません。 – wOxxOm

+0

Firefox Web Extensionsでは、 'chrome.tabs.executeScript'はPromiseを返します.MDNの文書によると、これはchromeの動作と互換性がありますが、これに関しては有用なChromeドキュメントが見つかりませんでした。何かを考えてください –

答えて

3

問題は、イベントとネイティブオブジェクトがページと拡張機能の間で直接使用できないということです。基本的には、シリアル化されたコピーを取得します。JSON.parse(JSON.stringify(obj))を実行した場合のようになります。

これは({}になる)(例えばnew Errorまたはnew Promiseのための)いくつかのネイティブオブジェクトが空にされることを意味し、イベントが失われ、約束のない実装が境界を越えて働くことはできません。

ソリューションはそれを聞くためにpopup.jsでスクリプトにメッセージを返すようにchrome.runtime.sendMessageを使用することであり、chrome.runtime.onMessage.addListener

chrome.tabs.executeScript(
    tab.id, 
    { code: `(async function() { 
     // Do lots of things with await 
     let result = true; 
     chrome.runtime.sendMessage(result, function (response) { 
      console.log(response); // Logs 'true' 
     }); 
    })()` }, 
    async emptyPromise => { 

     // Create a promise that resolves when chrome.runtime.onMessage fires 
     const message = new Promise(resolve => { 
      const listener = request => { 
       chrome.runtime.onMessage.removeListener(listener); 
       resolve(request); 
      }; 
      chrome.runtime.onMessage.addListener(listener); 
     }); 

     const result = await message; 
     console.log(result); // Logs true 
    }); 

を私はしましたextended this into a function chrome.tabs.executeAsyncFunctionchrome-extension-asyncの一部、「promisifies」など全体API):

function setupDetails(action, id) { 
    // Wrap the async function in an await and a runtime.sendMessage with the result 
    // This should always call runtime.sendMessage, even if an error is thrown 
    const wrapAsyncSendMessage = action => 
     `(async function() { 
    const result = { asyncFuncID: '${id}' }; 
    try { 
     result.content = await (${action})(); 
    } 
    catch(x) { 
     // Make an explicit copy of the Error properties 
     result.error = { 
      message: x.message, 
      arguments: x.arguments, 
      type: x.type, 
      name: x.name, 
      stack: x.stack 
     }; 
    } 
    finally { 
     // Always call sendMessage, as without it this might loop forever 
     chrome.runtime.sendMessage(result); 
    } 
})()`; 

    // Apply this wrapper to the code passed 
    let execArgs = {}; 
    if (typeof action === 'function' || typeof action === 'string') 
     // Passed a function or string, wrap it directly 
     execArgs.code = wrapAsyncSendMessage(action); 
    else if (action.code) { 
     // Passed details object https://developer.chrome.com/extensions/tabs#method-executeScript 
     execArgs = action; 
     execArgs.code = wrapAsyncSendMessage(action.code); 
    } 
    else if (action.file) 
     throw new Error(`Cannot execute ${action.file}. File based execute scripts are not supported.`); 
    else 
     throw new Error(`Cannot execute ${JSON.stringify(action)}, it must be a function, string, or have a code property.`); 

    return execArgs; 
} 

function promisifyRuntimeMessage(id) { 
    // We don't have a reject because the finally in the script wrapper should ensure this always gets called. 
    return new Promise(resolve => { 
     const listener = request => { 
      // Check that the message sent is intended for this listener 
      if (request && request.asyncFuncID === id) { 

       // Remove this listener 
       chrome.runtime.onMessage.removeListener(listener); 
       resolve(request); 
      } 

      // Return false as we don't want to keep this channel open https://developer.chrome.com/extensions/runtime#event-onMessage 
      return false; 
     }; 

     chrome.runtime.onMessage.addListener(listener); 
    }); 
} 

chrome.tabs.executeAsyncFunction = async function (tab, action) { 

    // Generate a random 4-char key to avoid clashes if called multiple times 
    const id = Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); 

    const details = setupDetails(action, id); 
    const message = promisifyRuntimeMessage(id); 

    // This will return a serialised promise, which will be broken 
    await chrome.tabs.executeScript(tab, details); 

    // Wait until we have the result message 
    const { content, error } = await message; 

    if (error) 
     throw new Error(`Error thrown in execution script: ${error.message}. 
Stack: ${error.stack}`) 

    return content; 
} 

このexecuteAsyncFunctionは、このように呼び出すことができます。

const result = await chrome.tabs.executeAsyncFunction(
    tab.id, 
    // Async function to execute in the page 
    async function() { 
     // Do lots of things with await 
     return true; 
    }); 

Thischrome.tabs.executeScriptchrome.runtime.onMessage.addListenerを包み、tryでスクリプトをラップ - 約束を解決するためにchrome.runtime.sendMessageを呼び出す前finally

+0

私は約束の中にonMessageリスナーを追加して、それを' resolve'してタイマーの代わりに。 – wOxxOm

+0

@wOxxOm私は現在約束をしていないので、イベントリスナーを削除してクリーンアップする方法が見つかりませんでした。どのようにそれを行う上の任意のポインターですか?リンクされたGitHubプロジェクトへのPRも大歓迎です:-) – Keith

+0

すべてのクロムイベントは、同じ関数リファレンスを持つremoveListenerをサポートしています。 'foo.addListener(function bar(msg){foo.removeListener(bar); resolve(msg)})'(もちろんconstと矢印を使って書き直すことができます)。 – wOxxOm

関連する問題