Полезная информация

Заказывай стафф с атрибутикой Mozilla и... пусть все вокруг завидуют тебе! Быть уникальным - быть с Mozilla!
 

Все с нуля, или как написать приложение для работы с Mozilla

Автор: Aerina

В связи с тем, что я столкнулась с проблемами при написании необходимой мне программы, я решила написать это руководство к действиям, своего рода «памятка» — схема последовательности действий. Я не претендую на то, что я решила проблему лучшим образом и все сделала правильно.
В начале я опишу, какая задача была передо мной поставлена, как я пыталась ее решить, а потом по шагам — как мне все же это удалось.

Итак, мне необходимо было написать приложение, которое бы подключалось к Mozilla и FF и в выбранный пользователем момент выполняло определенные действия.
Представьте, у вас есть какая-то база (приложение написанное на Delphi или С++, или вообще с помощь MFC — в дальнейшем назовем это приложение Главным), пользователь кликает мышью по какому-то полю в Mozilla, и в базу заносится информация, записанная в этом поле, тип поля и так далее, плюс — информация о страничке. Основная проблема! — при этом Mozilla не получает управления, то есть управление у Главного приложения!
И следующий шаг (более легкий): в дальнейшем, когда пользователь переключается между страничками, автоматически заполняются все поля, которые сохранены в базе.
Все примеры, которые я смотрела, были написаны с помощью JS, причем я не сильно понимаю, зачем делать на JavaScript диалоговые окна, если можно написать компоненту. Ладно, это уже пожелание авторов.

С чего я начала: http://xul.ru/ — хороший сайт, но совершенно не то, что мне надо. Полазив по http://xulplanet.com/ (основной помощник) и сайту Mozilla, поняла, что плагины совершенно не для меня. Плагин выполняется только для определенной странички.
Значит, надо написать расширение: взяла пример http://www.borngeek.com/firefox/tutorial/ (переведенный вариант http://www.toolbar.net.ru/1.html), помучила этот пример.
Не буду приводить здесь строгую схему создания XPI-файла, так как она очень хорошо описана в этих ссылках. Но получилось у меня в результате такое:

Myhook.xpi ->> install.rdf
               [chrome]->> myhook.jar->>[content]->> actions.js
                                                     contents.rdf
                                                     myhook.js
                                                     myhook.xul

Myhook.xpi — это переименованный ZIP-файл;
install.rdf взят из примера, в нем только изменены имя расширения, автор, GUID и имя JAR-файла. В мой проект также включен install.js, этот файл необходим для загрузки расширений в Mozilla Suite, но он не нужен для FF;
myhook.jar — делаю с помощью батника jar.bat:

" [путь] \bin\jar.exe" -cf0 myhook.jar *.*

contents.rdf — измены только пути и имена;
myhook.xul — прописаны два скрипта:

<script type="application/x-javascript" src="actions.js" />
<script type="application/x-javascript" src="myhook.js" />

myhook.js (все содержимое файла)

var actions = new actions();
 
window.addEventListener("load", formInitialize, true);
 
function formInitialize() {
   try {
       window.addEventListener("load", actions.doLoad,true); 
      // перехватываем события загрузки новой страницы
   } catch(e) {
   }                   
}

actions.js (все содержимое файла):

function actions() {
    
   this.doLoad = doLoad;
 
   function doLoad() {
        alert('Load page');
   }
}

Вот и получилось (расширение), которое каждый раз при загрузке странички выдает сообщение «Load page». Кстати, иногда выдает и по несколько раз на страничку, или бывает, что и не выдает, если загрузка никак не может закончиться.

Но это расширение не может решить проблему обмена информации с Главным приложением. Поэтому я начала искать возможность добавить какой-то элемент способный вызвать функции главной программы.

Выход нашла в создании компоненты — XPCOM. Снова же описание очень хорошее, а главное все работает.

Итак, копируем пример, рассматриваем его со всех сторон и экспериментируем. Общая схема создания и добавления компоненты в Mozilla такова:

1. Скачаем [gecko-sdk].
2. Скачаем пример. Архив xpcom-sample.zip содержит такие файлы:

  • IMyComponent.h
  • IMyComponent.idl
  • Module.cpp

Makefile меня не интересовал, так как с проектом я работала на Visual Studio 6.0.

3. Переименовываем все файлы, например, на MyNewComponent. Чтобы поменять имя проекта, откройте MyNewComponent.dsp в текстовом редакторе и замените все MyComponent на MyNewComponent, то же самое надо сделать в MyNewComponent.mak.
4. В директорию [gecko-sdk]-[idl] перекопируем файл MyNewComponent.idl.
5. Если необходимое меняем содержимое IMyNewComponent.idl файла (перечисляем все функции, которые хотим вызывать из своего файла actions.js):

#include "nsISupports.idl"
#include "nsIAccessibleDocument.idl"
 
 
[scriptable, uuid([свой GUID])]
interface IMyNewComponent : nsISupports
{
 
  long OnLoadPage();
  long SetBrWindow(in nsIAccessibleDocument window);
 
};

6. Пишем два батника или выполняем в командной строке:

xpidl.exe -m typelib IMyNewComponent.idl

– получим файл IMyNewComponent.xpi

xpidl.exe -m header IMyNewComponent.idl

– получим файл IMyNewComponent.h

7. Копируем IMyNewComponent.idl и IMyNewComponent.h файлы себе в проект.
8. Добавляем в MyNewComponent.сpp тело функций описанных в IMyNewComponent.h.
9. Открываем проект. Вызовем диалоговое окно Settings. В С/С++ в категории Preprocessor изменяем путь «Additional include directories» к [gecko-sdk].
10. Компилируем проект.

Установка для FF

Добавляем в схему еще одну директорию [components].

                Myhook.xpi ->> install.rdf
                               [chrome]     ->> myhook.jar
                               [components] ->> MyNewComponent.dll
                                                IMyNewComponent.xpi

Установка для Mozilla

  1. Переписываем MyNewComponent.dll и IMyNewComponent.xpi в директорию [components], которая находится в установленной Mozilla.
  2. Выполняем regxpcom.exe.
  3. И удаляем xpti.dat и compreg.dat.
  4. Запускаем Mozilla.

Теперь, когда компонента зарегистрирована, вызовем ее метод в actions.js:

function actions() {
    
   this.doLoad = doLoad;
 
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
   const cid = "@mydomain.com/XPCOMSample/MyNewComponent;1";
   obj = Components.classes[cid].createInstance();
   obj = obj.QueryInterface(Components.interfaces.IMyNewComponent);
 
  function doLoad() {
        alert('Load page');
   }
}

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

Теперь будем каждый раз при вызове doLoad передавать:

  function doLoad() {
    const accServiceID = '@mozilla.org/accessibilityService;1';
    const accServiceIF = Components.interfaces.nsIAccessibilityService;
    const accService = Components.classes[accServiceID].getService(accServiceIF);
    var   acc = accService.getAccessibleFor(window.content.document);
    var dom_window = acc.QueryInterface(Components.interfaces.nsIAccessibleDocument);
 
    obj.SetBrWindow(dom_window);
}

Где функция SetBrWindow записывает dom_window (элемент компоненты nsIAccessibleDocument) в глобальную переменную MyNewComponent.dll g_dom_window. Теперь в нашей компоненте хранится информация об окне, а соответственно мы легко можем получить содержимое документа. Выбор компоненты nsIAccessibleDocument связан с тем, что из нее можно получить HWND окна браузера:

HWND* DocWindow = new HWND;
   rv = g_ dom_window->GetWindowHandle((void**)DocWindow);

Но всегда ли мы имеем доступ к текущему документу? Как узнать Главному приложению, с каким именно окном нужно работать, если открыто запущенно несколько окон Mozilla? И что если в одном окне Mozilla загружено несколько страничек, и пользователь переходит с одной на другую?
Добавим в myhook.js перехват еще двух событий:

function formInitialize() {
   try {
       window.addEventListener("load", actions.doLoad,true); 
      // перехватываем события загрузки новой страницы
       window.addEventListener("click", actions.doClick, false);
     //пользователь перешел на другую страничка
       window.addEventListener("focus", actions.doActivate, true);
     //пользователь перешел на другое загруженное приложение FF или Mozilla.
   } catch(e) {
   }                   
}

И в каждой новой функции выполняются те же действия, как и в doLoad. Теперь осталась только научить компоненту обмениваться информацией с главным приложением.
Содержание MyNewComponent.h файла:

class MyNewComponent : public IMyNewComponent
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_IMYNEWCOMPONENT
 
 MyNewComponent ();
  virtual ~ MyNewComponent();
 
  bool InitWindow();
  bool InitMemory();
 
};

Функции InitWindow() и InitMemory() вызываются в конструкторе класса. В InitWindow создается обычное API окно с обработчиком событий:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

В InitMemory инициализируется глобальная переменная pShared. В выделенной Shared Memory будут храниться данные общие для двух приложений. То есть, когда мы из g_dom_window получим необходимую информацию, то запишем ее в структуру, на которую указывает pShared.

Теперь нам необходимо определять момент, когда Главному приложению требуется получить из странички информацию о содержании полей. Зарегистрируем новое для Window сообщение в компоненте и в Главном приложении:

UINT WM_SETSHARED_DATA = RegisterWindowMessage("SharedParamsMozilla");

Обработаем такие сообщения в WndProc:

    if (message == WM_SETSHARED_DATA) PostInfo();
            // выполняем функцию заполнения структуры, на которую указывает pShared,  
           //необходимыми данными 
    if (message == WM_CREATE) pShared->hWindowComponent = hWnd;
          // запоминаем HWND созданного нами окна.

А в Главном приложении пошлем зарегистрированное сообщение WM_SETSHARED_DATA для окна созданного в компоненте:

 WPARAM wParam = 0;
 LPARAM lParam = 0; 
 SendMessage(pShared->hWindowComponent,WM_SETSHARED_DATA,wParam,lParam);

После этого осталось только использовать данные записанные в структуре.

На последок несколько замечаний:

  1. Конечно, вместо Shared Memory можно использовать WM_COPYDATA.
  2. Отлавливать событие focus надо еще и из-за того, что необходимо записывать в pShared→hWindowComponent окно из текущей DLL.
  3. Чтобы избежать вопросов в отношении того, что в Mozilla есть реализованный интерфейс запоминания содержания полей на страничках (по крайней мере паролей), скажу, что по ряду причин он не устраивал моих заказчиков.
  4. Осталась проблема с написание Инсталляции — как сделать так, чтобы можно было не требовать у пользователя самостоятельной установки расширений?
  5. Каждый раз при запуске очередного окна Mozilla, создается новое окно в функции InitWindow(), которое и отвечает дальше за обмен информацией. Предыдущие созданные окна теряются. С этим вопросом еще предстоит разобраться.
 
  development/extension/mozilla.txt · Последние изменения: 2006/05/19 23:01
 

Board footer

Powered by PunBB
Modified by Mozilla Russia
Copyright © 2004–2011 Mozilla Russia
Язык отображения форума: [Русский] [English]