addon-sdk/source/lib/sdk/clipboard.js

changeset 0
6474c204b198
     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 +}

mercurial