Cache Fixer: troubles
На самом деле это даже не 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() уже целенаправленно.
Уффф, всё! Запускаем, смотрим и… нет, не радуемся. Думаю, не зря init/shutdown убрали подальше от шаловливых ручек. Все запросы, которые были инициализированы перед этим самопальным сохранением текущего состояния кэша, идут лесом. Т.е., если был сделан запрос к какому-то ресурсу, а после этого, до получения отклика сервера, пошёл процесс nsCacheService::shutdown/init, то результов этого запроса мы не дождёмся.
Рука не поднялась выкинуть всё это хозяйство в корзину. Поковырял внутренности LiveHTTPHeaders и повесил наблюдателя на событие http-on-modify-request. Теперь все новые запросы увеличивают некий счётчик (allowSave += 1), а после отработки — уменьшают (allowSave -= 1); при наступлении времени очередного сохранения кэша значение этого счётчика проверяется на ноль и, если это не так, то запускается вторичный таймер, который с интервалом в 10 секунд пытается (не более 5 попыток) повторить сохранение.
Будет ли это работать так, как оно задумано, и без существенных побочных эффектов — загадка. Сразу предупреждаю, что там всё относительно сыро, потому как желания продолжать там ковыряться пока нет. Если расширение будет выполнять свои функции более-менее нормально, то может быть допишу. А так, изменю пару моментов в 1.0 и успокоюсь; всё одно, sqlite на подходе.
Categories: Soft | comments: (0)