1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/lib/sdk/clipboard.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,338 @@ 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 +module.metadata = { 1.11 + "stability": "stable", 1.12 + "engines": { 1.13 + // TODO Fennec Support 789757 1.14 + "Firefox": "*" 1.15 + } 1.16 +}; 1.17 + 1.18 +const { Cc, Ci } = require("chrome"); 1.19 +const { DataURL } = require("./url"); 1.20 +const errors = require("./deprecated/errors"); 1.21 +const apiUtils = require("./deprecated/api-utils"); 1.22 +/* 1.23 +While these data flavors resemble Internet media types, they do 1.24 +no directly map to them. 1.25 +*/ 1.26 +const kAllowableFlavors = [ 1.27 + "text/unicode", 1.28 + "text/html", 1.29 + "image/png" 1.30 + /* CURRENTLY UNSUPPORTED FLAVORS 1.31 + "text/plain", 1.32 + "image/jpg", 1.33 + "image/jpeg", 1.34 + "image/gif", 1.35 + "text/x-moz-text-internal", 1.36 + "AOLMAIL", 1.37 + "application/x-moz-file", 1.38 + "text/x-moz-url", 1.39 + "text/x-moz-url-data", 1.40 + "text/x-moz-url-desc", 1.41 + "text/x-moz-url-priv", 1.42 + "application/x-moz-nativeimage", 1.43 + "application/x-moz-nativehtml", 1.44 + "application/x-moz-file-promise-url", 1.45 + "application/x-moz-file-promise-dest-filename", 1.46 + "application/x-moz-file-promise", 1.47 + "application/x-moz-file-promise-dir" 1.48 + */ 1.49 +]; 1.50 + 1.51 +/* 1.52 +Aliases for common flavors. Not all flavors will 1.53 +get an alias. New aliases must be approved by a 1.54 +Jetpack API druid. 1.55 +*/ 1.56 +const kFlavorMap = [ 1.57 + { short: "text", long: "text/unicode" }, 1.58 + { short: "html", long: "text/html" }, 1.59 + { short: "image", long: "image/png" } 1.60 +]; 1.61 + 1.62 +let clipboardService = Cc["@mozilla.org/widget/clipboard;1"]. 1.63 + getService(Ci.nsIClipboard); 1.64 + 1.65 +let clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"]. 1.66 + getService(Ci.nsIClipboardHelper); 1.67 + 1.68 +let imageTools = Cc["@mozilla.org/image/tools;1"]. 1.69 + getService(Ci.imgITools); 1.70 + 1.71 +exports.set = function(aData, aDataType) { 1.72 + 1.73 + let options = { 1.74 + data: aData, 1.75 + datatype: aDataType || "text" 1.76 + }; 1.77 + 1.78 + // If `aDataType` is not given or if it's "image", the data is parsed as 1.79 + // data URL to detect a better datatype 1.80 + if (aData && (!aDataType || aDataType === "image")) { 1.81 + try { 1.82 + let dataURL = new DataURL(aData); 1.83 + 1.84 + options.datatype = dataURL.mimeType; 1.85 + options.data = dataURL.data; 1.86 + } 1.87 + catch (e) { 1.88 + // Ignore invalid URIs 1.89 + if (e.name !== "URIError") { 1.90 + throw e; 1.91 + } 1.92 + } 1.93 + } 1.94 + 1.95 + options = apiUtils.validateOptions(options, { 1.96 + data: { 1.97 + is: ["string"] 1.98 + }, 1.99 + datatype: { 1.100 + is: ["string"] 1.101 + } 1.102 + }); 1.103 + 1.104 + let flavor = fromJetpackFlavor(options.datatype); 1.105 + 1.106 + if (!flavor) 1.107 + throw new Error("Invalid flavor for " + options.datatype); 1.108 + 1.109 + // Additional checks for using the simple case 1.110 + if (flavor == "text/unicode") { 1.111 + clipboardHelper.copyString(options.data); 1.112 + return true; 1.113 + } 1.114 + 1.115 + // Below are the more complex cases where we actually have to work with a 1.116 + // nsITransferable object 1.117 + var xferable = Cc["@mozilla.org/widget/transferable;1"]. 1.118 + createInstance(Ci.nsITransferable); 1.119 + if (!xferable) 1.120 + throw new Error("Couldn't set the clipboard due to an internal error " + 1.121 + "(couldn't create a Transferable object)."); 1.122 + // Bug 769440: Starting with FF16, transferable have to be inited 1.123 + if ("init" in xferable) 1.124 + xferable.init(null); 1.125 + 1.126 + switch (flavor) { 1.127 + case "text/html": 1.128 + // add text/html flavor 1.129 + let (str = Cc["@mozilla.org/supports-string;1"]. 1.130 + createInstance(Ci.nsISupportsString)) 1.131 + { 1.132 + str.data = options.data; 1.133 + xferable.addDataFlavor(flavor); 1.134 + xferable.setTransferData(flavor, str, str.data.length * 2); 1.135 + } 1.136 + 1.137 + // add a text/unicode flavor (html converted to plain text) 1.138 + let (str = Cc["@mozilla.org/supports-string;1"]. 1.139 + createInstance(Ci.nsISupportsString), 1.140 + converter = Cc["@mozilla.org/feed-textconstruct;1"]. 1.141 + createInstance(Ci.nsIFeedTextConstruct)) 1.142 + { 1.143 + converter.type = "html"; 1.144 + converter.text = options.data; 1.145 + str.data = converter.plainText(); 1.146 + xferable.addDataFlavor("text/unicode"); 1.147 + xferable.setTransferData("text/unicode", str, str.data.length * 2); 1.148 + } 1.149 + break; 1.150 + 1.151 + // Set images to the clipboard is not straightforward, to have an idea how 1.152 + // it works on platform side, see: 1.153 + // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsCopySupport.cpp?rev=7857c5bff017#530 1.154 + case "image/png": 1.155 + let image = options.data; 1.156 + 1.157 + let container = {}; 1.158 + 1.159 + try { 1.160 + let input = Cc["@mozilla.org/io/string-input-stream;1"]. 1.161 + createInstance(Ci.nsIStringInputStream); 1.162 + 1.163 + input.setData(image, image.length); 1.164 + 1.165 + imageTools.decodeImageData(input, flavor, container); 1.166 + } 1.167 + catch (e) { 1.168 + throw new Error("Unable to decode data given in a valid image."); 1.169 + } 1.170 + 1.171 + // Store directly the input stream makes the cliboard's data available 1.172 + // for Firefox but not to the others application or to the OS. Therefore, 1.173 + // a `nsISupportsInterfacePointer` object that reference an `imgIContainer` 1.174 + // with the image is needed. 1.175 + var imgPtr = Cc["@mozilla.org/supports-interface-pointer;1"]. 1.176 + createInstance(Ci.nsISupportsInterfacePointer); 1.177 + 1.178 + imgPtr.data = container.value; 1.179 + 1.180 + xferable.addDataFlavor(flavor); 1.181 + xferable.setTransferData(flavor, imgPtr, -1); 1.182 + 1.183 + break; 1.184 + default: 1.185 + throw new Error("Unable to handle the flavor " + flavor + "."); 1.186 + } 1.187 + 1.188 + // TODO: Not sure if this will ever actually throw. -zpao 1.189 + try { 1.190 + clipboardService.setData( 1.191 + xferable, 1.192 + null, 1.193 + clipboardService.kGlobalClipboard 1.194 + ); 1.195 + } catch (e) { 1.196 + throw new Error("Couldn't set clipboard data due to an internal error: " + e); 1.197 + } 1.198 + return true; 1.199 +}; 1.200 + 1.201 + 1.202 +exports.get = function(aDataType) { 1.203 + let options = { 1.204 + datatype: aDataType 1.205 + }; 1.206 + 1.207 + // Figure out the best data type for the clipboard's data, if omitted 1.208 + if (!aDataType) { 1.209 + if (~currentFlavors().indexOf("image")) 1.210 + options.datatype = "image"; 1.211 + else 1.212 + options.datatype = "text"; 1.213 + } 1.214 + 1.215 + options = apiUtils.validateOptions(options, { 1.216 + datatype: { 1.217 + is: ["string"] 1.218 + } 1.219 + }); 1.220 + 1.221 + var xferable = Cc["@mozilla.org/widget/transferable;1"]. 1.222 + createInstance(Ci.nsITransferable); 1.223 + if (!xferable) 1.224 + throw new Error("Couldn't set the clipboard due to an internal error " + 1.225 + "(couldn't create a Transferable object)."); 1.226 + // Bug 769440: Starting with FF16, transferable have to be inited 1.227 + if ("init" in xferable) 1.228 + xferable.init(null); 1.229 + 1.230 + var flavor = fromJetpackFlavor(options.datatype); 1.231 + 1.232 + // Ensure that the user hasn't requested a flavor that we don't support. 1.233 + if (!flavor) 1.234 + throw new Error("Getting the clipboard with the flavor '" + flavor + 1.235 + "' is not supported."); 1.236 + 1.237 + // TODO: Check for matching flavor first? Probably not worth it. 1.238 + 1.239 + xferable.addDataFlavor(flavor); 1.240 + // Get the data into our transferable. 1.241 + clipboardService.getData( 1.242 + xferable, 1.243 + clipboardService.kGlobalClipboard 1.244 + ); 1.245 + 1.246 + var data = {}; 1.247 + var dataLen = {}; 1.248 + try { 1.249 + xferable.getTransferData(flavor, data, dataLen); 1.250 + } catch (e) { 1.251 + // Clipboard doesn't contain data in flavor, return null. 1.252 + return null; 1.253 + } 1.254 + 1.255 + // There's no data available, return. 1.256 + if (data.value === null) 1.257 + return null; 1.258 + 1.259 + // TODO: Add flavors here as we support more in kAllowableFlavors. 1.260 + switch (flavor) { 1.261 + case "text/unicode": 1.262 + case "text/html": 1.263 + data = data.value.QueryInterface(Ci.nsISupportsString).data; 1.264 + break; 1.265 + case "image/png": 1.266 + let dataURL = new DataURL(); 1.267 + 1.268 + dataURL.mimeType = flavor; 1.269 + dataURL.base64 = true; 1.270 + 1.271 + let image = data.value; 1.272 + 1.273 + // Due to the differences in how images could be stored in the clipboard 1.274 + // the checks below are needed. The clipboard could already provide the 1.275 + // image as byte streams, but also as pointer, or as image container. 1.276 + // If it's not possible obtain a byte stream, the function returns `null`. 1.277 + if (image instanceof Ci.nsISupportsInterfacePointer) 1.278 + image = image.data; 1.279 + 1.280 + if (image instanceof Ci.imgIContainer) 1.281 + image = imageTools.encodeImage(image, flavor); 1.282 + 1.283 + if (image instanceof Ci.nsIInputStream) { 1.284 + let binaryStream = Cc["@mozilla.org/binaryinputstream;1"]. 1.285 + createInstance(Ci.nsIBinaryInputStream); 1.286 + 1.287 + binaryStream.setInputStream(image); 1.288 + 1.289 + dataURL.data = binaryStream.readBytes(binaryStream.available()); 1.290 + 1.291 + data = dataURL.toString(); 1.292 + } 1.293 + else 1.294 + data = null; 1.295 + 1.296 + break; 1.297 + default: 1.298 + data = null; 1.299 + } 1.300 + 1.301 + return data; 1.302 +}; 1.303 + 1.304 +function currentFlavors() { 1.305 + // Loop over kAllowableFlavors, calling hasDataMatchingFlavors for each. 1.306 + // This doesn't seem like the most efficient way, but we can't get 1.307 + // confirmation for specific flavors any other way. This is supposed to be 1.308 + // an inexpensive call, so performance shouldn't be impacted (much). 1.309 + var currentFlavors = []; 1.310 + for each (var flavor in kAllowableFlavors) { 1.311 + var matches = clipboardService.hasDataMatchingFlavors( 1.312 + [flavor], 1.313 + 1, 1.314 + clipboardService.kGlobalClipboard 1.315 + ); 1.316 + if (matches) 1.317 + currentFlavors.push(toJetpackFlavor(flavor)); 1.318 + } 1.319 + return currentFlavors; 1.320 +}; 1.321 + 1.322 +Object.defineProperty(exports, "currentFlavors", { get : currentFlavors }); 1.323 + 1.324 +// SUPPORT FUNCTIONS //////////////////////////////////////////////////////// 1.325 + 1.326 +function toJetpackFlavor(aFlavor) { 1.327 + for each (let flavorMap in kFlavorMap) 1.328 + if (flavorMap.long == aFlavor) 1.329 + return flavorMap.short; 1.330 + // Return null in the case where we don't match 1.331 + return null; 1.332 +} 1.333 + 1.334 +function fromJetpackFlavor(aJetpackFlavor) { 1.335 + // TODO: Handle proper flavors better 1.336 + for each (let flavorMap in kFlavorMap) 1.337 + if (flavorMap.short == aJetpackFlavor || flavorMap.long == aJetpackFlavor) 1.338 + return flavorMap.long; 1.339 + // Return null in the case where we don't match. 1.340 + return null; 1.341 +}