Итак, XPCOM - это технология, позволяющая получить доступ к каким-либо низкоуровневым вещам и нативным библиотекам через JavaScript в Mozilla Firefox. Теоретически может работать и в Chrome, но я не проверяла и думаю, что там есть какие-то ограничения и свои грабли. Грубо говоря, XPCOM - это ActiveX для Mozill`ы. Плюсы его в кроссплатформенности, в возможности использовать многое в обычной жизни для веб-разработчика недоступное. Минусы.. Эмм.. Вот этого добра тут валом. Начнем с того, что Mozilla нам ничего не обещает. В процессе изучения и поиска мне встретилась просто потрясающая фраза в MDN(о бинарных с++ компонентах) - "Вы не захотите этим заниматься, пока вам это не станет совершенно необходимо" К сожалению, ссылку утеряна, если найду - добавлю..После того, как расширение написано, его придется пересобирать каждый раз, когда выходит новая версия Mozilla. И самое главное - постоянно меняющееся SDK точно не даст заскучать.
Но, если вдруг у вас, также как у меня в один прекрасный день партия сказала - "Надо!" и отступать некуда, этот текст для вас.
Что понадобится: xulrunner-sdk 24 версии, python 27, visual studio 2010. Xulrunner-sdk скачиваем, распаковываем в папку c:\xulrunner-sdk. Python устанливаем, добавляем в переменные окружения PATHPYTHON с путем к нему, перезагружаемся.
Первым делом следует создать idl-файл, в котором будут описаны методы, реализуемые нашим компонентом. Я не буду мудрить и возьму ту idl-ку, которая много лет уже кочует из одного мануала в другой, добавив туда строковой метод
#include "nsISupports.idl" [scriptable, uuid(1DD004E8-DD8A-4DD6-9C08-956BA6A0FF91)] interface ITestComponent : nsISupports { long Test(in long a, in long b); AString RetString(in long index); };Из этой idl нужно сгенерировать два файла - h и xpt. Вот тут-то нам и пригодится python, потому как утилиты предоставлены в виде файлов py. Когда-то, в
Для удобства я советую создать батник с приблизительно таким содержимым:
c:\xulrunner-sdk\sdk\bin\header.py -I c:\xulrunner-sdk\idl -o itestcomponent.h testcomponent.idl
c:\xulrunner-sdk\sdk\bin\typelib.py -I c:\xulrunner-sdk\idl -o testcomponent.xpt testcomponent.idl
Замечу, что хидер и xpt подлежат перегенерации при добавлении новых методов в idl.
Переходим в студию, создаем проект типа Empty project и добавляем в него след файлы:
1) сгенерированный h
2) MyComponent.h с следующим содержимым
#include "itestcomponent.h" // These macros are used in ndnNrtModule.cpp #define TEST_CLASSNAME "test component" #define TEST_CONTRACTID "@any.kz/testcomponent;1" // "CD232E0F-A777-41A3-BB19-CF415B98088E" #define TEST_CID \ { 0x1dd004e8, 0xdd8a, 0x4dd6,\ {0x9c, 0x8, 0x95, 0x6b, 0xa6, 0xa0, 0xff, 0x91}} /** * Class description goes here */ class TestComponent: public ITestComponent { public: NS_DECL_ISUPPORTS NS_DECL_ITESTCOMPONENT TestComponent(); private: ~TestComponent(); protected: /* additional members */ };здесь
CID - класс ID нашего компонента, сгенерированный любым удобным способом (например, в Visual Studio)
3) MyComponent.cpp, содержащий в себе реализацию метода нашего класса
#include "TestComponent.h" #include <string> #include "nsStringAPI.h" NS_IMPL_ISUPPORTS1(TestComponent, ITestComponent) TestComponent::TestComponent() { /* member initializers and constructor code */ } TestComponent::~TestComponent() { /* destructor code */ } /* long Add (in long a, in long b); */ NS_IMETHODIMP TestComponent::Test(int32_t a, int32_t b, int32_t *_retval) { *_retval = a+b; return NS_OK; } NS_IMETHODIMP TestComponent::RetString(int32_t index, nsAString & _retval) { std::string temp_str = "12345"; std::string temp_str2 = "54321"; if (index==1) { nsString str; str.AppendLiteral((char*)temp_str.c_str()); _retval.Append(str); } else { nsString str; str.AppendLiteral((char*)temp_str2.c_str()); _retval.Append(str); } return NS_OK; }4) MyComponentModule.cpp, который содержит в себе необходимые для Mozzila макросы
#include "mozilla/ModuleUtils.h" #include "nsIClassInfoImpl.h" #include "TestComponent.h" NS_GENERIC_FACTORY_CONSTRUCTOR(TestComponent) NS_DEFINE_NAMED_CID(TESTCOMPONENT_CID); static const mozilla::Module::CIDEntry kNrtcCIDs[] = { { &kTESTCOMPONENT_CID, false, NULL, TestComponentConstructor}, { NULL } }; static const mozilla::Module::ContractIDEntry kNrtcContracts[] = { { TESTCOMPONENT_CONTRACTID, &kTESTCOMPONENT_CID }, { NULL } }; static const mozilla::Module::CategoryEntry kNrtcCategories[] = { { NULL } }; static const mozilla::Module kNrtcModule = { mozilla::Module::kVersion, kNrtcCIDs, kNrtcContracts, kNrtcCategories }; NSMODULE_DEFN(ndNrtcModule) = &kNrtcModule;В свойствах проекта указываем пути к дополнительным инклюдам и либам - соотвественно.
В зависимостях указываем - xul.lib;xpcomglue_s_nomozalloc.lib;nss3.lib
Директивы препроцессора - XPCOM_GLUE;_WINDLL;XP_WIN;XP_WIN32;_MBCS;
Не забываем изменить тип проекта на dll и Code Generation на MT. Можно собирать ))
Поздравляю, полпути пройдено.
Для того, чтобы добавить наше расширение в Mozilla нужно его упаковать. Упаковывается оно в банальный зип, только с расширением xpi. Структура простейшего xpi такая:
- chrome/
- content/
- components/
- TestComponent.dll
- TestComponent.xpt
- install.rdf
- chrome.manifest
директория components - тут должны находиться файлы dll и xpt. Строго говоря, директория может называться как угодно, но только не забудьте потом исправить ссылки и в chrome.manifest
chrome.manifest - в этом файле описана структура пакета. В нашем случае будет выглядеть так:
interfaces components/TestComponent.xpt binary-component components/TestComponent.dllinstall.rdf - тут в первую очередь следует обратить внимание на параметр unpack. Для бинарных компонентов он должен быть равен true. Наш install.rdf
<?xml version="1.0" ?> - <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>testcomponent@any.kz</em:id> <em:version>0.1</em:version> <em:unpack>true</em:unpack> <em:type>2</em:type> - <!-- <pre> Target Application this extension can install into, with minimum and maximum supported versions. </pre> --> - <em:targetApplication> - <Description> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <em:minVersion>4.5</em:minVersion> <em:maxVersion>99.*</em:maxVersion> </Description> </em:targetApplication> <em:name>Test</em:name> <em:description>TestComponent</em:description> <em:creator>Any</em:creator> <em:homepageURL>http://any.kz</em:homepageURL> </Description> </RDF>Как я и говорила ранее, все это упаковывается в zip-архив, после чего полученный xpi можно просто перетащить в браузер. Вас спросят на самом ли деле вы хотите добавить это сомнительное расширение и Firefox перезапустится.
Для того, чтобы убедиться в том, что компонент установился корректно, можно воспользоваться расширением XPCOM Viewer. Если все хорошо, ты вы увидите его в списке.
Компонент установлен, теперь следует проверить его работу. Раньше для вызова XPCOM из JavaScript использовались конструкции такого вида:
const cid = "@any.kz/TestComponent;1"; var obj = Components.classes[cid].getService(); var res = obj.Test();Начиная с 22-ой версии Firefox обращение к Components запрещено. Я попробовала разобраться с тем способом, который предлагает MDN, но тут то ли лыжи, то я.. В общем, лично у меня ничего не получилось, у кого получится - большая человеческая просьба - напишите об этом! А пока я предлагаю использовать подход, который описалPeter Gusev (дай Бог ему здоровья!). В общих чертах этот способ выглядит так: бинарный компонент оборачивается в JavaScript-компонент, который в свою очередь регистрируется в объекте window.
Итак, как выглядит обертка:
let Cu = Components.utils; let Ci = Components.interfaces; let Cc = Components.classes; let Cr = Components.results; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); function TESTCOMPONENTFUNC() {} TESTCOMPONENTFUNC.prototype = { classID: Components.ID("{1619E6AA-7266-440B-809F-B79D2E0D8806}"), QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer]), init: function(aWindow) { let self = this; let api = { Test: self.Test.bind(self), RetString: self.RetString.bind(self), __exposedProps__: { Test: "r", RetString: "r" } }; return api; }, Test: function(a, b) { var testcomponent= testcomponent= Cc["@any.kz/testcomponent;1"].createInstance(); testcomponent= testcomponent.QueryInterface(Ci.ITestComponent); return testcomponent.Test(a,b); }, RetString: function(index) { var testcomponent= testcomponent= Cc["@any.kz/testcomponent;1"].createInstance(); testcomponent= testcomponent.QueryInterface(Ci.ITestComponent); return testcomponent.RetString(index); } }; var NSGetFactory = XPCOMUtils.generateNSGetFactory([TESTCOMPONENTFUNC]);Как говорит Петер Гусев, сия конструкция позволяет внедрить в DOM наш компонент. Я в Javascript несильна, поэтому просто верю Теперь следует упаковать обертку и компонент вместе, желательно так, что MF понял, что от него требуется. Для этого создадим файл wrapper.manifest
component {1619E6AA-7266-440B-809F-B79D2E0D8806} wrapper.js contract @any.kz/testcomponent;1 {1619E6AA-7266-440B-809F-B79D2E0D8806} category JavaScript-global-property testObject @any.kz/testcomponent;1Этот файл, а также файл с оберткой перенесем в директорию components нашего пакета и внесем изменения в chrome.manifest:
manifest components/wrapper.manifest interfaces components/testcomponent.xpt binary-component components/testcomponent.dllСнова пакуем зипом, переименовываем в xpi и тащим в Firefox. Проверяем наличие зарегистрированного компонента в XPCOMViewer, если все ок, то можно создавать страничку с вызовом. Приблизительно таким:
alert(window.testObject.Test(1,2)); alert(window.testObject.RetString(1));Готово
Больше месяца собиралась опубликовать.. Уфф