Tuesday, March 23, 2010

Коммуникация расширений и веб страницы в Firefox.

Большинство расширений для firefox на целены на изменение веб страниц, убрать рекламу, добавить новый функционал. Как у разработчиков проблем с этим не возникает, до странице легко добраться из расширение. Иногда возникает обратная ситуация. Не обходимо, что после некоторых операций пользователя на странице данные были переданы в плагин. Напрямую этого сделать нельзя по причинам безопасности. Но есть способы как это можно сделать. В это статье мы рассмотрим каким образом организовать взаимодействие плагина и станице в обе стороны.

От расширения к веб-странице


Как правило после того станица загружена взаимодействие плагина со страницей сводиться к выполнению некоторого скрипта на ней. В самом простом случае можно создать нужный нам скрипт непосредственно на странице. Вот как это можно сделать для текущего странице в браузере:

var browser = gBrowser.selectedTab.linkedBrowser;
var document = browser.contentDocument;
var script = document.createElement("script");
script.type = "text/javascript";
script.innerHTML = "alert('Hello word!')";
document.body.appendChild(script);



Однако при реализации более сложного взаимодействия, могут возникнуть некоторые трудности c обработкой исключений, передачей параметров. Что бы их избежать нужно создать специальный div или другой элемент на странице, повесить на него обработчик событий отвечающий за выполнения скриптов на странице.

var element = document.getElementById("_console");
if (!element)
{
   element = document.createElement("div");
   element.setAttribute("id", "_console");
   element.setAttribute("style", "display:none");
   document.documentElement.appendChild(element);
}
element.addEventListener("runCommandEvent", function(event)
   {
      var element = event.target;
      var expr = JSON.parse(element.getAttribute("expr"));
      var data = element.getAttribute("data");
      evaluate(expr, data);
   }, true);

Что бы что, то выполнить на странице потребуется создать событие и предать необходимые данные

var document = tabBrowser.contentDocument;
var event = document.createEvent("Events");
event.initEvent("runCommandEvent", true, false);
var element = document.getElementById("_console");
element.setAttribute("expr", src);
element.setAttribute("data", JSON.strinfigy(data));
element.dispatchEvent(event);

Что бы данные корректно передались на станицу необходимо их стерилизовать в строку, из которого они потом будут восстановлены. Начиная с Firefox 3.5 это легко сделать с помощью встроенной поддержки JSON.

За исполнения скрипта отвечает функция evaluate в которой и происходит обработка исключений. Так же в контексте выполнения будет доступна переменная data c нужной информацией:

{
  try
  {
     var result = window.eval(expr);     
   }
   catch(exc)
   {
      var result = exc;
      result.source = expr;
   }
}

Этот способ предпочтителен для реализации сложного взаимодействия, тогда как в простых случаях достаточно ограничиться созданием нового скрипта на веб странице.

От страницы к расширению


Передавать что либо из веб странице в расширение удобно так же при помощи событий. Создадим обработчик для них

var browser = gBrowser.selectedTab.linkedBrowser;
var document = tabBrowser.contentDocument;
document.addEventListener("eventFromPage", eventListener, false, true);

Теперь необходимо создать событие. Данные помещаются в поле объекта document. Здесь так же проще передавать уже сереализованные данные.

var event = document.createEvent("Events");
event.initEvent("eventFromPage", true, false);
document._dataForExtension = JSON.stringify(someData);
document.dispatchEvent(event);

Следующий шаг - обработать событие и полученные данные. Если мы будем обращаться напрямую к полю _dataForExtension оно не будет доступно по соображением безопасности. Любой дом объект странице при доступе к нему из расширения обертывается в прокси. Получить доступ до полей объекта можно через wrappedJSObject. Таким образом обработчик событий получается следующим.

eventListener: function(event)
{
  var document = tabBrowser.contentDocument;
  var data = JSON.parse(document.wrappedJSObject._dataForExtension);
  callbackFromSrcipt(data);
}

Пару слов напоследок. При реализации данного подхода надо внимательно следить за тем, что бы обработчик был создан до того, как в его адрес начнут поступать события. К сожалению, это условие не всегда просто гарантировать.