Cache Fixer: troubles

12th June 2005 - 00:56

На самом деле это даже не troubles, а один большой trouble, заключающийся в том, что этого расширения-костыля не должно быть в принципе. Ну, не должен браузер терять кэш, не должен.

Но, раз уж этот костыль появился на свет, то… «If we can make something more useful»

А теперь о проблемках, идущих уже после проблемы главной.

Mozilla не предоставляет возможности сохранить текущее состояние кэша, «сбросив» его из памяти на диск. Хорошо, давайте попробуем рестартовать кэш, используя методы init/shutdown компоненты nsICacheService. Получилось? Нет. Но получалось, вплоть до появления FF1.1:

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

Что произошло? Ничего особенного, если учесть, что этот интерфейс не имеет статуса FROZEN. Просто эти методы убрали подальше и добраться до них, не прибегая к писанине на C++, можно лишь через наблюдатель nsCacheProfilePrefObserver, который оповещается о событиях смены профиля (profile-after-change, profile-before-change) и завершении работы (xpcom-shutdown).

OK, сейчас оповестим наблюдатели о якобы произошедшей смене профиля, вызвав тем самым nsCacheService::OnProfileShutdown и nsCacheService::OnProfileChanged:

const kOBSRV = Components.classes["@mozilla.org/observer-service;1"]
                         .getService(Components.interfaces.nsIObserverService);

kOBSRV.notifyObservers(null, "profile-before-change", false);
kOBSRV.notifyObservers(null, "profile-after-change", false);

Работает? Да. Кэш на диск сбрасывается? Да. А так как об этих событиях оповещаются все наблюдатели, а не только интересующий нас nsCacheProfilePrefObserver, то вместе с этим очищаются cookies, список доверенных сайтов, перезагружаются закладки, ломаются некоторые расширения и т.д. Хорошо, а как добраться до конкретного наблюдателя?

var observersPAC = new Array();

var enumerator = kOBSRV.enumerateObservers("profile-after-change");
while (enumerator.hasMoreElements()) {
  observersPAC.push(enumerator.getNext().QueryInterface(nsIObserver));
}

observersPAC — массив наблюдателей события profile-after-change. Несколько штук, в зависимости от браузера и установленных расширений. Просто штуки. Близнецы-братья. Какой из них «наш» — страшная тайна. Попробуем посмотреть, какие наблюдатели висят одновременно на всех трёх событиях:

function getObserversByTopic(topic) {
  var obsArray = new Array();
  var enumerator = kOBSRV.enumerateObservers(topic);
  while (enumerator.hasMoreElements()) {
    try { //~ для "xpcom-shutdown"
      obsArray.push(enumerator.getNext()
                              .QueryInterface(nsIObserver));
    } catch(e) {}
  }
  return obsArray;
}
    
function arrayDiff(array1, array2) {
  var inBoth = new Array();
  for (var k = 0; k < array1.length; k++) {
    for (var j = 0; j < array2.length; j++) {
      if (array1[k] == array2[j]) {
        inBoth.push(array1[k]);
        break;
      }
    }
  }
  return inBoth;
}

//~ Before && After
var obsArray = arrayDiff(getObserversByTopic("profile-before-change"),
                         getObserversByTopic("profile-after-change"));

//~ Before && After && Shutdown
obsArray = arrayDiff(obsArray, getObserversByTopic("xpcom-shutdown"));

obsArray.length = 1. Ура, господа, ура! Повезло. Но, кстати, может и не повезти. Так как Mozilla расширяема вдоль и поперёк, то совсем не факт, что у кого-нибудь не обнаружится ещё один+ такой наблюдатель. Тогда придётся смотреть реакцию элементов массива obsArray на событие "nsPref:changed" (изменение установок):

//~ obsArray.length = 1, but who knows...

var cacheProfilePrefObserver;

for (var k = 0; k < obsArray.length; k++) {
  try {
    obsArray[k].observe(null, "nsPref:changed", null);
  } catch(e) {
    if (e.name == "NS_ERROR_INVALID_POINTER")
      cacheProfilePrefObserver = obsArray[k];
  }
}

Но и это не панацея, а лишь снижение вероятности. В данном конкретном случае эти танцы с бубном помогут, а вот в других…

Вы, наверное, уже догадались, что сам бы я это ни асилил.

У меня в Mozilla определены 6 обсерверов для этого события, но остальные либо просто не слушают изменения установок, либо игнорируют сообщение из-за неправильного названия установки. Только nsCacheProfilePrefObserver пытается сделать QueryInterface() на aSubject, исходя из того, что там nsIPrefBranch — и выдаёт исключение, соответственно. По этому исключению его можно распознать и вызвать observe() уже целенаправленно.

Xpoint.ru, Владимир Палант

Уффф, всё! Запускаем, смотрим и… нет, не радуемся. Думаю, не зря init/shutdown убрали подальше от шаловливых ручек. Все запросы, которые были инициализированы перед этим самопальным сохранением текущего состояния кэша, идут лесом. Т.е., если был сделан запрос к какому-то ресурсу, а после этого, до получения отклика сервера, пошёл процесс nsCacheService::shutdown/init, то результов этого запроса мы не дождёмся.

Рука не поднялась выкинуть всё это хозяйство в корзину. Поковырял внутренности LiveHTTPHeaders и повесил наблюдателя на событие http-on-modify-request. Теперь все новые запросы увеличивают некий счётчик (allowSave += 1), а после отработки — уменьшают (allowSave -= 1); при наступлении времени очередного сохранения кэша значение этого счётчика проверяется на ноль и, если это не так, то запускается вторичный таймер, который с интервалом в 10 секунд пытается (не более 5 попыток) повторить сохранение.

Будет ли это работать так, как оно задумано, и без существенных побочных эффектов — загадка. Сразу предупреждаю, что там всё относительно сыро, потому как желания продолжать там ковыряться пока нет. Если расширение будет выполнять свои функции более-менее нормально, то может быть допишу. А так, изменю пару моментов в 1.0 и успокоюсь; всё одно, sqlite на подходе.

Categories: Soft | comments: (0)

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