【问题标题】:How to call a function from injected script?如何从注入的脚本中调用函数?
【发布时间】:2016-03-23 07:02:26
【问题描述】:

这是我的 contentScript.js 中的代码:

function loadScript(script_url)
  {
      var head= document.getElementsByTagName('head')[0];
      var script= document.createElement('script');
      script.type= 'text/javascript';
      script.src= chrome.extension.getURL('mySuperScript.js');
      head.appendChild(script);
      someFunctionFromMySuperScript(request.widgetFrame);// ReferenceError: someFunctionFromMySuperScript is not defined
  }

但是从注入的脚本调用函数时出现错误:

ReferenceError: someFunctionFromMySuperScript 未定义

有没有办法在不修改mySuperScript.js的情况下调用这个函数?

【问题讨论】:

标签: javascript google-chrome google-chrome-extension


【解决方案1】:

您的代码存在多个问题:

  1. 如您所见,注入脚本 (mySuperScript.js) 中的函数和变量对内容脚本 (contentScript.js) 不直接可见。那是因为这两个脚本运行在不同的execution environments。
  2. 插入带有通过src 属性引用的脚本的<script> 元素不会立即导致脚本执行。因此,即使脚本在同一环境中运行,您仍然无法访问它。

要解决这个问题,首先考虑是否真的需要在页面中运行mySuperScript.js。如果您不从页面本身访问任何 JavaScript 对象,那么您不需要注入脚本。您应该尽量减少在页面本身中运行的代码量以避免冲突。

如果您不必运行页面中的代码,则在contentScript.js 之前运行mySuperScript.js,然后任何函数和变量都立即可用(像往常一样,via the manifest 或programmatic injection)。 如果由于某种原因确实需要动态加载脚本,那么您可以在web_accessible_resources 中声明它并使用fetch 或XMLHttpRequest 加载脚本,然后eval 在内容脚本的上下文中运行它。

例如:

function loadScript(scriptUrl, callback) {
    var scriptUrl = chrome.runtime.getURL(scriptUrl);
    fetch(scriptUrl).then(function(response) {
        return response.text();
    }).then(function(responseText) {
        // Optional: Set sourceURL so that the debugger can correctly
        // map the source code back to the original script URL.
        responseText += '\n//# sourceURL=' + scriptUrl;
        // eval is normally frowned upon, but we are executing static
        // extension scripts, so that is safe.
        window.eval(responseText);
        callback();
    });
}

// Usage:
loadScript('mySuperScript.js', function() {
    someFunctionFromMySuperScript();
});

如果您确实必须从脚本调用页面中的函数(即mySuperScript.js 必须绝对在页面上下文中运行),那么您可以注入另一个脚本(通过Building a Chrome Extension - Inject code in a page using a Content script 中的任何技术)然后将消息传递回内容脚本(例如using custom events)。

例如:

var script = document.createElement('script');
script.src = chrome.runtime.getURL('mySuperScript.js');
// We have to use .onload to wait until the script has loaded and executed.
script.onload = function() {
    this.remove(); // Clean-up previous script tag
    var s = document.createElement('script');
    s.addEventListener('my-event-todo-rename', function(e) {
        // TODO: Do something with e.detail
        // (= result of someFunctionFromMySuperScript() in page)
        console.log('Potentially untrusted result: ', e.detail);
        // ^ Untrusted because anything in the page can spoof the event.
    });
    s.textContent = `(function() {
        var currentScript = document.currentScript;
        var result = someFunctionFromMySuperScript();
        currentScript.dispatchEvent(new CustomEvent('my-event-todo-rename', {
            detail: result,
        }));
    })()`;

    // Inject to run above script in the page.
    (document.head || document.documentElement).appendChild(s);
    // Because we use .textContent, the script is synchronously executed.
    // So now we can safely remove the script (to clean up).
    s.remove();
};
(document.head || document.documentElement).appendChild(script);

(在上面的示例中,我使用的是 template literals,Chrome 41+ 支持)

【讨论】:

    【解决方案2】:

    只要someFunctionFromMySuperScript 函数是全局的就可以调用它,但是你需要等待代码实际加载。

    function loadScript(script_url)
      {
          var head= document.getElementsByTagName('head')[0];
          var script= document.createElement('script');
          script.type= 'text/javascript';
          script.src= chrome.extension.getURL('mySuperScript.js');
          script.onload = function () {
              someFunctionFromMySuperScript(request.widgetFrame);         
          }
          head.appendChild(script);
      }
    

    你也可以使用jQuery的getScript方法。

    【讨论】:

    • ..假设脚本在"web_accessible_resources"
    • 它不起作用,someFunctionFromMySuperScript 仍然没有定义@Xan
    • @askona 你说得对,那是行不通的。稍等。
    【解决方案3】:

    这不起作用,因为您的内容脚本和注入脚本live in different contexts:您注入页面的内容是在页面上下文中。

    1. 如果您只想将代码动态加载到内容脚本上下文中,则无法从内容脚本中执行此操作 - 您需要让后台页面代表您执行 executeScript。

      // Content script
      chrome.runtime.sendMessage({injectScript: "mySuperScript.js"}, function(response) {
        // You can use someFunctionFromMySuperScript here
      });
      
      // Background script
      chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
        if (message.injectScript) {
          chrome.tabs.executeScript(
            sender.tab.id,
            {
              frameId: sender.frameId},
              file: message.injectScript
            },
            function() { sendResponse(true); }
          );
          return true; // Since sendResponse is called asynchronously
        }
      });
      
    2. 如果您需要在页面上下文中注入代码,那么您的方法是正确的,但您不能直接调用它。使用其他方式与之通信,例如custom DOM events。

    【讨论】:

    • 感谢您的回复!我使用第一种方法从 background.js 运行 myScript。但不幸的是,myScript(将新的 div 添加到 DOM)除了 NewTab 页面之外的任何地方都可以使用,修改 NewTab 页面 DOM 的唯一方法是从内容脚本中进行。所以我需要从内容脚本的上下文中执行 myScript 。 Its possible to do this way: **eval(myScript.plain_code); someFunctionFromMySuperScript (); ** This works fine, but im 试图找到一种方法来避免使用 eval。
    • 如果您提供给它的唯一内容是您自己的文件,则无需避免使用 eval。当输入不受信任时,评估是邪恶的。但是话又说回来,我看不出您如何在chrome: 页面上执行任何操作——这应该是不可能的。您应该查看overriding New Tab page 而不是尝试修改现有的(尽管不建议进行小的修改)。
    • 不涉及后台页面的更简单的解决方案是使用 fetch 或 XMLHttpRequest 然后window.eval。
    • @RobW 我鼓励您将其发布为答案!不过,它可能需要可通过网络访问。
    猜你喜欢
    • 1970-01-01
    • 2019-08-04
    • 1970-01-01
    • 2019-12-24
    • 1970-01-01
    • 2021-08-11
    • 2019-12-29
    • 2010-11-15
    • 2018-04-23
    相关资源
    最近更新 更多