michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: this.EXPORTED_SYMBOLS = ["LightweightThemeImageOptimizer"]; michael@0: michael@0: const Cu = Components.utils; michael@0: const Ci = Components.interfaces; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Services", michael@0: "resource://gre/modules/Services.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", michael@0: "resource://gre/modules/FileUtils.jsm"); michael@0: michael@0: const ORIGIN_TOP_RIGHT = 1; michael@0: const ORIGIN_BOTTOM_LEFT = 2; michael@0: michael@0: this.LightweightThemeImageOptimizer = { michael@0: optimize: function LWTIO_optimize(aThemeData, aScreen) { michael@0: let data = Utils.createCopy(aThemeData); michael@0: if (!data.headerURL) { michael@0: return data; michael@0: } michael@0: michael@0: data.headerURL = ImageCropper.getCroppedImageURL( michael@0: data.headerURL, aScreen, ORIGIN_TOP_RIGHT); michael@0: michael@0: if (data.footerURL) { michael@0: data.footerURL = ImageCropper.getCroppedImageURL( michael@0: data.footerURL, aScreen, ORIGIN_BOTTOM_LEFT); michael@0: } michael@0: michael@0: return data; michael@0: }, michael@0: michael@0: purge: function LWTIO_purge() { michael@0: let dir = FileUtils.getDir("ProfD", ["lwtheme"]); michael@0: dir.followLinks = false; michael@0: try { michael@0: dir.remove(true); michael@0: } catch (e) {} michael@0: } michael@0: }; michael@0: michael@0: Object.freeze(LightweightThemeImageOptimizer); michael@0: michael@0: let ImageCropper = { michael@0: _inProgress: {}, michael@0: michael@0: getCroppedImageURL: michael@0: function ImageCropper_getCroppedImageURL(aImageURL, aScreen, aOrigin) { michael@0: // We can crop local files, only. michael@0: if (!aImageURL.startsWith("file://")) { michael@0: return aImageURL; michael@0: } michael@0: michael@0: // Generate the cropped image's file name using its michael@0: // base name and the current screen size. michael@0: let uri = Services.io.newURI(aImageURL, null, null); michael@0: let file = uri.QueryInterface(Ci.nsIFileURL).file; michael@0: michael@0: // Make sure the source file exists. michael@0: if (!file.exists()) { michael@0: return aImageURL; michael@0: } michael@0: michael@0: let fileName = file.leafName + "-" + aScreen.width + "x" + aScreen.height; michael@0: let croppedFile = FileUtils.getFile("ProfD", ["lwtheme", fileName]); michael@0: michael@0: // If we have a local file that is not in progress, return it. michael@0: if (croppedFile.exists() && !(croppedFile.path in this._inProgress)) { michael@0: let fileURI = Services.io.newFileURI(croppedFile); michael@0: michael@0: // Copy the query part to avoid wrong caching. michael@0: fileURI.QueryInterface(Ci.nsIURL).query = uri.query; michael@0: return fileURI.spec; michael@0: } michael@0: michael@0: // Crop the given image in the background. michael@0: this._crop(uri, croppedFile, aScreen, aOrigin); michael@0: michael@0: // Return the original image while we're waiting for the cropped version michael@0: // to be written to disk. michael@0: return aImageURL; michael@0: }, michael@0: michael@0: _crop: function ImageCropper_crop(aURI, aTargetFile, aScreen, aOrigin) { michael@0: let inProgress = this._inProgress; michael@0: inProgress[aTargetFile.path] = true; michael@0: michael@0: function resetInProgress() { michael@0: delete inProgress[aTargetFile.path]; michael@0: } michael@0: michael@0: ImageFile.read(aURI, function crop_readImageFile(aInputStream, aContentType) { michael@0: if (aInputStream && aContentType) { michael@0: let image = ImageTools.decode(aInputStream, aContentType); michael@0: if (image && image.width && image.height) { michael@0: let stream = ImageTools.encode(image, aScreen, aOrigin, aContentType); michael@0: if (stream) { michael@0: ImageFile.write(aTargetFile, stream, resetInProgress); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: resetInProgress(); michael@0: }); michael@0: } michael@0: }; michael@0: michael@0: let ImageFile = { michael@0: read: function ImageFile_read(aURI, aCallback) { michael@0: this._netUtil.asyncFetch(aURI, function read_asyncFetch(aInputStream, aStatus, aRequest) { michael@0: if (Components.isSuccessCode(aStatus) && aRequest instanceof Ci.nsIChannel) { michael@0: let channel = aRequest.QueryInterface(Ci.nsIChannel); michael@0: aCallback(aInputStream, channel.contentType); michael@0: } else { michael@0: aCallback(); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: write: function ImageFile_write(aFile, aInputStream, aCallback) { michael@0: let fos = FileUtils.openSafeFileOutputStream(aFile); michael@0: this._netUtil.asyncCopy(aInputStream, fos, function write_asyncCopy(aResult) { michael@0: FileUtils.closeSafeFileOutputStream(fos); michael@0: michael@0: // Remove the file if writing was not successful. michael@0: if (!Components.isSuccessCode(aResult)) { michael@0: try { michael@0: aFile.remove(false); michael@0: } catch (e) {} michael@0: } michael@0: michael@0: aCallback(); michael@0: }); michael@0: } michael@0: }; michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(ImageFile, "_netUtil", michael@0: "resource://gre/modules/NetUtil.jsm", "NetUtil"); michael@0: michael@0: let ImageTools = { michael@0: decode: function ImageTools_decode(aInputStream, aContentType) { michael@0: let outParam = {value: null}; michael@0: michael@0: try { michael@0: this._imgTools.decodeImageData(aInputStream, aContentType, outParam); michael@0: } catch (e) {} michael@0: michael@0: return outParam.value; michael@0: }, michael@0: michael@0: encode: function ImageTools_encode(aImage, aScreen, aOrigin, aContentType) { michael@0: let stream; michael@0: let width = Math.min(aImage.width, aScreen.width); michael@0: let height = Math.min(aImage.height, aScreen.height); michael@0: let x = aOrigin == ORIGIN_TOP_RIGHT ? aImage.width - width : 0; michael@0: michael@0: try { michael@0: stream = this._imgTools.encodeCroppedImage(aImage, aContentType, x, 0, michael@0: width, height); michael@0: } catch (e) {} michael@0: michael@0: return stream; michael@0: } michael@0: }; michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(ImageTools, "_imgTools", michael@0: "@mozilla.org/image/tools;1", "imgITools"); michael@0: michael@0: let Utils = { michael@0: createCopy: function Utils_createCopy(aData) { michael@0: let copy = {}; michael@0: for (let [k, v] in Iterator(aData)) { michael@0: copy[k] = v; michael@0: } michael@0: return copy; michael@0: } michael@0: };