网站可以调用浏览器扩展吗?

时间:2012-05-10 03:04:53

标签: javascript google-chrome google-chrome-extension firefox-addon safari-extension

我是浏览器扩展开发的新手,我理解浏览器扩展的概念,改变页面并将代码注入其中。

有没有办法可以扭转这个方向?我编写了一个提供一组API的扩展程序,想要使用我的扩展程序的网站可以检测到它的存在,如果它存在,网站可以调用我的API方法,如var extension = Extenion(foo, bar)。这可以在Chrome,Firefox和Safari中使用吗?

示例:

  1. Google创建了一个名为BeautifierExtension的新扩展程序。它有一组API作为JS对象。

  2. 用户访问reddit.com。 Reddit.com通过调用beautifer = Beautifier();

  3. 检测BeautifierExtension并调用API

    参见#2 - 通常它是检测匹配网站并更改页面的扩展名。我有兴趣知道的是#2是否可行。

1 个答案:

答案 0 :(得分:59)

自Chrome推出externally_connectable以来,这在Chrome中很容易实现。首先,在manifest.json文件中指定允许的域:

"externally_connectable": {
  "matches": ["*://*.example.com/*"]
}

使用chrome.runtime.sendMessage从页面发送消息:

chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
  function(response) {
    // ...
  });

最后,使用chrome.runtime.onMessageExternal收听您的背景页面:

chrome.runtime.onMessageExternal.addListener(
  function(request, sender, sendResponse) {
    // verify `sender.url`, read `request` object, reply with `sednResponse(...)`...
  });

如果您无法获得externally_connectable支持,原始答案如下:

我将从以Chrome为中心的角度回答,尽管此处描述的原则(网页脚本注入,长时间运行的后台脚本,消息传递)几乎适用于所有浏览器扩展框架。

从高层次来看,您要做的是在每个网页中注入content script,这会添加一个API,可供网页访问。当站点调用API时,API会触发内容脚本执行某些操作,例如通过异步回调将消息发送到后台页面和/或将结果发送回内容脚本。

这里的主要困难是内容脚本被注入"进入网页不能直接改变页面的JavaScript execution environment。它们共享DOM,因此在内容脚本和网页之间共享事件对DOM结构的更改,但不共享函数和变量。例子:

  • DOM操作:如果内容脚本向页面添加<div>元素,则该元素将按预期工作。内容脚本和页面都会看到新的<div>

  • 事件:如果内容脚本设置了事件侦听器(例如,对于元素的点击),则侦听器将在事件发生时成功触发。如果页面为从内容脚本触发的自定义事件设置了侦听器,则在内容脚本触发这些事件时将成功接收它们。

  • 功能:如果内容脚本定义了新的全局函数foo()(正如您在设置新API时可能尝试的那样)。页面无法查看或执行foo,因为foo仅存在于内容脚本的执行环境中,而不存在于页面环境中。

那么,您如何设置合适的API?答案有很多步骤:

  1. 在较低级别,请制作您的API event-based。该网页使用dispatchEvent触发自定义DOM事件,内容脚本使用addEventListener监听它们,并在收到它们时采取措施。这是一个简单的基于事件的存储API,网页可以使用它来扩展存储数据:

    content_script.js (在您的扩展程序中):

    // an object used to store things passed in from the API
    internalStorage = {};
    
    // listen for myStoreEvent fired from the page with key/value pair data
    document.addEventListener('myStoreEvent', function(event) {
        var dataFromPage = event.detail;
        internalStorage[dataFromPage.key] = dataFromPage.value
    });
    

    非扩展性网页,使用基于事件的API:

    function sendDataToExtension(key, value) {
        var dataObj = {"key":key, "value":value};
        var storeEvent = new CustomEvent('myStoreEvent', {"detail":dataObj});
        document.dispatchEvent(storeEvent);
    }
    sendDataToExtension("hello", "world");
    

    正如您所看到的,普通网页正在触发内容脚本可以查看和响应的事件,因为它们共享DOM。这些事件附有数据,添加在CustomEvent constructor中。我的示例非常简单 - 显然,您可以在内容脚本中从页面获取数据(最有可能pass itbackground page进行进一步处理)。

  2. 然而,这只是战斗的一半。在上面的示例中,普通网页必须自己创建sendDataToExtension。创建和触发自定义事件非常冗长(我的代码占用3行并且相对简短)。您不想强迫网站编写神秘的事件触发代码,只是为了使用您的API。该解决方案有点讨厌:向您的共享DOM添加<script>标记,将事件触发代码添加到主页面的执行环境中。

    content_script.js:

    // inject a script from the extension's files
    // into the execution environment of the main page
    var s = document.createElement('script');
    s.src = chrome.extension.getURL("myapi.js");
    document.documentElement.appendChild(s);
    

    主页可以访问myapi.js中定义的任何功能。 (如果您使用的是"manifest_version":2,则需要在web_accessible_resources的清单列表中加入myapi.js

    <强> myapi.js:

    function sendDataToExtension(key, value) {
        var dataObj = {"key":key, "value":value};
        var storeEvent = new CustomEvent('myStoreEvent', {"detail":dataObj});
        document.dispatchEvent(storeEvent);
    }
    

    现在普通网页可以简单地执行:

    sendDataToExtension("hello", "world");
    
  3. 我们的API流程还有一个进一步的皱纹myapi.js脚本在加载时将无法正常使用。相反,它将在页面加载时间后加载一段时间。因此,普通网页需要知道何时可以安全地调用您的API。您可以通过myapi.js触发&#34; API准备就绪来解决这个问题。您的网页收听的活动。

    <强> myapi.js:

    function sendDataToExtension(key, value) {
        // as above
    }
    
    // since this script is running, myapi.js has loaded, so let the page know
    var customAPILoaded = new CustomEvent('customAPILoaded');
    document.dispatchEvent(customAPILoaded);
    
    使用API​​

    普通网页

    document.addEventListener('customAPILoaded', function() {
        sendDataToExtension("hello", "world");
        // all API interaction goes in here, now that the API is loaded...
    });
    
  4. 加载时脚本可用性问题的另一个解决方案是将清单中内容脚本的run_at属性设置为"document_start",如下所示:

    <强>的manifest.json:

        "content_scripts": [
          {
            "matches": ["https://example.com/*"],
            "js": [
              "myapi.js"
            ],
            "run_at": "document_start"
          }
        ],
    

    摘自docs

      

    在&#34; document_start&#34;的情况下,文件是在来自css的任何文件之后,但在构造任何其他DOM或运行任何其他脚本之前注入的。

    对于某些内容脚本而言,这些内容脚本可能比加载&#34; API更加合适且省力。事件

  5. 为了将结果发送回到页面,您需要提供异步回调函数。无法从API同步返回结果,因为事件触发/侦听本质上是异步的(即,您的站点端API函数在内容脚本通过API请求获取事件之前终止)。

    <强> myapi.js:

    function getDataFromExtension(key, callback) {
        var reqId = Math.random().toString(); // unique ID for this request
        var dataObj = {"key":key, "reqId":reqId};
        var fetchEvent = new CustomEvent('myFetchEvent', {"detail":dataObj});
        document.dispatchEvent(fetchEvent);
    
        // get ready for a reply from the content script
        document.addEventListener('fetchResponse', function respListener(event) {
            var data = event.detail;
    
            // check if this response is for this request
            if(data.reqId == reqId) {
                callback(data.value);
                document.removeEventListener('fetchResponse', respListener);
            }
        }
    }
    

    content_script.js (在您的扩展程序中):

    // listen for myFetchEvent fired from the page with key
    // then fire a fetchResponse event with the reply
    document.addEventListener('myStoreEvent', function(event) {
        var dataFromPage = event.detail;
        var responseData = {"value":internalStorage[dataFromPage.key], "reqId":data.reqId};
        var fetchResponse = new CustomEvent('fetchResponse', {"detail":responseData});
        document.dispatchEvent(fetchResponse);
    });
    

    普通网页:

    document.addEventListener('customAPILoaded', function() {
        getDataFromExtension("hello", function(val) {
            alert("extension says " + val);
        });
    });
    

    如果您一次发出多个请求,reqId是必要的,这样他们就不会阅读错误的回复。

  6. 我认为这就是一切!因此,当您考虑其他扩展也可以将侦听器绑定到您的事件以窃听页面如何使用您的API时,不适合胆小的人,也可能不值得。我只知道这一切,因为我为学校项目制作了一个概念验证加密API(并随后了解了与之相关的主要安全隐患)。

    总之:内容脚本可以从普通网页侦听自定义事件,脚本还可以注入一个脚本文件,其中包含的功能使网页更容易触发这些事件。内容脚本可以将消息传递到后台页面,然后后台页面存储,转换或传输消息中的数据。