Bug 621158 - make appcache use messaging for quota management, r=mayhemer,jaws

MozReview-Commit-ID: GfHbERuzuW8

--HG--
extra : rebase_source : 343aab5cb460641d4fb0dd21760ec40cc68e9c28
extra : amend_source : 4609109ca8cd686fae51956cca960b5f94656e34
This commit is contained in:
Gijs Kruitbosch 2016-02-29 17:52:35 +00:00
Родитель 6be7372f93
Коммит 7ded129f86
7 изменённых файлов: 259 добавлений и 233 удалений

Просмотреть файл

@ -1049,16 +1049,9 @@ var gBrowserInit = {
this._cancelDelayedStartup();
// We need to set the MozApplicationManifest event listeners up
// before we start loading the home pages in case a document has
// a "manifest" attribute, in which the MozApplicationManifest event
// will be fired.
gBrowser.addEventListener("MozApplicationManifest",
OfflineApps, false);
// listen for offline apps on social
let socialBrowser = document.getElementById("social-sidebar-browser");
socialBrowser.addEventListener("MozApplicationManifest",
OfflineApps, false);
// We need to set the OfflineApps message listeners up before we
// load homepages, which might need them.
OfflineApps.init();
// This pageshow listener needs to be registered before we may call
// swapBrowsersAndCloseOther() to receive pageshow events fired by that.
@ -1176,7 +1169,6 @@ var gBrowserInit = {
window.messageManager.addMessageListener("Browser:URIFixup", gKeywordURIFixup);
BrowserOffline.init();
OfflineApps.init();
IndexedDBPromptHelper.init();
if (AppConstants.E10S_TESTING_ONLY)
@ -1498,7 +1490,6 @@ var gBrowserInit = {
}
BrowserOffline.uninit();
OfflineApps.uninit();
IndexedDBPromptHelper.uninit();
LightweightThemeListener.uninit();
PanelUI.uninit();
@ -5745,141 +5736,49 @@ var BrowserOffline = {
};
var OfflineApps = {
/////////////////////////////////////////////////////////////////////////////
// OfflineApps Public Methods
init: function ()
{
Services.obs.addObserver(this, "offline-cache-update-completed", false);
},
uninit: function ()
{
Services.obs.removeObserver(this, "offline-cache-update-completed");
},
handleEvent: function(event) {
if (event.type == "MozApplicationManifest") {
this.offlineAppRequested(event.originalTarget.defaultView);
}
},
/////////////////////////////////////////////////////////////////////////////
// OfflineApps Implementation Methods
// XXX: _getBrowserWindowForContentWindow and _getBrowserForContentWindow
// were taken from browser/components/feeds/WebContentConverter.
_getBrowserWindowForContentWindow: function(aContentWindow) {
return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.wrappedJSObject;
},
_getBrowserForContentWindow: function(aBrowserWindow, aContentWindow) {
// This depends on pseudo APIs of browser.js and tabbrowser.xml
aContentWindow = aContentWindow.top;
var browsers = aBrowserWindow.gBrowser.browsers;
for (let browser of browsers) {
if (browser.contentWindow == aContentWindow)
return browser;
}
// handle other browser/iframe elements that may need popupnotifications
let browser = aContentWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler;
if (browser.getAttribute("popupnotificationanchor"))
return browser;
return null;
},
_getManifestURI: function(aWindow) {
if (!aWindow.document.documentElement)
return null;
var attr = aWindow.document.documentElement.getAttribute("manifest");
if (!attr)
return null;
try {
var contentURI = makeURI(aWindow.location.href, null, null);
return makeURI(attr, aWindow.document.characterSet, contentURI);
} catch (e) {
return null;
}
},
// A cache update isn't tied to a specific window. Try to find
// the best browser in which to warn the user about space usage
_getBrowserForCacheUpdate: function(aCacheUpdate) {
// Prefer the current browser
var uri = this._getManifestURI(content);
if (uri && uri.equals(aCacheUpdate.manifestURI)) {
return gBrowser.selectedBrowser;
}
var browsers = gBrowser.browsers;
for (let browser of browsers) {
uri = this._getManifestURI(browser.contentWindow);
if (uri && uri.equals(aCacheUpdate.manifestURI)) {
return browser;
}
}
// is this from a non-tab browser/iframe?
browsers = document.querySelectorAll("iframe[popupnotificationanchor] | browser[popupnotificationanchor]");
for (let browser of browsers) {
uri = this._getManifestURI(browser.contentWindow);
if (uri && uri.equals(aCacheUpdate.manifestURI)) {
return browser;
}
}
return null;
},
_warnUsage: function(aBrowser, aURI) {
if (!aBrowser)
warnUsage(browser, uri) {
if (!browser)
return;
let mainAction = {
label: gNavigatorBundle.getString("offlineApps.manageUsage"),
accessKey: gNavigatorBundle.getString("offlineApps.manageUsageAccessKey"),
callback: OfflineApps.manage
callback: this.manage
};
let warnQuota = gPrefService.getIntPref("offline-apps.quota.warn");
let warnQuotaKB = Services.prefs.getIntPref("offline-apps.quota.warn");
// This message shows the quota in MB, and so we divide the quota (in kb) by 1024.
let message = gNavigatorBundle.getFormattedString("offlineApps.usage",
[ aURI.host,
warnQuota / 1024 ]);
[ uri.host,
warnQuotaKB / 1024 ]);
let anchorID = "indexedDB-notification-icon";
PopupNotifications.show(aBrowser, "offline-app-usage", message,
PopupNotifications.show(browser, "offline-app-usage", message,
anchorID, mainAction);
// Now that we've warned once, prevent the warning from showing up
// again.
Services.perms.add(aURI, "offline-app",
Services.perms.add(uri, "offline-app",
Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
},
// XXX: duplicated in preferences/advanced.js
_getOfflineAppUsage: function (host, groups)
{
var cacheService = Cc["@mozilla.org/network/application-cache-service;1"].
_getOfflineAppUsage(host, groups) {
let cacheService = Cc["@mozilla.org/network/application-cache-service;1"].
getService(Ci.nsIApplicationCacheService);
if (!groups)
if (!groups) {
try {
groups = cacheService.getGroups();
} catch (ex) {
return 0;
}
}
var usage = 0;
let usage = 0;
for (let group of groups) {
var uri = Services.io.newURI(group, null, null);
let uri = Services.io.newURI(group, null, null);
if (uri.asciiHost == host) {
var cache = cacheService.getActiveCache(group);
let cache = cacheService.getActiveCache(group);
usage += cache.usage;
}
}
@ -5887,13 +5786,15 @@ var OfflineApps = {
return usage;
},
_checkUsage: function(aURI) {
_usedMoreThanWarnQuota(uri) {
// if the user has already allowed excessive usage, don't bother checking
if (Services.perms.testExactPermission(aURI, "offline-app") !=
if (Services.perms.testExactPermission(uri, "offline-app") !=
Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN) {
var usage = this._getOfflineAppUsage(aURI.asciiHost);
var warnQuota = gPrefService.getIntPref("offline-apps.quota.warn");
if (usage >= warnQuota * 1024) {
let usageBytes = this._getOfflineAppUsage(uri.asciiHost);
let warnQuotaKB = Services.prefs.getIntPref("offline-apps.quota.warn");
// The pref is in kb, the usage we get is in bytes, so multiply the quota
// to compare correctly:
if (usageBytes >= warnQuotaKB * 1024) {
return true;
}
}
@ -5901,43 +5802,22 @@ var OfflineApps = {
return false;
},
offlineAppRequested: function(aContentWindow) {
if (!gPrefService.getBoolPref("browser.offline-apps.notify")) {
return;
}
let browserWindow = this._getBrowserWindowForContentWindow(aContentWindow);
let browser = this._getBrowserForContentWindow(browserWindow,
aContentWindow);
let currentURI = aContentWindow.document.documentURIObject;
// don't bother showing UI if the user has already made a decision
if (Services.perms.testExactPermission(currentURI, "offline-app") != Services.perms.UNKNOWN_ACTION)
return;
try {
if (gPrefService.getBoolPref("offline-apps.allow_by_default")) {
// all pages can use offline capabilities, no need to ask the user
return;
}
} catch(e) {
// this pref isn't set by default, ignore failures
}
let host = currentURI.asciiHost;
requestPermission(browser, docId, uri) {
let host = uri.asciiHost;
let notificationID = "offline-app-requested-" + host;
let notification = PopupNotifications.getNotification(notificationID, browser);
if (notification) {
notification.options.documents.push(aContentWindow.document);
notification.options.controlledItems.push([
Cu.getWeakReference(browser), docId, uri
]);
} else {
let mainAction = {
label: gNavigatorBundle.getString("offlineApps.allow"),
accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
callback: function() {
for (let document of notification.options.documents) {
OfflineApps.allowSite(document);
for (let [browser, docId, uri] of notification.options.controlledItems) {
OfflineApps.allowSite(browser, docId, uri);
}
}
};
@ -5945,16 +5825,16 @@ var OfflineApps = {
label: gNavigatorBundle.getString("offlineApps.never"),
accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
callback: function() {
for (let document of notification.options.documents) {
OfflineApps.disallowSite(document);
for (let [, , uri] of notification.options.controlledItems) {
OfflineApps.disallowSite(uri);
}
}
}];
let message = gNavigatorBundle.getFormattedString("offlineApps.available",
[ host ]);
[host]);
let anchorID = "indexedDB-notification-icon";
let options= {
documents : [ aContentWindow.document ]
let options = {
controlledItems : [[Cu.getWeakReference(browser), docId, uri]]
};
notification = PopupNotifications.show(browser, notificationID, message,
anchorID, mainAction,
@ -5962,56 +5842,47 @@ var OfflineApps = {
}
},
allowSite: function(aDocument) {
Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.ALLOW_ACTION);
disallowSite(uri) {
Services.perms.add(uri, "offline-app", Services.perms.DENY_ACTION);
},
allowSite(browserRef, docId, uri) {
Services.perms.add(uri, "offline-app", Services.perms.ALLOW_ACTION);
// When a site is enabled while loading, manifest resources will
// start fetching immediately. This one time we need to do it
// ourselves.
this._startFetching(aDocument);
let browser = browserRef.get();
if (browser && browser.messageManager) {
browser.messageManager.sendAsyncMessage("OfflineApps:StartFetching", {
docId,
});
}
},
disallowSite: function(aDocument) {
Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.DENY_ACTION);
},
manage: function() {
manage() {
openAdvancedPreferences("networkTab");
},
_startFetching: function(aDocument) {
if (!aDocument.documentElement)
return;
var manifest = aDocument.documentElement.getAttribute("manifest");
if (!manifest)
return;
var manifestURI = makeURI(manifest, aDocument.characterSet,
aDocument.documentURIObject);
var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"].
getService(Ci.nsIOfflineCacheUpdateService);
updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject,
aDocument.nodePrincipal, window);
receiveMessage(msg) {
switch (msg.name) {
case "OfflineApps:CheckUsage":
let uri = makeURI(msg.data.uri);
if (this._usedMoreThanWarnQuota(uri)) {
this.warnUsage(msg.target, uri);
}
break;
case "OfflineApps:RequestPermission":
this.requestPermission(msg.target, msg.data.docId, makeURI(msg.data.uri));
break;
}
},
/////////////////////////////////////////////////////////////////////////////
// nsIObserver
observe: function (aSubject, aTopic, aState)
{
if (aTopic == "offline-cache-update-completed") {
var cacheUpdate = aSubject.QueryInterface(Ci.nsIOfflineCacheUpdate);
var uri = cacheUpdate.manifestURI;
if (OfflineApps._checkUsage(uri)) {
var browser = this._getBrowserForCacheUpdate(cacheUpdate);
if (browser) {
OfflineApps._warnUsage(browser, cacheUpdate.manifestURI);
}
}
}
}
init() {
let mm = window.messageManager;
mm.addMessageListener("OfflineApps:CheckUsage", this);
mm.addMessageListener("OfflineApps:RequestPermission", this);
},
};
var IndexedDBPromptHelper = {

Просмотреть файл

@ -1318,3 +1318,110 @@ var PageInfoListener = {
}
};
PageInfoListener.init();
let OfflineApps = {
_docId: 0,
_docIdMap: new Map(),
_docManifestSet: new Set(),
_observerAdded: false,
registerWindow(aWindow) {
if (!this._observerAdded) {
this._observerAdded = true;
Services.obs.addObserver(this, "offline-cache-update-completed", true);
}
let manifestURI = this._getManifestURI(aWindow);
this._docManifestSet.add(manifestURI.spec);
},
handleEvent(event) {
if (event.type == "MozApplicationManifest") {
this.offlineAppRequested(event.originalTarget.defaultView);
}
},
_getManifestURI(aWindow) {
if (!aWindow.document.documentElement)
return null;
var attr = aWindow.document.documentElement.getAttribute("manifest");
if (!attr)
return null;
try {
var contentURI = BrowserUtils.makeURI(aWindow.location.href, null, null);
return BrowserUtils.makeURI(attr, aWindow.document.characterSet, contentURI);
} catch (e) {
return null;
}
},
offlineAppRequested(aContentWindow) {
this.registerWindow(aContentWindow);
if (!Services.prefs.getBoolPref("browser.offline-apps.notify")) {
return;
}
let currentURI = aContentWindow.document.documentURIObject;
// don't bother showing UI if the user has already made a decision
if (Services.perms.testExactPermission(currentURI, "offline-app") != Services.perms.UNKNOWN_ACTION)
return;
try {
if (Services.prefs.getBoolPref("offline-apps.allow_by_default")) {
// all pages can use offline capabilities, no need to ask the user
return;
}
} catch(e) {
// this pref isn't set by default, ignore failures
}
let docId = ++this._docId;
this._docIdMap.set(docId, Cu.getWeakReference(aContentWindow.document));
sendAsyncMessage("OfflineApps:RequestPermission", {
uri: currentURI.spec,
docId,
});
},
_startFetching(aDocument) {
if (!aDocument.documentElement)
return;
let manifestURI = this._getManifestURI(aDocument.defaultView);
if (!manifestURI)
return;
var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"].
getService(Ci.nsIOfflineCacheUpdateService);
updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject,
aDocument.nodePrincipal, aDocument.defaultView);
},
receiveMessage(aMessage) {
if (aMessage.name == "OfflineApps:StartFetching") {
let doc = this._docIdMap.get(aMessage.data.docId);
doc = doc && doc.get();
if (doc) {
this._startFetching(doc);
}
this._docIdMap.delete(aMessage.data.docId);
}
},
observe(aSubject, aTopic, aState) {
if (aTopic == "offline-cache-update-completed") {
let cacheUpdate = aSubject.QueryInterface(Ci.nsIOfflineCacheUpdate);
let uri = cacheUpdate.manifestURI;
if (uri && this._docManifestSet.has(uri.spec)) {
sendAsyncMessage("OfflineApps:CheckUsage", {uri: uri.spec});
}
}
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
};
addEventListener("MozApplicationManifest", OfflineApps, false);
addMessageListener("OfflineApps:StartFetching", OfflineApps);

Просмотреть файл

@ -15,6 +15,8 @@ registerCleanupFunction(function() {
Services.perms.removeFromPrincipal(principal, "offline-app");
Services.prefs.clearUserPref("offline-apps.quota.warn");
Services.prefs.clearUserPref("offline-apps.allow_by_default");
let {OfflineAppCacheHelper} = Components.utils.import("resource:///modules/offlineAppCache.jsm", {});
OfflineAppCacheHelper.clear();
});
// Same as the other one, but for in-content preferences
@ -44,10 +46,21 @@ function test() {
// Wait for a notification that asks whether to allow offline storage.
promiseNotification(),
// Wait for the tab to load.
BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser)
BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser),
]).then(() => {
gBrowser.selectedBrowser.contentWindow.applicationCache.oncached = function() {
executeSoon(function() {
info("Loaded page, adding onCached handler");
// Need a promise to keep track of when we've added our handler.
let mm = gBrowser.selectedBrowser.messageManager;
let onCachedAttached = BrowserTestUtils.waitForMessage(mm, "Test:OnCachedAttached");
let gotCached = ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
return new Promise(resolve => {
content.window.applicationCache.oncached = function() {
setTimeout(resolve, 0);
};
sendAsyncMessage("Test:OnCachedAttached");
});
});
gotCached.then(function() {
// We got cached - now we should have provoked the quota warning.
let notification = PopupNotifications.getNotification('offline-app-usage');
ok(notification, "have offline-app-usage notification");
@ -62,13 +75,14 @@ function test() {
})
}, true);
});
};
onCachedAttached.then(function() {
Services.prefs.setIntPref("offline-apps.quota.warn", 1);
// Click the notification panel's "Allow" button. This should kick
// off updates which will call our oncached handler above.
PopupNotifications.panel.firstElementChild.button.click();
});
});
}
function promiseNotification() {

Просмотреть файл

@ -503,18 +503,22 @@ var gAdvancedPane = {
},
// XXX: duplicated in browser.js
_getOfflineAppUsage: function (perm, groups)
{
var cacheService = Components.classes["@mozilla.org/network/application-cache-service;1"].
getService(Components.interfaces.nsIApplicationCacheService);
var ios = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
_getOfflineAppUsage(perm, groups) {
let cacheService = Cc["@mozilla.org/network/application-cache-service;1"].
getService(Ci.nsIApplicationCacheService);
if (!groups) {
try {
groups = cacheService.getGroups();
} catch (ex) {
return 0;
}
}
var usage = 0;
for (var i = 0; i < groups.length; i++) {
var uri = ios.newURI(groups[i], null, null);
let usage = 0;
for (let group of groups) {
let uri = Services.io.newURI(group, null, null);
if (perm.matchesURI(uri, true)) {
var cache = cacheService.getActiveCache(groups[i]);
let cache = cacheService.getActiveCache(groups);
usage += cache.usage;
}
}

Просмотреть файл

@ -1130,3 +1130,15 @@ var ViewSelectionSource = {
};
ViewSelectionSource.init();
addEventListener("MozApplicationManifest", function(e) {
let doc = e.target;
let info = {
uri: doc.documentURI,
characterSet: doc.characterSet,
manifest: doc.documentElement.getAttribute("manifest"),
principal: doc.nodePrincipal,
};
sendAsyncMessage("MozApplicationManifest", info);
}, false);

Просмотреть файл

@ -498,6 +498,13 @@
return val;"
onget="return this.getAttribute('showresizer') == 'true';"/>
<property name="manifestURI"
readonly="true">
<getter><![CDATA[
return this.contentDocument.documentElement &&
this.contentDocument.documentElement.getAttribute("manifest");
]]></getter>
</property>
<property name="fullZoom">
<getter><![CDATA[
@ -1219,6 +1226,7 @@
"_textZoom",
"_isSyntheticDocument",
"_innerWindowID",
"_manifestURI",
]);
}

Просмотреть файл

@ -259,6 +259,11 @@
]]></body>
</method>
<field name="_manifestURI"/>
<property name="manifestURI"
onget="return this._manifestURI"
readonly="true"/>
<field name="mDestroyed">false</field>
<field name="_permitUnloadId">0</field>
@ -363,6 +368,7 @@
this.messageManager.addMessageListener("ZoomChangeUsingMouseWheel", this);
this.messageManager.addMessageListener("DOMFullscreen:RequestExit", this);
this.messageManager.addMessageListener("DOMFullscreen:RequestRollback", this);
this.messageManager.addMessageListener("MozApplicationManifest", this);
this.messageManager.loadFrameScript("chrome://global/content/browser-child.js", true);
if (this.hasAttribute("selectmenulist")) {
@ -497,6 +503,10 @@
break;
}
case "MozApplicationManifest":
this._manifestURI = aMessage.data.manifest;
break;
default:
// Delegate to browser.xml.
return this._receiveMessage(aMessage);