diff options
Diffstat (limited to 'toolkit/mozapps/extensions/internal/LightweightThemeImageOptimizer.jsm')
-rw-r--r-- | toolkit/mozapps/extensions/internal/LightweightThemeImageOptimizer.jsm | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/internal/LightweightThemeImageOptimizer.jsm b/toolkit/mozapps/extensions/internal/LightweightThemeImageOptimizer.jsm new file mode 100644 index 000000000..fccde9a81 --- /dev/null +++ b/toolkit/mozapps/extensions/internal/LightweightThemeImageOptimizer.jsm @@ -0,0 +1,198 @@ +/* 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"; + +this.EXPORTED_SYMBOLS = ["LightweightThemeImageOptimizer"]; + +const Cu = Components.utils; +const Ci = Components.interfaces; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); + +const ORIGIN_TOP_RIGHT = 1; +const ORIGIN_BOTTOM_LEFT = 2; + +this.LightweightThemeImageOptimizer = { + optimize: function LWTIO_optimize(aThemeData, aScreen) { + let data = Utils.createCopy(aThemeData); + if (!data.headerURL) { + return data; + } + + data.headerURL = ImageCropper.getCroppedImageURL( + data.headerURL, aScreen, ORIGIN_TOP_RIGHT); + + if (data.footerURL) { + data.footerURL = ImageCropper.getCroppedImageURL( + data.footerURL, aScreen, ORIGIN_BOTTOM_LEFT); + } + + return data; + }, + + purge: function LWTIO_purge() { + let dir = FileUtils.getDir("ProfD", ["lwtheme"]); + dir.followLinks = false; + try { + dir.remove(true); + } catch (e) {} + } +}; + +Object.freeze(LightweightThemeImageOptimizer); + +let ImageCropper = { + _inProgress: {}, + + getCroppedImageURL: + function ImageCropper_getCroppedImageURL(aImageURL, aScreen, aOrigin) { + // We can crop local files, only. + if (!aImageURL.startsWith("file://")) { + return aImageURL; + } + + if (Services.prefs.getBoolPref("lightweightThemes.animation.enabled")) { + //Don't crop if animated + return aImageURL; + } + + // Generate the cropped image's file name using its + // base name and the current screen size. + let uri = Services.io.newURI(aImageURL, null, null); + let file = uri.QueryInterface(Ci.nsIFileURL).file; + + // Make sure the source file exists. + if (!file.exists()) { + return aImageURL; + } + + let fileName = file.leafName + "-" + aScreen.width + "x" + aScreen.height; + let croppedFile = FileUtils.getFile("ProfD", ["lwtheme", fileName]); + + // If we have a local file that is not in progress, return it. + if (croppedFile.exists() && !(croppedFile.path in this._inProgress)) { + let fileURI = Services.io.newFileURI(croppedFile); + + // Copy the query part to avoid wrong caching. + fileURI.QueryInterface(Ci.nsIURL).query = uri.query; + return fileURI.spec; + } + + // Crop the given image in the background. + this._crop(uri, croppedFile, aScreen, aOrigin); + + // Return the original image while we're waiting for the cropped version + // to be written to disk. + return aImageURL; + }, + + _crop: function ImageCropper_crop(aURI, aTargetFile, aScreen, aOrigin) { + let inProgress = this._inProgress; + inProgress[aTargetFile.path] = true; + + function resetInProgress() { + delete inProgress[aTargetFile.path]; + } + + ImageFile.read(aURI, function crop_readImageFile(aInputStream, aContentType) { + if (aInputStream && aContentType) { + let image = ImageTools.decode(aInputStream, aContentType); + if (image && image.width && image.height) { + let stream = ImageTools.encode(image, aScreen, aOrigin, aContentType); + if (stream) { + ImageFile.write(aTargetFile, stream, resetInProgress); + return; + } + } + } + + resetInProgress(); + }); + } +}; + +let ImageFile = { + read: function ImageFile_read(aURI, aCallback) { + this._netUtil.asyncFetch2( + aURI, + function read_asyncFetch(aInputStream, aStatus, aRequest) { + if (Components.isSuccessCode(aStatus) && aRequest instanceof Ci.nsIChannel) { + let channel = aRequest.QueryInterface(Ci.nsIChannel); + aCallback(aInputStream, channel.contentType); + } else { + aCallback(); + } + }, + null, // aLoadingNode + Services.scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_NORMAL, + Ci.nsIContentPolicy.TYPE_IMAGE); + }, + + write: function ImageFile_write(aFile, aInputStream, aCallback) { + let fos = FileUtils.openSafeFileOutputStream(aFile); + this._netUtil.asyncCopy(aInputStream, fos, function write_asyncCopy(aResult) { + FileUtils.closeSafeFileOutputStream(fos); + + // Remove the file if writing was not successful. + if (!Components.isSuccessCode(aResult)) { + try { + aFile.remove(false); + } catch (e) {} + } + + aCallback(); + }); + } +}; + +XPCOMUtils.defineLazyModuleGetter(ImageFile, "_netUtil", + "resource://gre/modules/NetUtil.jsm", "NetUtil"); + +let ImageTools = { + decode: function ImageTools_decode(aInputStream, aContentType) { + let outParam = {value: null}; + + try { + this._imgTools.decodeImageData(aInputStream, aContentType, outParam); + } catch (e) {} + + return outParam.value; + }, + + encode: function ImageTools_encode(aImage, aScreen, aOrigin, aContentType) { + let stream; + let width = Math.min(aImage.width, aScreen.width); + let height = Math.min(aImage.height, aScreen.height); + let x = aOrigin == ORIGIN_TOP_RIGHT ? aImage.width - width : 0; + + try { + stream = this._imgTools.encodeCroppedImage(aImage, aContentType, x, 0, + width, height); + } catch (e) {} + + return stream; + } +}; + +XPCOMUtils.defineLazyServiceGetter(ImageTools, "_imgTools", + "@mozilla.org/image/tools;1", "imgITools"); + +let Utils = { + createCopy: function Utils_createCopy(aData) { + let copy = {}; + for (let [k, v] in Iterator(aData)) { + copy[k] = v; + } + return copy; + } +}; |