123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- /* 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";
- module.metadata = {
- "stability": "stable",
- "engines": {
- // TODO Fennec Support 789757
- "Firefox": "*"
- }
- };
- const { Cc, Ci } = require("chrome");
- const { DataURL } = require("./url");
- const errors = require("./deprecated/errors");
- const apiUtils = require("./deprecated/api-utils");
- /*
- While these data flavors resemble Internet media types, they do
- no directly map to them.
- */
- const kAllowableFlavors = [
- "text/unicode",
- "text/html",
- "image/png"
- /* CURRENTLY UNSUPPORTED FLAVORS
- "text/plain",
- "image/jpg",
- "image/jpeg",
- "image/gif",
- "text/x-moz-text-internal",
- "AOLMAIL",
- "application/x-moz-file",
- "text/x-moz-url",
- "text/x-moz-url-data",
- "text/x-moz-url-desc",
- "text/x-moz-url-priv",
- "application/x-moz-nativeimage",
- "application/x-moz-nativehtml",
- "application/x-moz-file-promise-url",
- "application/x-moz-file-promise-dest-filename",
- "application/x-moz-file-promise",
- "application/x-moz-file-promise-dir"
- */
- ];
- /*
- Aliases for common flavors. Not all flavors will
- get an alias. New aliases must be approved by a
- Jetpack API druid.
- */
- const kFlavorMap = [
- { short: "text", long: "text/unicode" },
- { short: "html", long: "text/html" },
- { short: "image", long: "image/png" }
- ];
- let clipboardService = Cc["@mozilla.org/widget/clipboard;1"].
- getService(Ci.nsIClipboard);
- let clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].
- getService(Ci.nsIClipboardHelper);
- let imageTools = Cc["@mozilla.org/image/tools;1"].
- getService(Ci.imgITools);
- exports.set = function(aData, aDataType) {
- let options = {
- data: aData,
- datatype: aDataType || "text"
- };
- // If `aDataType` is not given or if it's "image", the data is parsed as
- // data URL to detect a better datatype
- if (aData && (!aDataType || aDataType === "image")) {
- try {
- let dataURL = new DataURL(aData);
- options.datatype = dataURL.mimeType;
- options.data = dataURL.data;
- }
- catch (e if e.name === "URIError") {
- // Not a valid Data URL
- }
- }
- options = apiUtils.validateOptions(options, {
- data: {
- is: ["string"]
- },
- datatype: {
- is: ["string"]
- }
- });
- let flavor = fromJetpackFlavor(options.datatype);
- if (!flavor)
- throw new Error("Invalid flavor for " + options.datatype);
- // Additional checks for using the simple case
- if (flavor == "text/unicode") {
- clipboardHelper.copyString(options.data);
- return true;
- }
- // Below are the more complex cases where we actually have to work with a
- // nsITransferable object
- var xferable = Cc["@mozilla.org/widget/transferable;1"].
- createInstance(Ci.nsITransferable);
- if (!xferable)
- throw new Error("Couldn't set the clipboard due to an internal error " +
- "(couldn't create a Transferable object).");
- // Bug 769440: Starting with FF16, transferable have to be inited
- if ("init" in xferable)
- xferable.init(null);
- switch (flavor) {
- case "text/html":
- // add text/html flavor
- let (str = Cc["@mozilla.org/supports-string;1"].
- createInstance(Ci.nsISupportsString))
- {
- str.data = options.data;
- xferable.addDataFlavor(flavor);
- xferable.setTransferData(flavor, str, str.data.length * 2);
- }
- // add a text/unicode flavor (html converted to plain text)
- let (str = Cc["@mozilla.org/supports-string;1"].
- createInstance(Ci.nsISupportsString),
- converter = Cc["@mozilla.org/feed-textconstruct;1"].
- createInstance(Ci.nsIFeedTextConstruct))
- {
- converter.type = "html";
- converter.text = options.data;
- str.data = converter.plainText();
- xferable.addDataFlavor("text/unicode");
- xferable.setTransferData("text/unicode", str, str.data.length * 2);
- }
- break;
- // Set images to the clipboard is not straightforward, to have an idea how
- // it works on platform side, see:
- // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsCopySupport.cpp?rev=7857c5bff017#530
- case "image/png":
- let image = options.data;
- let container = {};
- try {
- let input = Cc["@mozilla.org/io/string-input-stream;1"].
- createInstance(Ci.nsIStringInputStream);
- input.setData(image, image.length);
- imageTools.decodeImageData(input, flavor, container);
- }
- catch (e) {
- throw new Error("Unable to decode data given in a valid image.");
- }
- // Store directly the input stream makes the cliboard's data available
- // for Firefox but not to the others application or to the OS. Therefore,
- // a `nsISupportsInterfacePointer` object that reference an `imgIContainer`
- // with the image is needed.
- var imgPtr = Cc["@mozilla.org/supports-interface-pointer;1"].
- createInstance(Ci.nsISupportsInterfacePointer);
- imgPtr.data = container.value;
- xferable.addDataFlavor(flavor);
- xferable.setTransferData(flavor, imgPtr, -1);
- break;
- default:
- throw new Error("Unable to handle the flavor " + flavor + ".");
- }
- // TODO: Not sure if this will ever actually throw. -zpao
- try {
- clipboardService.setData(
- xferable,
- null,
- clipboardService.kGlobalClipboard
- );
- } catch (e) {
- throw new Error("Couldn't set clipboard data due to an internal error: " + e);
- }
- return true;
- };
- exports.get = function(aDataType) {
- let options = {
- datatype: aDataType
- };
- // Figure out the best data type for the clipboard's data, if omitted
- if (!aDataType) {
- if (~currentFlavors().indexOf("image"))
- options.datatype = "image";
- else
- options.datatype = "text";
- }
- options = apiUtils.validateOptions(options, {
- datatype: {
- is: ["string"]
- }
- });
- var xferable = Cc["@mozilla.org/widget/transferable;1"].
- createInstance(Ci.nsITransferable);
- if (!xferable)
- throw new Error("Couldn't set the clipboard due to an internal error " +
- "(couldn't create a Transferable object).");
- // Bug 769440: Starting with FF16, transferable have to be inited
- if ("init" in xferable)
- xferable.init(null);
- var flavor = fromJetpackFlavor(options.datatype);
- // Ensure that the user hasn't requested a flavor that we don't support.
- if (!flavor)
- throw new Error("Getting the clipboard with the flavor '" + flavor +
- "' is not supported.");
- // TODO: Check for matching flavor first? Probably not worth it.
- xferable.addDataFlavor(flavor);
- // Get the data into our transferable.
- clipboardService.getData(
- xferable,
- clipboardService.kGlobalClipboard
- );
- var data = {};
- var dataLen = {};
- try {
- xferable.getTransferData(flavor, data, dataLen);
- } catch (e) {
- // Clipboard doesn't contain data in flavor, return null.
- return null;
- }
- // There's no data available, return.
- if (data.value === null)
- return null;
- // TODO: Add flavors here as we support more in kAllowableFlavors.
- switch (flavor) {
- case "text/unicode":
- case "text/html":
- data = data.value.QueryInterface(Ci.nsISupportsString).data;
- break;
- case "image/png":
- let dataURL = new DataURL();
- dataURL.mimeType = flavor;
- dataURL.base64 = true;
- let image = data.value;
- // Due to the differences in how images could be stored in the clipboard
- // the checks below are needed. The clipboard could already provide the
- // image as byte streams, but also as pointer, or as image container.
- // If it's not possible obtain a byte stream, the function returns `null`.
- if (image instanceof Ci.nsISupportsInterfacePointer)
- image = image.data;
- if (image instanceof Ci.imgIContainer)
- image = imageTools.encodeImage(image, flavor);
- if (image instanceof Ci.nsIInputStream) {
- let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
- createInstance(Ci.nsIBinaryInputStream);
- binaryStream.setInputStream(image);
- dataURL.data = binaryStream.readBytes(binaryStream.available());
- data = dataURL.toString();
- }
- else
- data = null;
- break;
- default:
- data = null;
- }
- return data;
- };
- function currentFlavors() {
- // Loop over kAllowableFlavors, calling hasDataMatchingFlavors for each.
- // This doesn't seem like the most efficient way, but we can't get
- // confirmation for specific flavors any other way. This is supposed to be
- // an inexpensive call, so performance shouldn't be impacted (much).
- var currentFlavors = [];
- for each (var flavor in kAllowableFlavors) {
- var matches = clipboardService.hasDataMatchingFlavors(
- [flavor],
- 1,
- clipboardService.kGlobalClipboard
- );
- if (matches)
- currentFlavors.push(toJetpackFlavor(flavor));
- }
- return currentFlavors;
- };
- Object.defineProperty(exports, "currentFlavors", { get : currentFlavors });
- // SUPPORT FUNCTIONS ////////////////////////////////////////////////////////
- function toJetpackFlavor(aFlavor) {
- for each (let flavorMap in kFlavorMap)
- if (flavorMap.long == aFlavor)
- return flavorMap.short;
- // Return null in the case where we don't match
- return null;
- }
- function fromJetpackFlavor(aJetpackFlavor) {
- // TODO: Handle proper flavors better
- for each (let flavorMap in kFlavorMap)
- if (flavorMap.short == aJetpackFlavor || flavorMap.long == aJetpackFlavor)
- return flavorMap.long;
- // Return null in the case where we don't match.
- return null;
- }
|