Внимание: использование 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, принимающий на входе строку и возвращающий её же.
Всё, теперь пакуем созданную структуру в zip-файл, меняем расширение на xpi и перетаскиваем его на окно FF. Компонент должен успешно установиться, перезапускаем FF. Вызвать наш echo-метод из JS можно:
Уф, один рабочий день и наконец-то получилось понять, как же все-таки писать расширения для 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"?>Такое содержимое install.rdf позволит установить xpi на версии FF от 3.0 до 7.* (т.е. любой 7ой версии).
<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>
var a = Components.classes["@some.domain.com/echosample;1"].createInstance();
alert(a.echo("HELLO WORLD!"));
Примечание - на эту операцию требуется разрешение. Читал, что оно устанавливается вызовом netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect") (естественно, использовать его можно только в тестовых целях, а не на боевых веб-страницах), но у меня почему-то и с ним не работает. Приведенный выше вызов работает из js-консоли Firebug, и должен также работать при вызове из js-кода расширения.
Комментариев нет:
Отправить комментарий