Xpoint
   [напомнить пароль]

Использование Gecko для отображения HTML и экспорт результата в картинку

Метки: [без меток]
2006-12-01 17:55:23 [обр] Максим Деркачев(0/568)[досье]
Тема не про XUL, но про Gecko.
Встала задача конвертирования HTML-содержимого (со стилями, картинками и т.п.) в картинку под UNIX. Решения существуют, однако обычно связаны с снятием скриншотов, и последующим выковыриванием страницы из скриншота. Такой подход не очень нравится по понятным причинам.
Напрашивается идея использовать для конвертации Mozilla. Хороший пример расширения, реализующего подобную логику - http://weblogs.mozillazine.org......s/2005/05/rendering_web_p.html . Только хочется реализовать подобное вызовами внешней программы.
Сценарий я вижу примерно такой: приложение соединяется с существующим процессом mozilla (или запускает его), и через XPCOM открывает окно, открывает в нем URL, открывает другое окно, добавляет canvas, заполняет его содержимым первого окна (или каким-то элементом окна), сохраняет содержимое canvas в картинку, чистит за собой. В Windows COM такое бы прошло легко и на ура, вот с XPCOM - не уверен.
Я только "потрогал" XPCOM (PyXPCOM), так что не силен в этой теме. Подскажите, такой вариант вообще возможен? Если да, то насколько прост в реализации (если слишком сложно, то скорее лучше остановиться на screen-grabbers)? Что про это почитать? Документация интерфейсов на developer.mozilla.com как-то не радует ...
спустя 3 часа 14 минут [обр] Сергей Чернышев(25/589)[досье]

Я видел где-то платный софт на базе мозиллы, который именно это и делал как отдельное приложение, но поднять его на моем сервере мне его не удалось, хотя скорее всего это из-за лени и старой версии софта - копать дальше у меня не было времени в тот момент.

Буду очень рад если найдете решение, пусть даже платное и поделитесь им.

спустя 1 час 28 минут [обр] Максим Деркачев(0/568)[досье]

Пока ушел от XPCOM и смотрю в сторону XUL-приложения.
Взял XULRunner и пример mybrowser. Небольшая переделка по мотивам ссылки выше (про canvas) и он засовывает в canvas содержимое страницы (как раз эта фича работает только в XUL, иначе можно было и обычным HTML обойтись).
Остался пустяк :) - выдрать это из canvas. Для этого предусмотрен метод canvas toDataUrl (пример), который может получить содержимое в виде data URL, по умолчанию - PNG. Это можно потом отправить скрипту на сервере (через XHR, например), и там уже - никаких проблем. Однако с методом связано ограничение безопасности - все элементы, которые были отрисованы в canvas, должны быть of the same origin, что и скрипт, который в canvas рисовал. Копирование страницы на локальный диск не помогло.
Для меня это не проблема - нужно драть шоты с одного сайта, который я контролирую, так что, как я понимаю, достаточно загрузить XUL и его JS с этого сайта. В prefs.js добавил
user_pref("capability.principal.codebase.111.id", "http://####");
user_pref("capability.principal.codebase.111.granted", "UniversalXPConnect");

Загружаю XUL c сайта, JS выдает ошибку uncaught exception: Permission denied to get property UnnamedClass.classes (в FireBug). На этом пока остановился.
В-общем, я в XUL первый день, так что если есть что посоветовать - буду благодарен.

спустя 2 дня 2 часа [обр] Владимир Палант(434/4445)[досье]
сообщение промодерировано

canvas прекрасно работает в HTML, XUL абсолютно не нужен. Нужны исключительно права доступа. Вот эта страничка у меня весьма успешно делает каринку из google.com, если ее открыть с chrome://:

<html>
<head>
  <script type="text/javascript">
    function doCapture() {
      var canvas = document.getElementById("canvas");
      var ctx = canvas.getContext("2d");

      var source = frames[0];
      var width = source.document.documentElement.offsetWidth;
      var height = source.document.documentElement.offsetHeight;
      canvas.setAttribute("width", width);
      canvas.setAttribute("height", height);
      ctx.drawWindow(source, 0, 0, width, height, "rgb(255,255,255)");
      location.href = canvas.toDataURL();
    }
  </script>
</head>
<body onload="doCapture()">
<canvas id="canvas"></canvas>
<iframe src="http://www.google.com/" width="100%" height="1" frameborder="0" style="visibility:hidden;"></iframe>
</body>
</html>

После перехода на нужный адрес картинку можно и просто сохранить браузером. Если же у вас эта страничка не в расширении, а открывается просто с диска или из интернета, то ставим запрос расширенных прав доступа в начало функции doCapture():

netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");

Ну и надо позаботиться, чтобы страница могла эти права получить — настройки codebase principal вы уже нашли.

спустя 16 часов [обр] Максим Деркачев(0/568)[досье]

Владимир, благодарю, из chrome:// все работает прекрасно.
Вот из внешнего источника - не получается. UniversalBrowserRead включил в начале скрипта. A script from "http://xxxxx" was denied UniversalBrowserRead privileges.
В моем профиле в prefs.js указано
user_pref("capability.principal.codebase.111.granted", "UniversalXPConnect");
user_pref("capability.principal.codebase.111.id", "http://xxxxx");

Может еще чего ему указать надо?

спустя 12 минут [обр] Владимир Палант(434/4445)[досье]
Думаю, что надо signed.applets.codebase_principal_support включить.
спустя 1 минуту [обр] Владимир Палант(434/4445)[досье]
PS: Для изменения настроек не обязательно prefs.js править, можно просто ввести about:config в адресной строке.
спустя 33 минуты [обр] Максим Деркачев(0/568)[досье]
Да, спасибо, помогло.
спустя 26 минут [обр] Максим Деркачев(0/568)[досье]

В результате:
Несколько модифицированный скрипт, приведенный Владимиром. Вызывается по URL <capture>?<page to capture>, где capture - адрес страницы со скриптом, а page to capture - адрес страницы, которую надо сграбить. Скрипт заводит новый iframe, выдирает адрес page to capture и присваивает его атрибуту src нового фрейма. На событие load элемента iframe ставится функция, которая с определенным интервалом проверяет, загрузилась ли страница во фрейме (повесить onload на frame.contentWindow почему-то не получилось). По факту загрузки запускается функция, которая заполняет canvas содержимым окна iframe (iframe.contentWindow), получает data URL и отправляет его на сервер асинхронным запросом по POST. Там это раскодируется серверным скриптом и кладется куда надо. После удачного запроса окно закрывается через window.close().

Как это использовать:
Тут у меня 2 мысли. Одна - запускать firefox-remote / mozilla -remote со строкой URL. Другая - запустить firefox на странице со скриптом, который будет поллить асинхронно по интервалу сервер запросами на URL для скриншотов, открывать окна, делать в них работу и закрывать их. Второй вариант красив, но боюсь память потечет рекой ...

спустя 1 час 59 минут [обр] Владимир Палант(434/4445)[досье]

Повесить обработчик load на frame.contentWindow не получается, поскольку в этот момент загрузка еще не началась и в фрейме загружен about:blank. Вешайте обработчик по таймауту 0ms, тогда ваш код вызовется сразу же после начала загрузки.

А зачем открывать/закрывать окна? Почему нельзя всё грузить в пределах одной страницы в одном окне? Даже новый iframe каждый раз создавать не нужно, достаточно менять src у существующего. Тогда и вероятность утечек памяти гораздо ниже.

спустя 12 минут [обр] Максим Деркачев(0/568)[досье]
Вешайте обработчик по таймауту 0ms, тогда ваш код вызовется сразу же после начала загрузки.

Ну я примерно так и сделал - повесил обработчик на событие load элемента iframe (т.е. когда он появляется в DOM). Этот обработчик потом по интервалу лазит в contentWindow и ждет появления div-а с условным id. Конечно, хотелось бы повесить на load contentWindow, чтобы код был более универсальным, но у меня впечатление, что когда страница загружается очень быстро, то этот обработчик может быть назначен слишком поздно, и не сработает.

Окна открывать-закрывать. Задача еще в том, чтобы была возможность обработать несколько запросов параллельно. Для этого, думаю, лучше всего для каждого запроса выделять свое окно/таб, а потом его закрывать по завершению. Иначе придется париться с синхронизацией внутри одного окна.

спустя 32 минуты [обр] Владимир Палант(434/4445)[досье]

Ну, если вам еще и параллельное выполнение нужно...

Что касается load — вешать обработчик на окно по таймауту в 0ms безопасно, если данные грузятся с HTTP. Дело в том, что начало загрузки ставится в очереди первым, таймаут попадает туда сразу же за ней, поскольку устанавливается еще до начала этой загрузки. А обработка данных фрейма попадает в очередь никак не раньше начала загрузки, соответственно и выполниться до таймаута не может.

спустя 45 минут [обр] Максим Деркачев(0/568)[досье]
Еще вопрос.
capability.principal.codebase.xxxxx.id - можно ли установить на wildcard/regex? Например, на все поддомены одного домена, типа http://*.xxxxx.com или http://www*.xxxxx.com ? Гуглил - не нашел, методом тыка не выходит ...
спустя 4 часа 24 минуты [обр] Владимир Палант(434/4445)[досье]
Нет. Этой опцией вообще пользоваться рекомендуется исключительно для тестирования, дыра безопасности все-таки. Для более серьезных вещей рекомендуют подписывать скрипты. К сожалению, это достаточно нетривиально. В Удаленные XUL приложения. Несколько интересных предложений (240798) и Удаленные XUL приложения. Несколько интересных предложений (240803) я писал о своих экспериментах с этим. Недавно пробовал повторить — получилось с минимальными различиями.
спустя 2 года 10 месяцев [обр] gehrmann[досье]

Спасибо.

На базе этих знаний написал симпатичный корпоративный скриншотер.

Вот только не удалось пока что побороть сайты, которые не дают себя грузить в iframe (пример http://answers.yahoo.com/question/index?qid=20070605121548aa9edmr)

Powered by POEM™ Engine Copyright © 2002-2005