Bug 745303 - Part 1 - Make Panorama use the thumbnail service; r=dietrich

This commit is contained in:
Tim Taubert 2012-04-19 08:24:32 +02:00
parent 80b5199be2
commit 8fd12e8742
6 changed files with 26 additions and 750 deletions

View File

@ -1,38 +1,6 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is content.js.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Tim Taubert <ttaubert@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
@ -52,14 +20,6 @@ __defineGetter__("webProgress", function () {
//
// Handles events dispatched by the content window.
let WindowEventHandler = {
// ----------
// Function: onDOMContentLoaded
// Sends an asynchronous message when the "onDOMContentLoaded" event for the
// current page is fired.
onDOMContentLoaded: function WEH_onDOMContentLoaded(event) {
sendAsyncMessage("Panorama:DOMContentLoaded");
},
// ----------
// Function: onDOMWillOpenModalDialog
// Sends a synchronous message when the "onDOMWillOpenModalDialog" event
@ -86,7 +46,6 @@ let WindowEventHandler = {
};
// add event listeners
addEventListener("DOMContentLoaded", WindowEventHandler.onDOMContentLoaded, false);
addEventListener("DOMWillOpenModalDialog", WindowEventHandler.onDOMWillOpenModalDialog, false);
addEventListener("MozAfterPaint", WindowEventHandler.onMozAfterPaint, false);
@ -119,91 +78,3 @@ let WindowMessageHandler = {
addMessageListener("Panorama:isDocumentLoaded", WindowMessageHandler.isDocumentLoaded);
addMessageListener("Panorama:isImageDocument", WindowMessageHandler.isImageDocument);
// ----------
// WebProgressListener
//
// Observe the web progress of content pages loaded into this browser. When the
// state of a page changes we check if we're still allowed to store page
// information permanently.
let WebProgressListener = {
// ----------
// Function: onStateChange
// Called by the webProgress when its state changes.
onStateChange: function WPL_onStateChange(webProgress, request, flag, status) {
// The browser just started loading (again). Explicitly grant storage
// because the browser might have been blocked before (e.g. when navigating
// from a https-page to a http-page).
if (flag & Ci.nsIWebProgressListener.STATE_START) {
// ensure the dom window is the top one
if (this._isTopWindow(webProgress))
sendAsyncMessage("Panorama:StoragePolicy:granted");
}
// The browser finished loading - check the cache control headers. Send
// a message if we're not allowed to store information about this page.
if (flag & Ci.nsIWebProgressListener.STATE_STOP) {
// ensure the dom window is the top one
if (this._isTopWindow(webProgress) &&
request && request instanceof Ci.nsIHttpChannel) {
request.QueryInterface(Ci.nsIHttpChannel);
let exclude = false;
let reason = "";
// Check if the "Cache-Control" header is "no-store". In this case we're
// not allowed to store information about the current page.
if (this._isNoStoreResponse(request)) {
exclude = true;
reason = "no-store";
}
// Otherwise we'll deny storage if we're currently viewing a https
// page without a "Cache-Control: public" header.
else if (request.URI.schemeIs("https")) {
let cacheControlHeader = this._getCacheControlHeader(request);
if (cacheControlHeader && !(/public/i).test(cacheControlHeader)) {
exclude = true;
reason = "https";
}
}
if (exclude)
sendAsyncMessage("Panorama:StoragePolicy:denied", {reason: reason});
}
}
},
// ----------
// Function: _isTopWindow
// Returns whether the DOMWindow associated with the webProgress is the
// top content window (and not an iframe or similar).
_isTopWindow: function WPL__isTopWindow(webProgress) {
// can throw if there's no associated DOMWindow
return !!Utils.attempt(function () webProgress.DOMWindow == content);
},
// ----------
// Function: _isNoStoreResponse
// Checks if the "Cache-Control" header is "no-store".
_isNoStoreResponse: function WPL__isNoStoreResponse(req) {
// can throw if called before the response has been received
return !!Utils.attempt(function () req.isNoStoreResponse());
},
// ----------
// Function: _getCacheControlHeader
// Returns the value of the "Cache-Control" header.
_getCacheControlHeader: function WPL__getCacheControlHeader(req) {
// can throw when the "Cache-Control" header doesn't exist
return Utils.attempt(function () req.getResponseHeader("Cache-Control"));
},
// ----------
// Implements progress listener interface.
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference,
Ci.nsISupports])
};
// add web progress listener
webProgress.addProgressListener(WebProgressListener, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);

View File

@ -1,208 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is storagePolicy.js.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Tim Taubert <ttaubert@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// **********
// Title: storagePolicy.js
// ##########
// Class: StoragePolicy
// Singleton for implementing a storage policy for sensitive data.
let StoragePolicy = {
// Pref that controls whether we can store SSL content on disk
PREF_DISK_CACHE_SSL: "browser.cache.disk_cache_ssl",
// Used to keep track of disk_cache_ssl preference
_enablePersistentHttpsCaching: null,
// Used to keep track of browsers whose data we shouldn't store permanently
_deniedBrowsers: [],
// ----------
// Function: toString
// Prints [StoragePolicy] for debug use.
toString: function StoragePolicy_toString() {
return "[StoragePolicy]";
},
// ----------
// Function: init
// Initializes the StoragePolicy object.
init: function StoragePolicy_init() {
// store the preference value
this._enablePersistentHttpsCaching =
Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
Services.prefs.addObserver(this.PREF_DISK_CACHE_SSL, this, false);
// tabs are already loaded before UI is initialized so cache-control
// values are unknown. We add browsers with https to the list for now.
if (!this._enablePersistentHttpsCaching)
Array.forEach(gBrowser.browsers, this._initializeBrowser.bind(this));
// make sure to remove tab browsers when tabs get closed
this._onTabClose = this._onTabClose.bind(this);
gBrowser.tabContainer.addEventListener("TabClose", this._onTabClose, false);
let mm = gWindow.messageManager;
// add message listeners for storage granted
this._onGranted = this._onGranted.bind(this);
mm.addMessageListener("Panorama:StoragePolicy:granted", this._onGranted);
// add message listeners for storage denied
this._onDenied = this._onDenied.bind(this);
mm.addMessageListener("Panorama:StoragePolicy:denied", this._onDenied);
},
// ----------
// Function: _initializeBrowser
// Initializes the given browser and checks if we need to add it to our
// internal exclusion list.
_initializeBrowser: function StoragePolicy__initializeBrowser(browser) {
let self = this;
function checkExclusion() {
if (browser.currentURI.schemeIs("https"))
self._deniedBrowsers.push(browser);
}
function waitForDocumentLoad() {
let mm = browser.messageManager;
mm.addMessageListener("Panorama:DOMContentLoaded", function onLoad(cx) {
mm.removeMessageListener(cx.name, onLoad);
checkExclusion(browser);
});
}
this._isDocumentLoaded(browser, function (isLoaded) {
if (isLoaded)
checkExclusion();
else
waitForDocumentLoad();
});
},
// ----------
// Function: _isDocumentLoaded
// Check if the given browser's document is loaded.
_isDocumentLoaded: function StoragePolicy__isDocumentLoaded(browser, callback) {
let mm = browser.messageManager;
let message = "Panorama:isDocumentLoaded";
mm.addMessageListener(message, function onMessage(cx) {
mm.removeMessageListener(cx.name, onMessage);
callback(cx.json.isLoaded);
});
mm.sendAsyncMessage(message);
},
// ----------
// Function: uninit
// Is called by UI.init() when the browser windows is closed.
uninit: function StoragePolicy_uninit() {
Services.prefs.removeObserver(this.PREF_DISK_CACHE_SSL, this);
gBrowser.removeTabsProgressListener(this);
gBrowser.tabContainer.removeEventListener("TabClose", this._onTabClose, false);
let mm = gWindow.messageManager;
// remove message listeners
mm.removeMessageListener("Panorama:StoragePolicy:granted", this._onGranted);
mm.removeMessageListener("Panorama:StoragePolicy:denied", this._onDenied);
},
// ----------
// Function: _onGranted
// Handle the 'granted' message and remove the given browser from the list
// of denied browsers.
_onGranted: function StoragePolicy__onGranted(cx) {
let index = this._deniedBrowsers.indexOf(cx.target);
if (index > -1)
this._deniedBrowsers.splice(index, 1);
},
// ----------
// Function: _onDenied
// Handle the 'denied' message and add the given browser to the list of denied
// browsers.
_onDenied: function StoragePolicy__onDenied(cx) {
// exclusion is optional because cache-control is not no-store or public and
// the protocol is https. don't exclude when persistent https caching is
// enabled.
if ("https" == cx.json.reason && this._enablePersistentHttpsCaching)
return;
let browser = cx.target;
if (this._deniedBrowsers.indexOf(browser) == -1)
this._deniedBrowsers.push(browser);
},
// ----------
// Function: _onTabClose
// Remove the browser from our internal exclusion list when a tab gets closed.
_onTabClose: function StoragePolicy__onTabClose(event) {
let browser = event.target.linkedBrowser;
let index = this._deniedBrowsers.indexOf(browser);
if (index > -1)
this._deniedBrowsers.splice(index, 1);
},
// ----------
// Function: canStoreThumbnailForTab
// Returns whether we're allowed to store the thumbnail of the given tab.
canStoreThumbnailForTab: function StoragePolicy_canStoreThumbnailForTab(tab) {
// deny saving thumbnails in private browsing mode
if (gPrivateBrowsing.privateBrowsingEnabled &&
UI._privateBrowsing.transitionMode != "enter")
return false;
return (this._deniedBrowsers.indexOf(tab.linkedBrowser) == -1);
},
// ----------
// Function: observe
// Observe pref changes.
observe: function StoragePolicy_observe(subject, topic, data) {
this._enablePersistentHttpsCaching =
Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
}
};

View File

@ -68,8 +68,7 @@ function TabItem(tab, options) {
let div = document.body.lastChild;
let $div = iQ(div);
this._cachedImageData = null;
this._thumbnailNeedsSaving = false;
this._showsCachedData = false;
this.canvasSizeForced = false;
this.$thumb = iQ('.thumb', $div);
this.$fav = iQ('.favicon', $div);
@ -81,13 +80,6 @@ function TabItem(tab, options) {
this.tabCanvas = new TabCanvas(this.tab, this.$canvas[0]);
let self = this;
// when we paint onto the canvas make sure our thumbnail gets saved
this.tabCanvas.addSubscriber("painted", function () {
self._thumbnailNeedsSaving = true;
});
this.defaultSize = new Point(TabItems.tabWidth, TabItems.tabHeight);
this._hidden = false;
this.isATabItem = true;
@ -123,6 +115,8 @@ function TabItem(tab, options) {
this.draggable();
let self = this;
// ___ more div setup
$div.mousedown(function(e) {
if (!Utils.isRightClick(e))
@ -190,35 +184,32 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
// Returns a boolean indicates whether the cached data is being displayed or
// not.
isShowingCachedData: function TabItem_isShowingCachedData() {
return (this._cachedImageData != null);
return this._showsCachedData;
},
// ----------
// Function: showCachedData
// Shows the cached data i.e. image and title. Note: this method should only
// be called at browser startup with the cached data avaliable.
//
// Parameters:
// imageData - the image data
showCachedData: function TabItem_showCachedData(imageData) {
this._cachedImageData = imageData;
this.$cachedThumb.attr("src", this._cachedImageData).show();
showCachedData: function TabItem_showCachedData() {
let {title, url} = this.getTabState();
let thumbnailURL = gPageThumbnails.getThumbnailURL(url);
this.$cachedThumb.attr("src", thumbnailURL).show();
this.$canvas.css({opacity: 0});
let {title, url} = this.getTabState();
this.$tabTitle.text(title).attr("title", title ? title + "\n" + url : url);
this._sendToSubscribers("showingCachedData");
let tooltip = (title && title != url ? title + "\n" + url : url);
this.$tabTitle.text(title).attr("title", tooltip);
this._showsCachedData = true;
},
// ----------
// Function: hideCachedData
// Hides the cached data i.e. image and title and show the canvas.
hideCachedData: function TabItem_hideCachedData() {
this.$cachedThumb.hide();
this.$cachedThumb.attr("src", "").hide();
this.$canvas.css({opacity: 1.0});
if (this._cachedImageData)
this._cachedImageData = null;
this._showsCachedData = false;
},
// ----------
@ -290,89 +281,6 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
// Function: loadThumbnail
// Loads the tabItems thumbnail.
loadThumbnail: function TabItem_loadThumbnail() {
let self = this;
function TabItem_loadThumbnail_callback(error, imageData) {
// we could have been unlinked while waiting for the thumbnail to load
if (!self.tab)
return;
if (error || !imageData) {
// paint the canvas to avoid leaving traces when dragging tab over it
self.tabCanvas.paint();
return;
}
self._sendToSubscribers("loadedCachedImageData");
// If we have a cached image, then show it if the loaded URL matches
// what the cache is from, OR the loaded URL is blank, which means
// that the page hasn't loaded yet.
let currentUrl = self.tab.linkedBrowser.currentURI.spec;
if (self.getTabState().url == currentUrl || currentUrl == "about:blank")
self.showCachedData(imageData);
}
ThumbnailStorage.loadThumbnail(this.getTabState().url, TabItem_loadThumbnail_callback);
},
// ----------
// Function: saveThumbnail
// Saves the tabItems thumbnail.
saveThumbnail: function TabItem_saveThumbnail(options) {
if (!this.tabCanvas)
return;
// nothing to do if the thumbnail hasn't changed
if (!this._thumbnailNeedsSaving)
return;
// check the storage policy to see if we're allowed to store the thumbnail
if (!StoragePolicy.canStoreThumbnailForTab(this.tab)) {
this._sendToSubscribers("deniedToSaveImageData");
return;
}
let url = this.tab.linkedBrowser.currentURI.spec;
let delayed = this._saveThumbnailDelayed;
let synchronously = (options && options.synchronously);
// is there a delayed save waiting?
if (delayed) {
// check if url has changed since last call to saveThumbnail
if (!synchronously && url == delayed.url)
return;
// url has changed in the meantime, clear the timeout
clearTimeout(delayed.timeout);
}
let self = this;
function callback(error) {
if (!error) {
self._thumbnailNeedsSaving = false;
self._sendToSubscribers("savedCachedImageData");
}
}
function doSaveThumbnail() {
self._saveThumbnailDelayed = null;
// we could have been unlinked in the meantime
if (!self.tabCanvas)
return;
let imageData = self.tabCanvas.toImageData();
ThumbnailStorage.saveThumbnail(url, imageData, callback, options);
}
if (synchronously) {
doSaveThumbnail();
} else {
let timeout = setTimeout(doSaveThumbnail, 2000);
this._saveThumbnailDelayed = {url: url, timeout: timeout};
}
},
// ----------
@ -394,7 +302,9 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
let groupItem;
if (tabData && TabItems.storageSanity(tabData)) {
this.loadThumbnail();
// Show the cached data while we're waiting for the tabItem to be updated.
// If the tab isn't restored yet this acts as a placeholder until it is.
this.showCachedData();
if (this.parent)
this.parent.remove(this, {immediately: true});
@ -823,7 +733,6 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
if (this.tabCanvas)
this.tabCanvas.paint();
this.saveThumbnail();
// ___ cache
if (this.isShowingCachedData())
@ -1085,7 +994,8 @@ let TabItems = {
// ___ URL
let tabUrl = tab.linkedBrowser.currentURI.spec;
tabItem.$container.attr("title", label + "\n" + tabUrl);
let tooltip = (label == tabUrl ? label : label + "\n" + tabUrl);
tabItem.$container.attr("title", tooltip);
// ___ Make sure the tab is complete and ready for updating.
if (options && options.force) {
@ -1315,17 +1225,6 @@ let TabItems = {
});
},
// ----------
// Function: saveAllThumbnails
// Saves thumbnails of all open <TabItem>s.
saveAllThumbnails: function TabItems_saveAllThumbnails(options) {
let tabItems = this.getItems();
tabItems.forEach(function TabItems_saveAllThumbnails_forEach(tabItem) {
tabItem.saveThumbnail(options);
});
},
// ----------
// Function: storageSanity
// Checks the specified data (as returned by TabItem.getStorageData or loaded from storage)

View File

@ -25,16 +25,11 @@ XPCOMUtils.defineLazyGetter(this, "gPrefBranch", function() {
return Services.prefs.getBranch("browser.panorama.");
});
XPCOMUtils.defineLazyGetter(this, "gPrivateBrowsing", function() {
return Cc["@mozilla.org/privatebrowsing;1"].
getService(Ci.nsIPrivateBrowsingService);
});
XPCOMUtils.defineLazyServiceGetter(this, "gPrivateBrowsing",
"@mozilla.org/privatebrowsing;1", "nsIPrivateBrowsingService");
XPCOMUtils.defineLazyGetter(this, "gNetUtil", function() {
var obj = {};
Cu.import("resource://gre/modules/NetUtil.jsm", obj);
return obj.NetUtil;
});
XPCOMUtils.defineLazyModuleGetter(this, "gPageThumbnails",
"resource:///modules/PageThumbs.jsm", "PageThumbs");
var gWindow = window.parent;
var gBrowser = gWindow.gBrowser;
@ -71,13 +66,11 @@ let AllTabs = {
#include iq.js
#include storage.js
#include storagePolicy.js
#include items.js
#include groupitems.js
#include tabitems.js
#include favicons.js
#include drag.js
#include trench.js
#include thumbnailStorage.js
#include search.js
#include ui.js

View File

@ -1,266 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is thumbnailStorage.js.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Raymond Lee <raymond@appcoast.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// **********
// Title: thumbnailStorage.js
// ##########
// Class: ThumbnailStorage
// Singleton for persistent storage of thumbnail data.
let ThumbnailStorage = {
CACHE_CLIENT_IDENTIFIER: "tabview-cache",
CACHE_PREFIX: "moz-panorama:",
// Holds the cache session reference
_cacheSession: null,
// Holds the string input stream reference
_stringInputStream: null,
// Holds the storage stream reference
_storageStream: null,
// ----------
// Function: toString
// Prints [ThumbnailStorage] for debug use.
toString: function ThumbnailStorage_toString() {
return "[ThumbnailStorage]";
},
// ----------
// Function: init
// Should be called when UI is initialized.
init: function ThumbnailStorage_init() {
// Create stream-based cache session for tabview
let cacheService =
Cc["@mozilla.org/network/cache-service;1"].
getService(Ci.nsICacheService);
this._cacheSession = cacheService.createSession(
this.CACHE_CLIENT_IDENTIFIER, Ci.nsICache.STORE_ON_DISK, true);
this._stringInputStream = Components.Constructor(
"@mozilla.org/io/string-input-stream;1", "nsIStringInputStream",
"setData");
this._storageStream = Components.Constructor(
"@mozilla.org/storagestream;1", "nsIStorageStream",
"init");
},
// ----------
// Function: _openCacheEntry
// Opens a cache entry for the given <url> and requests access <access>.
// Calls <successCallback>(entry) when the entry was successfully opened with
// requested access rights. Otherwise calls <errorCallback>().
//
// Parameters:
// url - the url to use as the storage key
// access - access flags, see Ci.nsICache.ACCESS_*
// successCallback - the callback to be called on success
// errorCallback - the callback to be called when an error occured
// options - an object with additional parameters, see below
//
// Possible options:
// synchronously - set to true to force sync mode
_openCacheEntry:
function ThumbnailStorage__openCacheEntry(url, access, successCallback,
errorCallback, options) {
Utils.assert(url, "invalid or missing argument <url>");
Utils.assert(access, "invalid or missing argument <access>");
Utils.assert(successCallback, "invalid or missing argument <successCallback>");
Utils.assert(errorCallback, "invalid or missing argument <errorCallback>");
function onCacheEntryAvailable(entry, accessGranted, status) {
if (entry && access == accessGranted && Components.isSuccessCode(status)) {
successCallback(entry);
} else {
if (entry)
entry.close();
errorCallback();
}
}
let key = this.CACHE_PREFIX + url;
if (options && options.synchronously) {
let entry = this._cacheSession.openCacheEntry(key, access, true);
let status = Cr.NS_OK;
onCacheEntryAvailable(entry, entry.accessGranted, status);
} else {
let listener = new CacheListener(onCacheEntryAvailable);
this._cacheSession.asyncOpenCacheEntry(key, access, listener);
}
},
// ----------
// Function: saveThumbnail
// Saves the given thumbnail in the cache.
//
// Parameters:
// url - the url to use as the storage key
// imageData - the image data to save for the given key
// callback - the callback that is called when the operation is finished
// options - an object with additional parameters, see below
//
// Possible options:
// synchronously - set to true to force sync mode
saveThumbnail:
function ThumbnailStorage_saveThumbnail(url, imageData, callback, options) {
Utils.assert(url, "invalid or missing argument <url>");
Utils.assert(imageData, "invalid or missing argument <imageData>");
Utils.assert(callback, "invalid or missing argument <callback>");
let synchronously = (options && options.synchronously);
let self = this;
function onCacheEntryAvailable(entry) {
let outputStream = entry.openOutputStream(0);
function cleanup() {
outputStream.close();
entry.close();
}
// synchronous mode
if (synchronously) {
outputStream.write(imageData, imageData.length);
cleanup();
callback();
return;
}
// asynchronous mode
let inputStream = new self._stringInputStream(imageData, imageData.length);
gNetUtil.asyncCopy(inputStream, outputStream, function (result) {
cleanup();
inputStream.close();
callback(Components.isSuccessCode(result) ? "" : "failure");
});
}
function onCacheEntryUnavailable() {
callback("unavailable");
}
this._openCacheEntry(url, Ci.nsICache.ACCESS_WRITE, onCacheEntryAvailable,
onCacheEntryUnavailable, options);
},
// ----------
// Function: loadThumbnail
// Loads a thumbnail from the cache.
//
// Parameters:
// url - the url to use as the storage key
// callback - the callback that is called when the operation is finished
loadThumbnail: function ThumbnailStorage_loadThumbnail(url, callback) {
Utils.assert(url, "invalid or missing argument <url>");
Utils.assert(callback, "invalid or missing argument <callback>");
let self = this;
function onCacheEntryAvailable(entry) {
let imageChunks = [];
let nativeInputStream = entry.openInputStream(0);
const CHUNK_SIZE = 0x10000; // 65k
const PR_UINT32_MAX = 0xFFFFFFFF;
let storageStream = new self._storageStream(CHUNK_SIZE, PR_UINT32_MAX, null);
let storageOutStream = storageStream.getOutputStream(0);
let cleanup = function () {
nativeInputStream.close();
storageStream.close();
storageOutStream.close();
entry.close();
}
gNetUtil.asyncCopy(nativeInputStream, storageOutStream, function (result) {
// cancel if parent window has already been closed
if (typeof UI == "undefined") {
cleanup();
return;
}
let imageData = null;
let isSuccess = Components.isSuccessCode(result);
if (isSuccess) {
let storageInStream = storageStream.newInputStream(0);
imageData = gNetUtil.readInputStreamToString(storageInStream,
storageInStream.available());
storageInStream.close();
}
cleanup();
callback(isSuccess ? "" : "failure", imageData);
});
}
function onCacheEntryUnavailable() {
callback("unavailable");
}
this._openCacheEntry(url, Ci.nsICache.ACCESS_READ, onCacheEntryAvailable,
onCacheEntryUnavailable);
}
}
// ##########
// Class: CacheListener
// Generic CacheListener for feeding to asynchronous cache calls.
// Calls <callback>(entry, access, status) when the requested cache entry
// is available.
function CacheListener(callback) {
Utils.assert(typeof callback == "function", "callback arg must be a function");
this.callback = callback;
};
CacheListener.prototype = {
// ----------
// Function: toString
// Prints [CacheListener] for debug use
toString: function CacheListener_toString() {
return "[CacheListener]";
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsICacheListener]),
onCacheEntryAvailable: function CacheListener_onCacheEntryAvailable(
entry, access, status) {
this.callback(entry, access, status);
}
};

View File

@ -158,15 +158,9 @@ let UI = {
// initialize the direction of the page
this._initPageDirection();
// ___ thumbnail storage
ThumbnailStorage.init();
// ___ storage
Storage.init();
// ___ storage policy
StoragePolicy.init();
if (Storage.readWindowBusyState(gWindow))
this.storageBusy();
@ -277,7 +271,6 @@ let UI = {
GroupItems.removeHiddenGroups();
TabItems.saveAll();
TabItems.saveAllThumbnails({synchronously: true});
self._save();
}, false);
@ -319,7 +312,6 @@ let UI = {
GroupItems.uninit();
FavIcons.uninit();
Storage.uninit();
StoragePolicy.uninit();
this._removeTabActionHandlers();
this._currentTab = null;
@ -717,11 +709,6 @@ let UI = {
if (data == "enter" || data == "exit") {
Search.hide();
self._privateBrowsing.transitionMode = data;
// make sure to save all thumbnails that haven't been saved yet
// before we enter the private browsing mode
if (data == "enter")
TabItems.saveAllThumbnails({synchronously: true});
}
} else if (topic == "private-browsing-transition-complete") {
// We use .transitionMode here, as aData is empty.