特权页面和非特权页面之间相互通信

这篇翻译不完整。请帮忙从英语翻译这篇文章

普通页面(非特权)向chrome发送数据

有一种很简单的方法从普通页面(非特权)向浏览器扩展发送数据,就是使用DOM事件。在你扩展里面的browser.xul写一个监听事件的方法。这里我们叫这个事件为“MyExtensionEvent”。 

var myExtension = {
  myListener: function(evt) {
    alert("Received from web page: " +
          evt.target.getAttribute("attribute1") + "/" + 
          evt.target.getAttribute("attribute2"));
  }
}
document.addEventListener("MyExtensionEvent", function(e) { myExtension.myListener(e); }, false, true);
// The last value is a Mozilla-specific value to indicate untrusted content is allowed to trigger the event.

来自普通页面(非特权)的数据会传递给attribute1 和 attribute2。 监听器会触发alert()然后从发送页面的数据, 页面的代码如下:

var element = document.createElement("MyExtensionDataElement");
element.setAttribute("attribute1", "foobar");
element.setAttribute("attribute2", "hello world");
document.documentElement.appendChild(element);

var evt = document.createEvent("Events");
evt.initEvent("MyExtensionEvent", true, false);
element.dispatchEvent(evt);

这段代码会创建一个任意的元素-- <MyExtensionDataElement/> -- 然后插入页面的DOM中。赋给该元素两个任意的属性值。你可以按照你喜欢的方式命名,但是这里我们命名为attribute1 和 attribute2。最后代码会创建一个名字为MyExtensionEvent的事件,这类似于标准的DOM单击事件可以用onclick 捕捉到。这个页面的元素触发事件后,事件会从页面冒泡一直都到达插件层(特权代码),直到你的监听器扑捉到它,然后读取这个DOM元素的属性

(为了确保别人不会使用相同的事件去实现其他事情,可以使用命名空间的方式命名 <MyExtensionDataElement/>  ,检查事件处理函数对页面的性能影响,或者根据DOM的标准使用它自己的命名空间(XML字符规则)初始化 initEvent() : "强烈建议第三方添加自己的事件时使用自己的前缀,以避免混乱和减少与其他新事件冲突的概率。")

在这种情况下你的插件overlay不能直接和browser.xul直接产生作用,例如在侧边栏,它可能更容易的监听到最顶层的文档。如下所示 (参见: accessing the elements of the top-level document from a child window).

var mainWindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                  .getInterface(Components.interfaces.nsIWebNavigation)
                  .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
                  .rootTreeItem
                  .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                  .getInterface(Components.interfaces.nsIDOMWindow);
mainWindow.document.addEventListener("MyExtensionEvent", function(e) { myExtension.myListener(e); }, false, true);

如果你需要发送大量的数据,可考虑用CDATA片段去代替简单的DOM元素属性。

提示:如果你使用HTML5中的postMessage()发送消息的方法,在非特权代码发送特权代码,那么你需要在特权代码中的事件监听器的最后加上 'true'(事件捕获),这样你的消息就可以被接受。

document.addEventListener("message", function(e) { yourFunction(e); }, false, true);

 

从chrome向普通页面发送数据

为了去应答这个页面(例如:return code),你的插件要可以设置一个属性或者接触到这个目标元素(例如这个<MyExtensionDataElement/>)的子元素。

你可以选择清除这个创建的元素,或者在页面加载完后创建元素一次,然后每次都使用它。

另一个选择是从插件发送一个返回的事件给页面,上面的例子可以使用相同的原则去做。

这里插件只有一个,但是却有很多页面。So to trigger the right event on the right page we have to tell the extension which page to call. The information we need for that is contained in evt.target.ownerDocument.

We can extend the above example with some data transfer from the extension to the web page. In the following code sample 2 methods are combined: Setting an extra attribute in the original event target element, and creating a new event message with a new event target element. For this to work we need to define the original target element globally. We need a new event trigger in the web page and some code to show the event message actually arrived. In the extension we have to dispatch an event message to the right web page.

The code containing the callback could look like this:

In the extension:

var myExtension = 
{
  myListener: function(evt) 
  {
    alert("Received from web page: " + 
           evt.target.getAttribute("attribute1") + "/" + 
           evt.target.getAttribute("attribute2"));

/* the extension answers the page*/
    evt.target.setAttribute("attribute3", "The extension");
   
    var doc = evt.target.ownerDocument;
   
    var AnswerEvt = doc.createElement("MyExtensionAnswer");
    AnswerEvt.setAttribute("Part1", "answers this.");

    doc.documentElement.appendChild(AnswerEvt);

    var event = doc.createEvent("HTMLEvents");
    event.initEvent("MyAnswerEvent", true, false);
    AnswerEvt.dispatchEvent(event);
  }
}

document.addEventListener("MyExtensionEvent", function(e) { myExtension.myListener(e); }, false, true);
// The last value is a Mozilla-specific value to indicate untrusted content is allowed to trigger the event.

In the web page:

document.addEventListener("MyAnswerEvent",function(e) { ExtensionAnswer(e); },false);

var element;

function CallExtension()
{
  var element = document.createElement("MyExtensionDataElement");
  element.setAttribute("attribute1", "foobar");
  element.setAttribute("attribute2", "hello world");
  document.documentElement.appendChild(element);
  var evt = document.createEvent("Events");
  evt.initEvent("MyExtensionEvent", true, false);
  element.dispatchEvent(evt);
}

function ExtensionAnswer(EvtAnswer)
{
  alert(element.getAttribute("attribute3") + " " +
        EvtAnswer.target.getAttribute("Part1"));
}

Basic example of similar idea, extension passes information via attributes and fires event on div in page, here.

Chromium-like messaging: json request with json callback

Web page:

<html>
  <head>
    <script>
      var something = {
        send_request: function(data, callback) { // analogue of chrome.extension.sendRequest
          var request = document.createTextNode(JSON.stringify(data));

          request.addEventListener("something-response", function(event) {
            request.parentNode.removeChild(request);

            if (callback) {
              var response = JSON.parse(request.nodeValue);
              callback(response);
            }
          }, false);

          document.head.appendChild(request);

          var event = document.createEvent("HTMLEvents");
          event.initEvent("something-query", true, false);
          request.dispatchEvent(event);
        },

        callback: function(response) {
          return alert("response: " + (response ? response.toSource() : response));
        }
      }
    </script>
  </head>
  <body>
    <button onclick="return something.send_request({foo: 1}, something.callback)">send {foo: 1} with callback</button>
    <button onclick="return something.send_request({baz: 3}, something.callback)">send {baz: 3} with callback</button>
    <button onclick="return something.send_request({mozilla: 3})">send {mozilla: 3} without callback</button>
    <button onclick="return something.send_request({firefox: 4}, something.callback)">send {firefox: 4} with callback</button>
  </body>
</html>

Overlay on browser.xul in your extension:

var something = {
  listen_request: function(callback) { // analogue of chrome.extension.onRequest.addListener
    document.addEventListener("something-query", function(event) {
      var node = event.target;
      if (!node || node.nodeType != Node.TEXT_NODE)
        return;

      var doc = node.ownerDocument;
      callback(JSON.parse(node.nodeValue), doc, function(response) {
        node.nodeValue = JSON.stringify(response);

        var event = doc.createEvent("HTMLEvents");
        event.initEvent("something-response", true, false);
        return node.dispatchEvent(event);
      });
    }, false, true);
  },
 
  callback: function(request, sender, callback) {
    if (request.foo) {
      return setTimeout(function() {
      callback({bar: 2});
      }, 1000);
    }
 
    if (request.baz) {
      return setTimeout(function() {
      callback({quux: 4});
      }, 3000);
    }
 
    if (request.mozilla) {
      return alert("alert in chrome");
    }

    return callback(null);
  }
}

something.listen_request(something.callback);

Message Passing in Chromium

Sending structured data

The above mechanisms use element attributes and are thus only strings. You may want to transfer objects. Gecko prevents chrome to access custom object properties added by the content, because that can create security holes. A workaround is to treat the communication between webpage and chrome as a normal network protocol and use XML.

With element attributes and E4X, this is fairly easy. You do need to convert your data to/from E4X objects, though. And your chrome needs to carefully check every value passed (you need to do that either way).

var targetDoc = null;

function onLoad() {
  var iframe = document.getElementById("contentiframe");
  targetDoc = iframe.contentDocument;
  iframe.contentWindow.addEventListener("newStuff", receiveStuffFromPage, false);
}

function receiveStuffFromPage(event) {
  var uc = getEventData(event); // uc = unchecked data in form of E4X XML
  var stuff = {};
  stuff.id = sanitize.integer(uc.@id);
  stuff.name = sanitize.label(uc.@name);
}

function sendSomethingToPage (something) {
  var somethingXML = <something/>; // |something| object as E4X XML
  somethingXML.@id = something.id;
  somethingXML.@weight = something.weight;
  sendMsg("sendSomething", somethingXML);
}

/**
 * Send msgs from chrome to the page
 * @param type {String} the event type. The receiver needs to use that
 * when doing addEventListener(type, ...)
 * @param dataXML {E4X} the data or detail
 */
function sendMsg(type, dataXML) {
  var el = targetDoc.body;
  el.setAttribute("eventDataToPage", dataXML ? dataXML.toString() : "");
  var event = targetDoc.createEvent("Event")
  event.initEvent(type, true, true);
  el.dispatchEvent(event);
}

/**
 * Verifies that the event is indeed coming from our page
 * as expected, and returns the data for that event.
 * @returns {E4X} the (unchecked) detail data from the page.
 * You must check the data.
 * @see <https://developer.mozilla.org/en-US/docs/Code_snippets/
 * Interaction_between_privileged_and_non-privileged_pages#Security_notes>
 */
function getEventData(event) {
  if (event.target.ownerDocument != targetDoc)
    throw "event from unexpected source";
  return new XML(event.target.getAttribute("eventDataFromPage"));
}

Security notes

  • Never invoke the web page's JavaScript functions from your extension - doing this increases the chance of creating a security hole, where a malicious web page can trick the browser to run its code with extended privileges (just like your extension) with, for example, the ability to delete local files.
  • It is highly recommended to check the source of the event (via event.target.ownerDocument.location) and make your extension ignore any events from pages not from your server.

Resources

Mozillazine Forum Discussion

Communication between HTML and your extension

See also

<textbox id="dataIpnutCount" style="display:none" type="text"></textbox>

文档标签和贡献者

 此页面的贡献者: watership
 最后编辑者: watership,