Mozilla: ImgLikeOpera (images, cached images only, no images)

29th August 2004 - 01:33

Ниже я попробую рассказать о том, как я пытался написать extension для Mozilla, позволяющий «по-Оперному» управлять загрузкой графических объектов, и почему эта попытка не удалась.

Следует отметить, что я мало знаком с JavaScript и XUL, поэтому некоторые утверждения могут быть не совсем верными.

Будем считать, что эта кнопка уже есть (думаю, что прикручивание такого элемента к интерфейсу Mozilla больших сложностей не вызовет). Кнопка стоит в положении «Show cached images only», пользователь запросил ресурс, начинает работать скрипт, который должен сделать следующее:

  1. Отключить графику в начале загрузки страницы.
  2. Включить графику после окончания загрузки.
  3. Обойти все картинки и собрать значения атрибута src.
  4. Выяснить, есть ли картинка в кэше браузера и будет ли он задействован при её показе (expiration time):
    • да: показать;
    • нет: повесить на картинку обработчик события (aka «Load image»).

Рассмотрим более подробно.

Управление загрузкой графики:

const preferencesService =
  Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("");

preferencesService.setIntPref("network.image.imageBehavior", "2");
  • 2 — не загружать картинки;
  • 1 — загружать только с родительского (originating) сайта;
  • 0 — загружать все картинки.

Обход картинок и сверка с кэшем:

// Текущее время
timeNow = parseInt( (new Date()).getTime() / 1000 );
imgObjs = window.content.document.images;
for( i=0; i<imgObjs.length; i++ )
{
    // Смотрим в кэш
    if (getExpirationTime(imgObjs[i].src) < timeNow)
    {
        // В кэше картинки нет или она устарела: вешаем обработчик.
        // "click" — для примера; можно сделать
        // пункт в контекстном меню на правой кнопке мыши.
        imgObjs[i].addEventListener("click",showNewImg,true);
    }
    else
    {
        // Показываем
        imgObjs[i].parentNode.replaceChild(imgObjs[i],imgObjs[i]);
    }
}

Для работы с кэшем:

const nsICacheService =
  Components.interfaces.nsICacheService;
const cacheService =
  Components.classes["@mozilla.org/network/cache-service;1"].getService(nsICacheService);

var httpCacheSession = cacheService.createSession("HTTP", 0, true);
httpCacheSession.doomEntriesIfExpired = false;

function getExpirationTime(url)
{
    try
    {
        var cacheEntryDescriptor = httpCacheSession.openCacheEntry(url, Components.interfaces.nsICache.ACCESS_READ, false);
        if(cacheEntryDescriptor)
          return cacheEntryDescriptor.expirationTime;
    }
    catch(ex) {}
    return null;
}

Обработчик для новой, ранее не загруженной картинки:

function showNewImg()
{
    this.removeEventListener("click", showNewImg, true);
    this.parentNode.replaceChild(this,this);
    return;
}

Осталось лишь привязаться к событиям начала и окончания загрузки документа. События «onBeforeLoad» нет, аналог можно сделать таким методом (впервые увидел здесь):

const NOTIFY_ALL =
  Components.interfaces.nsIWebProgress.NOTIFY_ALL;
const STATE_STOP =
  Components.interfaces.nsIWebProgressListener.STATE_STOP;
const STATE_START =
  Components.interfaces.nsIWebProgressListener.STATE_START;

var docDone = false;

var imageLoadListener = {
  QueryInterface : function(aIID)
  {
    if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
          aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
          aIID.equals(Components.interfaces.nsISupports))
      return this;
    throw Components.results.NS_NOINTERFACE;
  },
  onStateChange:function(aProgress,aRequest,aFlag,aStatus)
  {
    // Начало загрузки документа
    if (aFlag & STATE_START)
    {
        preferencesService.setIntPref("network.image.imageBehavior", 2);
        docDone = false;
    }
    // Документ загружен
    if (aFlag & STATE_STOP)
    {
      if (docDone == false)
      {
        preferencesService.setIntPref("network.image.imageBehavior", 0);
        // .. .. ..
        // Здесь обходим картинки
        // .. .. ..
        docDone = true;
      }
    }
  },
}

function loadImgHide()
{  
  window.getBrowser().addProgressListener(imageLoadListener, NOTIFY_ALL);
}
function unloadImgHide()
{
  window.getBrowser().removeProgressListener(imageLoadListener);
}
function setImagePrefOnWindowLoad()
{
  preferencesService.setIntPref("network.image.imageBehavior", 2);
}
function setImagePrefOnWindowUnload()
{
    preferencesService.setIntPref("network.image.imageBehavior", 0);
    return;
}
window.addEventListener("load", setImagePrefOnWindowLoad, false);
window.addEventListener("unload", setImagePrefOnWindowUnload, false);
document.addEventListener("load", loadImgHide, true);
document.addEventListener("unload", unloadImgHide, true);

Это даже почти работает. :)

Чего не хватает? Собственно самой кнопки; показа картинки в другом (уже открытом) табе, если она была загружена в текущем; более корректной работы с пользовательскими настройками; обработки не только img, но и background-image; etc. Всем этим можно было бы заняться после решения двух вопросов:

  • как отлавливать начало загрузки таба (особенно при browser.tabs.loadInBackground = true)? Мой английский хромает, может быть я не понял чего?
  • если значение network.image.imageBehavior носит глобальный характер, то как менять его «локально», лишь в области видимости таба?

Вот те две вещи, которые мне остались непонятны. Если они решаемы, то…

О багах. Работоспособность скрипта проверялась на localhost, могут быть проблемы с «внешним миром» на тонких каналах. Ну и с табами проблемы, да. Желающие могут скачать (zip, 9kb) и посмотреть, но я не несу ответственности за возможный ущерб, причиной которого будет использование этого extension (лично у меня иногда зависал браузер). Делалось лишь из любопытства.

Для тех, кто захочет посмотреть в работе и установит: тест. Первая страница без картинок, начинать с неё; ссылку на вторую страницу открывать в текущем табе.

Update: Xpoint.ru

Update 2: ILO FF1.0-only

Categories: dHtml, Soft, Usability | comments: (3)

Комментарии

1. kukutz 29th August 2004 - 09:36

Может, анонсировать в
http://www.livejournal.com/community/ru_mozilla/
http://www.livejournal.com/community/mozilla_ru/
http://www.livejournal.com/community/ru_mozdev/
?

А также в форумах mozillazine & extensionmirror?

С вопросами, как быть с табами.

Mash:

LJ: даже не знаю как писать в community. Там нужно быть его членом, да?

mozillazine/extensionmirror: мой английский. :(

2. kukutz 29th August 2004 - 13:24

Да, сперва вступить в сообщество на странице его профиля, потом /update.bml и выбрать там журнал, в который писать.

Английский — ну, я готов помочь вычитать сообщение. Но вообще да, это проблема.

Mash:

ru_mozdev, mozilla_ru. Спасибо.

3. ventzy 17th January 2005 - 14:29

is there update for FF 1.0?

Mash:

No, because FF1.0 has bug.

I can write this extension without some features and thinking about it. Maybe after 1-2 weeks.

(later) Here. Buggy now, but work.

Комментарии временно отключены.