一个网站可以调用浏览器扩展吗? [英] Can a site invoke a browser extension?

查看:154
本文介绍了一个网站可以调用浏览器扩展吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

有没有办法可以转身吗?我写了一个提供一组API的扩展,想要使用我的扩展的网站可以检测到它的存在,如果它存在,网站可以调用我的API方法,如 var extension = Extenion(foo ,bar)。例如:


  1. 在Chrome,Firefox和Safari中可以这样做吗? Google创建了一个名为BeautifierExtension的新扩展。它有一组API作为JS对象。
  2. 用户转到reddit.com。 Reddit.com检测到BeautifierExtension并通过调用 beautifer = Beautifier();


来调用API

请参阅#2 - 通常,它是检测匹配网站并更改网页的扩展程序。我有兴趣知道的是#2是否可能。

解决方案

由于Chrome引入了 manifest.json 文件中指定允许的域名:

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

使用 chrome。

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

最后,在 chrome.runtime.onMessageExternal

  chrome.runtime.onMessageExternal.addListener(
function(request,sender,sendResponse){
// verify`sender.url`,read`request `对象,回复`sednResponse(...)`...
});






如果您无权访问 externally_connectable 支持,原始答案如下:

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

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



这里的主要困难在于被注入到网页中的内容脚本不能直接改变JavaScript 执行环境。它们共享DOM,所以内容脚本和网页之间共享事件更改为DOM结构,但函数和变量不共享。例如:


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


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

  • 函数:如果内容脚本定义了一个新的全局函数 foo()(正如您在设置新的API时可能会尝试的那样)。由于 foo 仅存在于内容脚本的执行中,因此页面 不能 环境,而不是在页面的环境中。


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


    1. 在一个低级别,让你的API 基于事件。网页使用 dispatchEvent 激发自定义DOM事件,内容脚本通过 addEventListener 来监听它们,收到。这是一个简单的基于事件的存储API,网页可以使用扩展来存储数据:
      $ b content_script.js 在你的扩展中):

        //用于存储从API传入的内容的对象
      internalStorage = {} ;

      //使用键/值对数据监听myStoreEvent触发
      document.addEventListener('myStoreEvent',function(event){
      var dataFromPage = event.detail ;
      internalStorage [dataFromPage.key] = dataFromPage.value
      });

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

       函数sendDataToExtension(key,value){
      var dataObj = {key:key,value:value };
      var storeEvent = new CustomEvent('myStoreEvent',{detail:dataObj});
      document.dispatchEvent(storeEvent);
      }
      sendDataToExtension(hello,world);正如你所看到的,普通的网页正在触发内容脚本可以看到并作出反应的事件,因为他们共享DOM。这些事件附有数据,添加到 CustomEvent 构造函数。我这里的例子非常简单 - 一旦从页面获取了数据,您可以在内容脚本中做更多的事情(最有可能的是将其传递后台页面以供进一步处理)。
    2. 然而,这只是战斗的一半。在我上面的例子中,普通的网页必须自己创建 sendDataToExtension 。创建和激发自定义事件是相当冗长的(我的代码占用3行,相对简短)。你不想强制一个站点写一些神秘的事件触发代码来使用你的API。这个解决方案有点难以理解:在你的共享DOM中追加一个< script> 标签,这个标签将事件触发代码添加到主页的执行环境中。 p>

      里面 content_script.js:
      $ b

        / /从扩展的文件
      //注入脚本到主页面的执行环境
      var s = document.createElement('script');
      s.src = chrome.extension.getURL(myapi.js);
      document.documentElement.appendChild(s);

      定义在 myapi.js 将可以访问主页面。 (如果您使用的是manifest_version:2 ,您需要在清单列表中包含 myapi.js web_accessible_resources )。

      myapi.js:

       函数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 激发您的页面侦听的API准备好事件来解决此问题。



      myapi.js:

       函数sendDataToExtension(key,value){
      //如上面
      }

      //因为这个脚本正在运行,myapi.js已经加载,所以让页面知道
      var customAPILoaded = new CustomEvent('customAPILoaded');
      document.dispatchEvent(customAPILoaded);

      使用API​​的普通网页

        document.addEventListener('customAPILoaded',function(){
      sendDataToExtension(hello,world);
      // all API交互进入这里,现在API被加载...
      });


    4. 在加载时脚本可用性的另一个解决方案是设置 run_at 清单中内容脚本的属性为document_start像这样:

      manifest.json

       content_scripts:[
      {
      匹配:[https://example.com/*],
      js:[
      myapi.js
      ],
      run_at: document_start


      摘自 docs


      在document_start的情况下,文件被注入到来自css的任何文件之后,但是在任何其他DOM被构建或者其他任何脚本被运行之前。


      对于某些可能比API加载事件更合适,更省力的内容。


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

      b
      $ b

      myapi.js:

        function getDataFromExtension(key,回调){
      var reqId = Math.random()。toString(); //这个请求的唯一ID
      var dataObj = {key:key,reqId:reqId};
      var fetchEvent = new CustomEvent('myFetchEvent',{detail:dataObj});
      document.dispatchEvent(fetchEvent);

      //准备从内容脚本回复
      document.addEventListener('fetchResponse',function respListener(event){
      var data = event.detail;

      //检查这个请求是否为这个请求
      if(data.reqId == reqId){
      callback(data.value);
      document.removeEventListener(' fetchResponse',respListener);
      }
      }
      }

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

        //监听myFetchEvent key 
      //然后触发一个fetchResponse事件,回复
      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。 dispatchEve NT(fetchResponse);
      });

      普通网页

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

      reqId 多个请求一次,以便他们不读错误的答复。


      我认为这就是一切!所以,当你考虑到其他扩展也可以将监听器绑定到你的事件来窃听页面如何使用你的API的时候,并不是因为内心的淡淡,而可能是不值得的。我只知道这一切,因为我为一个学校项目做了一个概念证明密码学API(后来学到了与之相关的重大安全隐患)。总而言之:内容脚本可以侦听来自普通网页的自定义事件,脚本还可以为脚本文件添加功能,使网页更容易触发这些事件。内容脚本可以将消息传递给后台页面,然后存储,转换或传输消息中的数据。

      I am a newbie to the browser extension development and I understand the concept of browser extensions altering the page and injecting codes into it.

      Is there a way this direction can be turned around? I write an extension that provides a set of APIs, and web sites that want to use my extension can detect its presence and if it is present, the website can call my API methods like var extension = Extenion(foo, bar). Is this possible in Chrome, Firefox and Safari?

      Example:

      1. Google created a new extension called BeautifierExtension. It has a set of APIs as JS objects.

      2. User goes to reddit.com. Reddit.com detects BeautifierExtension and invoke the API by calling beautifer = Beautifier();

      See #2 - normally it's the extension that detects the matching sites and alter the pages. What I am interested to know is whether #2 is possible.

      解决方案

      Since Chrome introduced externally_connectable, this is quite easy to do in Chrome. First, specify the allowed domain in your manifest.json file:

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

      Use chrome.runtime.sendMessage to send a message from the page:

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

      Finally, listen in your background page with chrome.runtime.onMessageExternal:

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


      If you don't have access to externally_connectable support, the original answer follows:

      I'll answer from a Chrome-centric perspective, although the principles described here (webpage script injections, long-running background scripts, message passing) are applicable to virtually all browser extension frameworks.

      From a high level, what you want to do is inject a content script into every web page, which adds an API, accessible to the web page. When the site calls the API, the API triggers the content script to do something, like sending messages to the background page and/or send a result back to the content script, via asynchronous callback.

      The main difficulty here is that content scripts which are "injected" into a web page cannot directly alter the JavaScript execution environment of a page. They share the DOM, so events and changes to DOM structure are shared between the content script and the web page, but functions and variables are not shared. Examples:

      • DOM manipulation: If a content script adds a <div> element to a page, that will work as expected. Both content script and page will see the new <div>.

      • Events: If a content script sets up an event listener, e.g., for clicks on an element, the listener will successfully fire when the event occurs. If the page sets up a listener for custom events fired from the content script, they will be successfully received when the content script fires those events.

      • Functions: If the content script defines a new global function foo() (as you might try when setting up a new API). The page cannot see or execute foo, because foo exists only in the content script's execution environment, not in the page's environment.

      So, how can you set up a proper API? The answer comes in many steps:

      1. At a low-level, make your API event-based. The web page fires custom DOM events with dispatchEvent, and the content scripts listens for them with addEventListener, taking action when they are received. Here's a simple event-based storage API which a web page can use to have the extension to store data for it:

        content_script.js (in your extension):

        // 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
        });
        

        Non-extension web page, using your event-based API:

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

        As you can see, the ordinary web page is firing events that the content script can see and react to, because they share the DOM. The events have data attached, added in the CustomEvent constructor. My example here is pitifully simple -- you can obviously do much more in your content script once it has the data from the page (most likely pass it to the background page for further processing).

      2. However, this is only half the battle. In my example above, the ordinary web page had to create sendDataToExtension itself. Creating and firing custom events is quite verbose (my code takes up 3 lines and is relatively brief). You don't want to force a site to write arcane event-firing code just to use your API. The solution is a bit of a nasty hack: append a <script> tag to your shared DOM which adds the event-firing code to the main page's execution environment.

        Inside 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);
        

        Any functions that are defined in myapi.js will become accessible to the main page. (If you are using "manifest_version":2, you'll need to include myapi.js in your manifest's list of web_accessible_resources).

        myapi.js:

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

        Now the plain web page can simply do:

        sendDataToExtension("hello", "world");
        

      3. There is one further wrinkle to our API process: the myapi.js script will not be available exactly at load time. Instead, it will be loaded some time after page-load time. Therefore, the plain web page needs to know when it can safely call your API. You can solve this by having myapi.js fire an "API ready" event, which your page listens for.

        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);
        

        Plain web page using API:

        document.addEventListener('customAPILoaded', function() {
            sendDataToExtension("hello", "world");
            // all API interaction goes in here, now that the API is loaded...
        });
        

      4. Another solution to the problem of script availability at load time is setting run_at property of content script in manifest to "document_start" like this:

        manifest.json:

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

        Excerpt from docs:

        In the case of "document_start", the files are injected after any files from css, but before any other DOM is constructed or any other script is run.

        For some contentscripts that could be more appropriate and of less effort than having "API loaded" event.

      5. In order to send results back to the page, you need to provide an asynchronous callback function. There is no way to synchronously return a result from your API, because event firing/listening is inherently asynchronous (i.e., your site-side API function terminates before the content script ever gets the event with the API request).

        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 (in your extension):

        // 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);
        });
        

        ordinary web page:

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

        The reqId is necessary in case you have multiple requests out at once, so that they don't read the wrong responses.

      And I think that's everything! So, not for the faint of heart, and possibly not worth it, when you consider that other extensions can also bind listeners to your events to eavesdrop on how a page is using your API. I only know all this because I made made a proof-of-concept cryptography API for a school project (and subsequently learned the major security pitfalls associated with it).

      In sum: A content script can listen for custom events from an ordinary web page, and the script can also inject a script file with functions that makes it easier for web pages to fire those events. The content script can pass messages to a background page, which then stores, transforms, or transmits data from the message.

      这篇关于一个网站可以调用浏览器扩展吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆