diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index c1e0ac38a167..e6b325212317 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -68,9 +68,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils", XPCOMUtils.defineLazyModuleGetter(this, "webappsUI", "resource:///modules/webappsUI.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs", - "resource:///modules/PageThumbs.jsm"); - const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser"; const PREF_PLUGINS_UPDATEURL = "plugins.update.url"; @@ -361,8 +358,6 @@ BrowserGlue.prototype = { // Initialize webapps UI webappsUI.init(); - PageThumbs.init(); - Services.obs.notifyObservers(null, "browser-ui-startup-complete", ""); }, @@ -384,7 +379,6 @@ BrowserGlue.prototype = { _onProfileShutdown: function BG__onProfileShutdown() { this._shutdownPlaces(); this._sanitizer.onShutdown(); - PageThumbs.uninit(); }, // All initial windows have opened. diff --git a/browser/components/thumbnails/PageThumbs.jsm b/browser/components/thumbnails/PageThumbs.jsm index 92b364fb3b91..3330718d4e1a 100644 --- a/browser/components/thumbnails/PageThumbs.jsm +++ b/browser/components/thumbnails/PageThumbs.jsm @@ -4,7 +4,7 @@ "use strict"; -let EXPORTED_SYMBOLS = ["PageThumbs", "PageThumbsStorage", "PageThumbsCache"]; +let EXPORTED_SYMBOLS = ["PageThumbs", "PageThumbsCache"]; const Cu = Components.utils; const Cc = Components.classes; @@ -12,11 +12,6 @@ const Ci = Components.interfaces; const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml"; -/** - * Name of the directory in the profile that contains the thumbnails. - */ -const THUMBNAIL_DIRECTORY = "thumbnails"; - /** * The default background color for page thumbnails. */ @@ -30,28 +25,12 @@ XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", - "resource://gre/modules/PlacesUtils.jsm"); - -XPCOMUtils.defineLazyGetter(this, "gCryptoHash", function () { - return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash); -}); - -XPCOMUtils.defineLazyGetter(this, "gUnicodeConverter", function () { - let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] - .createInstance(Ci.nsIScriptableUnicodeConverter); - converter.charset = 'utf8'; - return converter; -}); - /** * Singleton providing functionality for capturing web page thumbnails and for * accessing them if already cached. */ let PageThumbs = { + /** * The calculated width and height of the thumbnails. */ @@ -73,14 +52,6 @@ let PageThumbs = { */ get contentType() "image/png", - init: function PageThumbs_init() { - PlacesUtils.history.addObserver(PageThumbsHistoryObserver, false); - }, - - uninit: function PageThumbs_uninit() { - PlacesUtils.history.removeObserver(PageThumbsHistoryObserver); - }, - /** * Gets the thumbnail image's url for a given web page's url. * @param aUrl The web page's url that is depicted in the thumbnail. @@ -153,14 +124,32 @@ let PageThumbs = { // Sync and therefore also redirect sources appear on the newtab // page. We also want thumbnails for those. if (url != originalURL) - PageThumbsStorage.copy(url, originalURL); + PageThumbsCache._copy(url, originalURL); } if (aCallback) aCallback(aSuccessful); } - PageThumbsStorage.write(url, aInputStream, finish); + // Get a writeable cache entry. + PageThumbsCache.getWriteEntry(url, function (aEntry) { + if (!aEntry) { + finish(false); + return; + } + + let outputStream = aEntry.openOutputStream(0); + + // Write the image data to the cache entry. + NetUtil.asyncCopy(aInputStream, outputStream, function (aResult) { + let success = Components.isSuccessCode(aResult); + if (success) + aEntry.markValid(); + + aEntry.close(); + finish(success); + }); + }); }); }, @@ -208,7 +197,7 @@ let PageThumbs = { */ _getThumbnailSize: function PageThumbs_getThumbnailSize() { if (!this._thumbnailWidth || !this._thumbnailHeight) { - let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"] + let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"] .getService(Ci.nsIScreenManager); let left = {}, top = {}, width = {}, height = {}; screenManager.primaryScreen.GetRect(left, top, width, height); @@ -219,88 +208,6 @@ let PageThumbs = { } }; -let PageThumbsStorage = { - getFileForURL: function Storage_getFileForURL(aURL) { - let hash = this._calculateMD5Hash(aURL); - let parts = [THUMBNAIL_DIRECTORY, hash[0], hash[1], hash.slice(2) + ".png"]; - return FileUtils.getFile("ProfD", parts); - }, - - write: function Storage_write(aURL, aDataStream, aCallback) { - let file = this.getFileForURL(aURL); - let fos = FileUtils.openSafeFileOutputStream(file); - - NetUtil.asyncCopy(aDataStream, fos, function (aResult) { - FileUtils.closeSafeFileOutputStream(fos); - aCallback(Components.isSuccessCode(aResult)); - }); - }, - - copy: function Storage_copy(aSourceURL, aTargetURL) { - let sourceFile = this.getFileForURL(aSourceURL); - let targetFile = this.getFileForURL(aTargetURL); - - try { - sourceFile.copyTo(targetFile.parent, targetFile.leafName); - } catch (e) { - /* We might not be permitted to write to the file. */ - } - }, - - remove: function Storage_remove(aURL) { - try { - this.getFileForURL(aURL).remove(false); - } catch (e) { - /* The file might not exist or we're not permitted to remove it. */ - } - }, - - wipe: function Storage_wipe() { - try { - FileUtils.getDir("ProfD", [THUMBNAIL_DIRECTORY]).remove(true); - } catch (e) { - /* The file might not exist or we're not permitted to remove it. */ - } - }, - - _calculateMD5Hash: function Storage_calculateMD5Hash(aValue) { - let hash = gCryptoHash; - let value = gUnicodeConverter.convertToByteArray(aValue); - - hash.init(hash.MD5); - hash.update(value, value.length); - return this._convertToHexString(hash.finish(false)); - }, - - _convertToHexString: function Storage_convertToHexString(aData) { - let hex = ""; - for (let i = 0; i < aData.length; i++) - hex += ("0" + aData.charCodeAt(i).toString(16)).slice(-2); - return hex; - }, - -}; - -let PageThumbsHistoryObserver = { - onDeleteURI: function Thumbnails_onDeleteURI(aURI, aGUID) { - PageThumbsStorage.remove(aURI.spec); - }, - - onClearHistory: function Thumbnails_onClearHistory() { - PageThumbsStorage.wipe(); - }, - - onTitleChanged: function () {}, - onBeginUpdateBatch: function () {}, - onEndUpdateBatch: function () {}, - onVisit: function () {}, - onBeforeDeleteURI: function () {}, - onPageChanged: function () {}, - onDeleteVisits: function () {}, - - QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver]) -}; - /** * A singleton handling the storage of page thumbnails. */ @@ -315,6 +222,64 @@ let PageThumbsCache = { this._openCacheEntry(aKey, Ci.nsICache.ACCESS_READ, aCallback); }, + /** + * Calls the given callback with a cache entry opened for writing. + * @param aKey The key identifying the desired cache entry. + * @param aCallback The callback that is called when the cache entry is ready. + */ + getWriteEntry: function Cache_getWriteEntry(aKey, aCallback) { + // Try to open the desired cache entry. + this._openCacheEntry(aKey, Ci.nsICache.ACCESS_WRITE, aCallback); + }, + + /** + * Copies an existing cache entry's data to a new cache entry. + * @param aSourceKey The key that contains the data to copy. + * @param aTargetKey The key that will be the copy of aSourceKey's data. + */ + _copy: function Cache_copy(aSourceKey, aTargetKey) { + let sourceEntry, targetEntry, waitingCount = 2; + + function finish() { + if (sourceEntry) + sourceEntry.close(); + + if (targetEntry) + targetEntry.close(); + } + + function copyDataWhenReady() { + if (--waitingCount > 0) + return; + + if (!sourceEntry || !targetEntry) { + finish(); + return; + } + + let inputStream = sourceEntry.openInputStream(0); + let outputStream = targetEntry.openOutputStream(0); + + // Copy the image data to a new entry. + NetUtil.asyncCopy(inputStream, outputStream, function (aResult) { + if (Components.isSuccessCode(aResult)) + targetEntry.markValid(); + + finish(); + }); + } + + this.getReadEntry(aSourceKey, function (aSourceEntry) { + sourceEntry = aSourceEntry; + copyDataWhenReady(); + }); + + this.getWriteEntry(aTargetKey, function (aTargetEntry) { + targetEntry = aTargetEntry; + copyDataWhenReady(); + }); + }, + /** * Opens the cache entry identified by the given key. * @param aKey The key identifying the desired cache entry. diff --git a/browser/components/thumbnails/PageThumbsProtocol.js b/browser/components/thumbnails/PageThumbsProtocol.js index d72e82798579..4cf06067b73a 100644 --- a/browser/components/thumbnails/PageThumbsProtocol.js +++ b/browser/components/thumbnails/PageThumbsProtocol.js @@ -72,14 +72,6 @@ Protocol.prototype = { * @return The newly created channel. */ newChannel: function Proto_newChannel(aURI) { - let {url} = parseURI(aURI); - let file = PageThumbsStorage.getFileForURL(url); - - if (file.exists()) { - let fileuri = Services.io.newFileURI(file); - return Services.io.newChannelFromURI(fileuri); - } - return new Channel(aURI); }, diff --git a/browser/components/thumbnails/test/Makefile.in b/browser/components/thumbnails/test/Makefile.in index c7af21dc9a55..ac00a023a678 100644 --- a/browser/components/thumbnails/test/Makefile.in +++ b/browser/components/thumbnails/test/Makefile.in @@ -14,7 +14,6 @@ include $(topsrcdir)/config/rules.mk _BROWSER_FILES = \ browser_thumbnails_capture.js \ browser_thumbnails_redirect.js \ - browser_thumbnails_storage.js \ browser_thumbnails_bug726727.js \ head.js \ background_red.html \ diff --git a/browser/components/thumbnails/test/browser_thumbnails_redirect.js b/browser/components/thumbnails/test/browser_thumbnails_redirect.js index 7bc420c1ed62..54803e1e348a 100644 --- a/browser/components/thumbnails/test/browser_thumbnails_redirect.js +++ b/browser/components/thumbnails/test/browser_thumbnails_redirect.js @@ -4,6 +4,9 @@ const URL = "http://mochi.test:8888/browser/browser/components/thumbnails/" + "test/background_red_redirect.sjs"; +let cacheService = Cc["@mozilla.org/network/cache-service;1"] + .getService(Ci.nsICacheService); + /** * These tests ensure that we save and provide thumbnails for redirecting sites. */ @@ -16,17 +19,33 @@ function runTests() { yield addTab(URL); yield captureAndCheckColor(255, 0, 0, "we have a red thumbnail"); - // Wait until the referrer's thumbnail's file has been written. - yield whenFileExists(URL); + // Wait until the referrer's thumbnail's cache entry has been written. + yield whenCacheEntryExists(URL); yield checkThumbnailColor(URL, 255, 0, 0, "referrer has a red thumbnail"); } -function whenFileExists(aURL) { +function whenCacheEntryExists(aKey) { let callback = next; - let file = PageThumbsStorage.getFileForURL(aURL); - if (!file.exists()) - callback = function () whenFileExists(aURL); + checkCacheEntryExists(aKey, function (aExists) { + if (!aExists) + callback = function () whenCacheEntryExists(aKey); - executeSoon(callback); + executeSoon(callback); + }); +} + +function checkCacheEntryExists(aKey, aCallback) { + PageThumbsCache.getReadEntry(aKey, function (aEntry) { + let inputStream = aEntry && aEntry.openInputStream(0); + let exists = inputStream && inputStream.available(); + + if (inputStream) + inputStream.close(); + + if (aEntry) + aEntry.close(); + + aCallback(exists); + }); } diff --git a/browser/components/thumbnails/test/browser_thumbnails_storage.js b/browser/components/thumbnails/test/browser_thumbnails_storage.js deleted file mode 100644 index db91a980173e..000000000000 --- a/browser/components/thumbnails/test/browser_thumbnails_storage.js +++ /dev/null @@ -1,89 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -const URL = "http://mochi.test:8888/"; -const URL_COPY = URL + "#copy"; - -XPCOMUtils.defineLazyGetter(this, "Sanitizer", function () { - let tmp = {}; - Cc["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Ci.mozIJSSubScriptLoader) - .loadSubScript("chrome://browser/content/sanitize.js", tmp); - return tmp.Sanitizer; -}); - -/** - * These tests ensure that the thumbnail storage is working as intended. - * Newly captured thumbnails should be saved as files and they should as well - * be removed when the user sanitizes their history. - */ -function runTests() { - clearHistory(); - - // create a thumbnail - yield addTab(URL); - yield whenFileExists(); - gBrowser.removeTab(gBrowser.selectedTab); - - // clear all browser history - yield clearHistory(); - - // create a thumbnail - yield addTab(URL); - yield whenFileExists(); - gBrowser.removeTab(gBrowser.selectedTab); - - // make sure copy() updates an existing file - PageThumbsStorage.copy(URL, URL_COPY); - let copy = PageThumbsStorage.getFileForURL(URL_COPY); - let mtime = copy.lastModifiedTime -= 60; - - PageThumbsStorage.copy(URL, URL_COPY); - isnot(PageThumbsStorage.getFileForURL(URL_COPY).lastModifiedTime, mtime, - "thumbnail file was updated"); - - // clear last 10 mins of history - yield clearHistory(true); - ok(!copy.exists(), "copy of thumbnail has been removed"); -} - -function clearHistory(aUseRange) { - let s = new Sanitizer(); - s.prefDomain = "privacy.cpd."; - - let prefs = gPrefService.getBranch(s.prefDomain); - prefs.setBoolPref("history", true); - prefs.setBoolPref("downloads", false); - prefs.setBoolPref("cache", false); - prefs.setBoolPref("cookies", false); - prefs.setBoolPref("formdata", false); - prefs.setBoolPref("offlineApps", false); - prefs.setBoolPref("passwords", false); - prefs.setBoolPref("sessions", false); - prefs.setBoolPref("siteSettings", false); - - if (aUseRange) { - let usec = Date.now() * 1000; - s.range = [usec - 10 * 60 * 1000 * 1000, usec]; - } - - s.sanitize(); - s.range = null; - - executeSoon(function () { - if (PageThumbsStorage.getFileForURL(URL).exists()) - clearHistory(aFile, aUseRange); - else - next(); - }); -} - -function whenFileExists() { - let callback = whenFileExists; - - let file = PageThumbsStorage.getFileForURL(URL); - if (file.exists() && file.fileSize) - callback = next; - - executeSoon(callback); -} diff --git a/browser/components/thumbnails/test/head.js b/browser/components/thumbnails/test/head.js index 6a4ee10335db..6bfd76e7d300 100644 --- a/browser/components/thumbnails/test/head.js +++ b/browser/components/thumbnails/test/head.js @@ -4,7 +4,7 @@ let tmp = {}; Cu.import("resource:///modules/PageThumbs.jsm", tmp); let PageThumbs = tmp.PageThumbs; -let PageThumbsStorage = tmp.PageThumbsStorage; +let PageThumbsCache = tmp.PageThumbsCache; registerCleanupFunction(function () { while (gBrowser.tabs.length > 1)