1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/mozapps/extensions/internal/LightweightThemeImageOptimizer.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,186 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +this.EXPORTED_SYMBOLS = ["LightweightThemeImageOptimizer"]; 1.11 + 1.12 +const Cu = Components.utils; 1.13 +const Ci = Components.interfaces; 1.14 + 1.15 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.16 + 1.17 +XPCOMUtils.defineLazyModuleGetter(this, "Services", 1.18 + "resource://gre/modules/Services.jsm"); 1.19 + 1.20 +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", 1.21 + "resource://gre/modules/FileUtils.jsm"); 1.22 + 1.23 +const ORIGIN_TOP_RIGHT = 1; 1.24 +const ORIGIN_BOTTOM_LEFT = 2; 1.25 + 1.26 +this.LightweightThemeImageOptimizer = { 1.27 + optimize: function LWTIO_optimize(aThemeData, aScreen) { 1.28 + let data = Utils.createCopy(aThemeData); 1.29 + if (!data.headerURL) { 1.30 + return data; 1.31 + } 1.32 + 1.33 + data.headerURL = ImageCropper.getCroppedImageURL( 1.34 + data.headerURL, aScreen, ORIGIN_TOP_RIGHT); 1.35 + 1.36 + if (data.footerURL) { 1.37 + data.footerURL = ImageCropper.getCroppedImageURL( 1.38 + data.footerURL, aScreen, ORIGIN_BOTTOM_LEFT); 1.39 + } 1.40 + 1.41 + return data; 1.42 + }, 1.43 + 1.44 + purge: function LWTIO_purge() { 1.45 + let dir = FileUtils.getDir("ProfD", ["lwtheme"]); 1.46 + dir.followLinks = false; 1.47 + try { 1.48 + dir.remove(true); 1.49 + } catch (e) {} 1.50 + } 1.51 +}; 1.52 + 1.53 +Object.freeze(LightweightThemeImageOptimizer); 1.54 + 1.55 +let ImageCropper = { 1.56 + _inProgress: {}, 1.57 + 1.58 + getCroppedImageURL: 1.59 + function ImageCropper_getCroppedImageURL(aImageURL, aScreen, aOrigin) { 1.60 + // We can crop local files, only. 1.61 + if (!aImageURL.startsWith("file://")) { 1.62 + return aImageURL; 1.63 + } 1.64 + 1.65 + // Generate the cropped image's file name using its 1.66 + // base name and the current screen size. 1.67 + let uri = Services.io.newURI(aImageURL, null, null); 1.68 + let file = uri.QueryInterface(Ci.nsIFileURL).file; 1.69 + 1.70 + // Make sure the source file exists. 1.71 + if (!file.exists()) { 1.72 + return aImageURL; 1.73 + } 1.74 + 1.75 + let fileName = file.leafName + "-" + aScreen.width + "x" + aScreen.height; 1.76 + let croppedFile = FileUtils.getFile("ProfD", ["lwtheme", fileName]); 1.77 + 1.78 + // If we have a local file that is not in progress, return it. 1.79 + if (croppedFile.exists() && !(croppedFile.path in this._inProgress)) { 1.80 + let fileURI = Services.io.newFileURI(croppedFile); 1.81 + 1.82 + // Copy the query part to avoid wrong caching. 1.83 + fileURI.QueryInterface(Ci.nsIURL).query = uri.query; 1.84 + return fileURI.spec; 1.85 + } 1.86 + 1.87 + // Crop the given image in the background. 1.88 + this._crop(uri, croppedFile, aScreen, aOrigin); 1.89 + 1.90 + // Return the original image while we're waiting for the cropped version 1.91 + // to be written to disk. 1.92 + return aImageURL; 1.93 + }, 1.94 + 1.95 + _crop: function ImageCropper_crop(aURI, aTargetFile, aScreen, aOrigin) { 1.96 + let inProgress = this._inProgress; 1.97 + inProgress[aTargetFile.path] = true; 1.98 + 1.99 + function resetInProgress() { 1.100 + delete inProgress[aTargetFile.path]; 1.101 + } 1.102 + 1.103 + ImageFile.read(aURI, function crop_readImageFile(aInputStream, aContentType) { 1.104 + if (aInputStream && aContentType) { 1.105 + let image = ImageTools.decode(aInputStream, aContentType); 1.106 + if (image && image.width && image.height) { 1.107 + let stream = ImageTools.encode(image, aScreen, aOrigin, aContentType); 1.108 + if (stream) { 1.109 + ImageFile.write(aTargetFile, stream, resetInProgress); 1.110 + return; 1.111 + } 1.112 + } 1.113 + } 1.114 + 1.115 + resetInProgress(); 1.116 + }); 1.117 + } 1.118 +}; 1.119 + 1.120 +let ImageFile = { 1.121 + read: function ImageFile_read(aURI, aCallback) { 1.122 + this._netUtil.asyncFetch(aURI, function read_asyncFetch(aInputStream, aStatus, aRequest) { 1.123 + if (Components.isSuccessCode(aStatus) && aRequest instanceof Ci.nsIChannel) { 1.124 + let channel = aRequest.QueryInterface(Ci.nsIChannel); 1.125 + aCallback(aInputStream, channel.contentType); 1.126 + } else { 1.127 + aCallback(); 1.128 + } 1.129 + }); 1.130 + }, 1.131 + 1.132 + write: function ImageFile_write(aFile, aInputStream, aCallback) { 1.133 + let fos = FileUtils.openSafeFileOutputStream(aFile); 1.134 + this._netUtil.asyncCopy(aInputStream, fos, function write_asyncCopy(aResult) { 1.135 + FileUtils.closeSafeFileOutputStream(fos); 1.136 + 1.137 + // Remove the file if writing was not successful. 1.138 + if (!Components.isSuccessCode(aResult)) { 1.139 + try { 1.140 + aFile.remove(false); 1.141 + } catch (e) {} 1.142 + } 1.143 + 1.144 + aCallback(); 1.145 + }); 1.146 + } 1.147 +}; 1.148 + 1.149 +XPCOMUtils.defineLazyModuleGetter(ImageFile, "_netUtil", 1.150 + "resource://gre/modules/NetUtil.jsm", "NetUtil"); 1.151 + 1.152 +let ImageTools = { 1.153 + decode: function ImageTools_decode(aInputStream, aContentType) { 1.154 + let outParam = {value: null}; 1.155 + 1.156 + try { 1.157 + this._imgTools.decodeImageData(aInputStream, aContentType, outParam); 1.158 + } catch (e) {} 1.159 + 1.160 + return outParam.value; 1.161 + }, 1.162 + 1.163 + encode: function ImageTools_encode(aImage, aScreen, aOrigin, aContentType) { 1.164 + let stream; 1.165 + let width = Math.min(aImage.width, aScreen.width); 1.166 + let height = Math.min(aImage.height, aScreen.height); 1.167 + let x = aOrigin == ORIGIN_TOP_RIGHT ? aImage.width - width : 0; 1.168 + 1.169 + try { 1.170 + stream = this._imgTools.encodeCroppedImage(aImage, aContentType, x, 0, 1.171 + width, height); 1.172 + } catch (e) {} 1.173 + 1.174 + return stream; 1.175 + } 1.176 +}; 1.177 + 1.178 +XPCOMUtils.defineLazyServiceGetter(ImageTools, "_imgTools", 1.179 + "@mozilla.org/image/tools;1", "imgITools"); 1.180 + 1.181 +let Utils = { 1.182 + createCopy: function Utils_createCopy(aData) { 1.183 + let copy = {}; 1.184 + for (let [k, v] in Iterator(aData)) { 1.185 + copy[k] = v; 1.186 + } 1.187 + return copy; 1.188 + } 1.189 +};