barbitoff programmer`s blog

Здесь я публикую заметки из программерской жизни: грабли, на которые мне случилось наступить, проблемы, для которых было найдено элегантное (или не очень) решение, а также все, с чем мне пришлось столкнуться и чем хотелось бы поделиться =)
PS Если хотите меня поблагодарить - на странице есть 3 места, чтобы это сделать =)
Показаны сообщения с ярлыком Gecko. Показать все сообщения
Показаны сообщения с ярлыком Gecko. Показать все сообщения

вторник, 6 декабря 2011 г.

Расширение Firefox: Получение пути к файлу xpi-пакета расширения из JavaScript

Задача:
Из JavaScript-кода расширения Firefox получить путь (полноценный абсолютный путь файловой системы) к какому-либо файлу из xpi-пакета приложения, который при установке расширения распаковывается в AppData пользователя. Например, пусть нужен путь к файлу ext.dll, размещенного в папке "components/" xpi-архива, для его последующей загрузки функцией ctypes.open() библиотеки ctypes.

Решение:
Для версий Firefox 4.0+ (Gecko 2.0 и выше) имеется компонент AddonManager, предназначенный для работы с расширениями, и, в частности, позволяющий получать абсолютные пути к любым ресурсам расширения следующим образом:
addonId = "myaddon@somesite.org";// id, указанный в теге <em:id> install.rdf расширения
Components.utils.import("resource://gre/modules/AddonManager.jsm");
AddonManager.getAddonByID(addonId, function(addon)
{
var uri = addon.getResourceURI("components/ext.dll");
if (uri instanceof Components.interfaces.nsIFileURL && uri.file.path)
{
// работаем с путем uri.file.path, например, сохраняем его в какой-нибудь переменной, видимой снаружи
}
else
console.debug(addonId+": getAddonByID callback: uri is not Components.interfaces.nsIFileURL or uri.file.path is null or empty");
});
Однако, если Вам необходимо, чтобы Ваше расширение работало и в Firefox 3.6, придется изменить подход (вернее, можно использовать код, приведенный выше, для новых FF, и отдельный код для 3.6). Вызвана такая необходимость отсутствием в 3.6 AddonManager`а. Выход следующий: разместить файл, к которому нужно получить доступ, в chrome-реестре браузера, после чего воспользоваться преобразованием Chrome URI -> путь файловой системы. Размещение файла в chrome-реестре описывается в chrome.manifest файле расширения. В моем случае оно выглядело так (я продублировал нужную мне dll-ку, поместив её также в chrome/content/ xpi-пакета):
content     myaddon     chrome/content/
Теперь моя dll-ка доступна по chrome-пути "chrome://myaddon/content/ext.dll" (можно проверить, набрав такой URL в браузере). Для того, чтобы получить к ней абсолютный путь файловой системы, понадобится чуть больше кода, чем для 2ого Gecko (для работы данного кода нужен также енкодер/декодер URL, код которого я приводил тут):
/**
* Создает объект URI (реализующий интерфейс nsIURI) из переданной строки-URI, и, опционально, кодировки и базового URI
*/
function makeURI(aURL, aOriginCharset, aBaseURI)
{
var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
return ioService.newURI(aURL, aOriginCharset, aBaseURI);
}
/**
* Получает путь в фаловой системе к объекту, хранимому в chrome-реестре FF.
*/
function getFilepathFromChromeURL(chromeURL)
{
var chromeRegistry = Components.classes["@mozilla.org/chrome/chrome-registry;1"].getService(Components.interfaces.nsIChromeRegistry);
var convertedURI;
try
{
convertedURI = chromeRegistry.convertChromeURL(makeURI(chromeURL,null,null));
}
catch(ex)
{
return false; }
if(convertedURI.scheme!="file")
return false;
return Url.decode(convertedURI.path.substring(1));
}
myPath = getFilepathFromChromeURL("chrome://myaddon/content/ext.dll");
В итоге в переменной myPath мы получим абсолютный путь к искомому файлу или false при ошибке.

пятница, 28 октября 2011 г.

js-ctypes в Firefox 3.6

В Firefox 3.6 и Gecko 1.9.2 js-stypes ещё был немного недоразвит, но всё же работоспособен.
Для передачи C-функции параметров-строк (или для возврата строк), использующих Юникод (т.е. wchar на стороне C), можно использовать ctypes.ustring вместо появившегося позже типа ctypes.jschar.ptr.
Также в 3.6 нет вообще указателей, т.е. нельзя получить из типа указатель на него, обратившись к полю ptr, и мне кажется, что обойти это ограничение нельзя.
Для типов ctypes.int и ctypes.long имеются аналоги ctypes.int16_t и ctypes.int32_t.

вторник, 25 октября 2011 г.

Создание расширения Firefox на C++ c использованием XPCOM

Внимание: использование XPCOM уже не рекомендуется разработчиками Mozilla (как минимум из-за того, что придется перекомпилировать бинарные компоненты с выходом новой версии FF, которые теперь будут выходить часто). В версиях FF 4+ можно использовать гораздо более простой способ - js-ctypes (https://developer.mozilla.org/en/js-ctypes/Using_js-ctypes). js-ctypes, с небольшими изменениями, работает даже в FF 3.6.20 (например, там нет ctypes.char, но есть ctypes.ustring).

Уф, один рабочий день и наконец-то получилось понять, как же все-таки писать расширения для FF с использованием XPCOM. MDN оказался куда хуже чем я ожидал - туториалы все древние и не работают с новыми версиями xulrunner-sdk, а то, что удалось накопать по новому движку Gecko2 - толком не описано, поэтому пришлось искать решение практически методом тыка.

Задача для начала простая - написать расширение, к которому можно было обратиться из JavaScript`а на веб-странице  вызвать метод echo, принимающий на входе строку и возвращающий её же.

Для начала качаем Gecko SDK, он же xulrunner-sdk: http://releases.mozilla.org/pub/mozilla.org/xulrunner/releases/7.0.1/sdk/xulrunner-7.0.1.en-US.win32.sdk.zip. Затем - проект-пример: https://developer.mozilla.org/samples/xpcom/xpcom-test.zip. Проект создан для старой версии xulrunner, поэтому компилироваться не будет, но всё же удобнее использовать его чем начинать с нуля. 
Пусть xulrunner-sdk распакован в "O:\xulrunner-sdk", а проект-пример - в O:\xpcom-test.

Во-первых, нужно описать интерфейс будущего расширения на языке idl (это не майкросовтовский IDL, а мозилловский собственный). Создаем файл (пусть он будет называться echo.idl, его нужно положить в O:\xpcom-test) со следующим содержимым (uuid можно сгенерировать любым генератором UUID):
#include "nsISupports.idl"
[scriptable, uuid(27110427-209a-4f4b-b829-8ca5ebe356a6)]
interface ISpecialThing : nsISupports
{
 AString echo(in AString srcStr);
};
В проект нужно внести следующие изменения:
  • Открываем xpcom-test/xpidl-build.bat и меняем его содержимое на:
    ..\xulrunner-sdk\bin\xpidl.exe -m header -I..\xulrunner-sdk\idl echo.idl
    ..\xulrunner-sdk\bin\xpidl.exe -m typelib -I..\xulrunner-sdk\idl echo.idl
    Запускаем батник, находясь в O:\xpcom-test (создадутся файлы echo.xpt и echo.h).
  • Открываем проект xpcom-test (я использовал MS VS 2005: во-первых, другого под рукой не нашлось, да и проект подойдет для него без конвертации). 
    • Заходим в свойства проектаC/C++ > General > Additional Include Directories, меняем на "..\xulrunner-sdk\include".
    • Затем - в Linker > General > additional Library Directories, ставим "..\xulrunner-sdk\lib"
    • Linker > Input > Additional Dependencies, "nspr4.lib xpcom.lib xpcomglue_s.lib mozalloc.lib"
  • Исключаем из проекта все файлы, кроме созданного нами idl. В свойствах idl-файла отменяем его компиляцию ("Exclude from build" = Yes). Включаем в проект созданный в пункте 1 h-файл с заголовком интерфейса. В комментариях этот файл содержит заготовку для класса, реализующего этот интерфейс.
  • Создаем класс, реализующий описанный в h-файле интерфейс. Пусть он будет называться echoImpl. Ниже приведен код заголовочного файла этого класса:
#include "echo.h"
#include "nsStringAPI.h"
#define ECHO_CONTRACTID "@some.domain.com/echosample;1"
#define ECHO_CLASSNAME "echosample"
#define ECHO_CID { 0xe99152f7, 0x73f7, 0x45d3, { 0x8e, 0x94, 0x26, 0x3e, 0x21, 0x10, 0x63, 0x57 } }
static const nsCID kEchoCID = IACSIGN_CID;
class echoImpl: public ISpecialThing
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_ISPECIALTHING
  echoImpl();
  ~echoImpl();
};
Define`ами в данном файле задает 3 важных параметра будущего компонента:  
    • ECHO_CONTRACTID - contract id компонента, имеющий вид домен/модуль/компонент;версия и используемый для идентификации компонента в массиве Components.classes при обращении из JavaScript.
    • ECHO_CLASSNAME - имя класса компонента (не нашел пока, где оно используется при эксплуатации компонента)
    •  ECHO_CID - уникальный GUI класса компонент. Сгенерировать его можно любым средством генерации GUID (например, http://www.guidgenerator.com/), после чего представить в указанном виде.
Теперь - само тело класса:
#include "echoImpl.h"
#include "nsStringAPI.h"
#include "nsIClassInfoImpl.h"
#include "nsMemory.h"
NS_IMPL_CLASSINFO(echoImpl, NULL, 0, ECHO_CID)
NS_IMPL_ISUPPORTS1_CI(echoImpl, ISpecialThing) 
echoImpl::echoImpl(){}
echoImpl::~echoImpl(){} 
NS_IMETHODIMP echoImpl::echo(const nsAString & srcStr, nsAString & _retval NS_OUTPARAM)
{
return NS_StringCopy(_retval,srcStr);
}
Тут все предельно просто - пара макросов и собственно метод echo.
  • Теперь необходимо описать модуль - код, который будет сообщать FF о написанном нами модуле. Создаем файл echoModule.cpp со следующим наполнением (код взят из примера http://mxr.mozilla.org/mozilla-central/source/xpcom/sample/, который, правда, не является 100% рабочим):
#include "echoImpl.h"
#include "mozilla/ModuleUtils.h"
#include "nsIClassInfoImpl.h"
////////////////////////////////////////////////////////////////////////
// With the below sample, you can define an implementation glue
// that talks with xpcom for creation of component nsSampleImpl
// that implement the interface nsISample. This can be extended for
// any number of components.
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// Define the contructor function for the object nsSampleImpl
//
// What this does is defines a function nsSampleImplConstructor which we
// will specific in the nsModuleComponentInfo table. This function will
// be used by the generic factory to create an instance of nsSampleImpl.
//
// NOTE: This creates an instance of nsSampleImpl by using the default
// constructor nsSampleImpl::nsSampleImpl()
//
NS_GENERIC_FACTORY_CONSTRUCTOR(echoImpl);
// The following line defines a kNS_SAMPLE_CID CID variable.
NS_DEFINE_NAMED_CID(ECHO_CID);
// Build a table of ClassIDs (CIDs) which are implemented by this module. CIDs
// should be completely unique UUIDs.
// each entry has the form { CID, service, factoryproc, constructorproc }
// where factoryproc is usually NULL.
nsresult myF (nsISupports* aOuter,
                 const nsIID& aIID,
                 void** aResult) {
*aResult = new echoImpl();
return NS_OK;
}
static const mozilla::Module::CIDEntry kSampleCIDs[] = {
{ &kEchoCID , false, NULL, myF },
    { NULL }
};
// Build a table which maps contract IDs to CIDs.
// A contract is a string which identifies a particular set of functionality. In some
// cases an extension component may override the contract ID of a builtin gecko component
// to modify or extend functionality.
static const mozilla::Module::ContractIDEntry kSampleContracts[] = {
    { ECHO_CONTRACTID , &kEchoCID },
    { NULL }
};
// Category entries are category/key/value triples which can be used
// to register contract ID as content handlers or to observe certain
// notifications. Most modules do not need to register any category
// entries: this is just a sample of how you'd do it.
// @see nsICategoryManager for information on retrieving category data.
static const mozilla::Module::CategoryEntry kSampleCategories[] = {
    //{ "my-category", "my-key", ECHO_CONTRACTID },
    { NULL }
};
static const mozilla::Module kSampleModule = {
    mozilla::Module::kVersion,
    kSampleCIDs,
    kSampleContracts,
    kSampleCategories
};
// The following line implements the one-and-only "NSModule" symbol exported from this
// shared library.
NSMODULE_DEFN(nsSampleModule) = &kSampleModule;
// The following line implements the one-and-only "NSGetModule" symbol
// for compatibility with mozilla 1.9.2. You should only use this
// if you need a binary which is backwards-compatible and if you use
// interfaces carefully across multiple versions.
// NS_IMPL_MOZILLA192_NSGETMODULE(&kSampleModule)

Здесь стоит обратить внимание на функцию myF - она возвращает объект созданного ранее класса echoImpl. В примере с MDN её не было (точнее имя-то функции было, а вот тела - нет).

Всё, проект можно компилировать. Если всё пройдет ок, то на выходе получим dll-файл, и представляющий собой компонент (к совокупности с полученным ранее xpt-файлом, описывающим его интерфейс).

Теперь дополнение необходимо добавить в FF. Для этого понадобиться xpi-файл со следующей структурой:
  • components/ - директория, куда нужно положить колченные xpt и dll файлы
  • chrome.manifest - файл реестра FF, описывающий компонент, со следующим содержимым:
binary-component components/echo.dll
interfaces components/echo.xpt
component {99152f77-3f74-5d38-e942-63e21106357} components/echo.dll
Здесь в фигурных скобках указан GUID, установленный в коде компонента с помощью константы ECHO_CID.

  •  install.rdf - файл, описывающий компонент и необходимый для его установки. Минимальное содержимое файла следующее:
<?xml version="1.0" encoding="UTF-8"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
  <Description about="urn:mozilla:install-manifest">
    <em:id>echo@demo.com</em:id>
    <em:type>2</em:type>
    <em:name>Echo echo</em:name>
    <em:version>0.2.1</em:version>
    <em:creator>barbitoff</em:creator>
    <em:contributor/>
    <em:description>Blablabla echo</em:description>
    <em:aboutURL/>
<em:unpack>true</em:unpack>
    <em:targetApplication>
      <Description>
        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!--FF-->
        <em:minVersion>3.0</em:minVersion>
        <em:maxVersion>7.*</em:maxVersion>
      </Description>
    </em:targetApplication>
  </Description>
</RDF>
Такое содержимое  install.rdf позволит установить xpi на версии FF от 3.0 до 7.* (т.е. любой 7ой версии).
Всё, теперь пакуем созданную структуру в zip-файл, меняем расширение на xpi и перетаскиваем его на окно FF. Компонент должен успешно установиться, перезапускаем FF. Вызвать наш echo-метод из JS можно:
var a = Components.classes["@some.domain.com/echosample;1"].createInstance();
alert(a.echo("HELLO WORLD!"));
Примечание - на эту операцию требуется разрешение. Читал, что оно устанавливается вызовом netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect") (естественно, использовать его можно только в тестовых целях, а не на боевых веб-страницах), но у меня почему-то и с ним не работает. Приведенный выше вызов работает из js-консоли Firebug, и должен также работать при вызове из js-кода расширения.