gecko-dev/toolkit/components/extensions/ext-clipboard.js
Rob Wu 297c108fec Bug 1356543 - Add clipboard.setImageData API r=mixedpuppy
This introduces an implementation of the clipboard.setImageData API.
I did not find any complete documentation about how copying and
pasting images is supposed to work in Firefox, so I added many lines
of documentation based on experimenting and reading the source code.

The implementation is very similar to the Add-on SDK's implementation,
save for one difference: The third parameter to setTransferData is 0
instead of -1. Its significance is elaborated in ext-clipboard.js.

The newly added tests serve the following purposes:
- Verification that clipboard.setImageData is working as expected.
  There is no way to test that pasting in an external application
  really works, so we just check whether Firefox recognizes the
  special image data by pasting in a contentEditable area.

- Test coverage for reading clipboard data via the "paste" event and
  using event.clipboardData to access the pasted data, because this is
  the only way to read non-text data in a WebExtension extension.

MozReview-Commit-ID: Ldrx7LCIta2

--HG--
extra : rebase_source : f76fe85e5c9a525c159255c29698f4bdbdede8bc
2017-09-04 21:43:06 +02:00

85 lines
4.0 KiB
JavaScript

/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
XPCOMUtils.defineLazyServiceGetter(this, "imgTools",
"@mozilla.org/image/tools;1", "imgITools");
const ArrayBufferInputStream = Components.Constructor(
"@mozilla.org/io/arraybuffer-input-stream;1", "nsIArrayBufferInputStream");
const SupportsInterfacePointer = Components.Constructor(
"@mozilla.org/supports-interface-pointer;1", "nsISupportsInterfacePointer");
const Transferable = Components.Constructor(
"@mozilla.org/widget/transferable;1", "nsITransferable");
this.clipboard = class extends ExtensionAPI {
getAPI(context) {
return {
clipboard: {
async setImageData(imageData, imageType) {
if (AppConstants.platform == "android") {
return Promise.reject({message: "Writing images to the clipboard is not supported on Android"});
}
let mimeType = `image/${imageType}`;
let input = new ArrayBufferInputStream();
input.setData(imageData, 0, imageData.byteLength);
let container;
try {
container = imgTools.decodeImage(input, mimeType);
} catch (e) {
return Promise.reject({message: `Data is not a valid ${imageType} image`});
}
// Other applications can only access the copied image once the data
// is exported via the platform-specific clipboard APIs:
// nsClipboard::SelectionGetEvent (widget/gtk/nsClipboard.cpp)
// nsClipboard::PasteDictFromTransferable (widget/cocoa/nsClipboard.mm)
// nsDataObj::GetDib (widget/windows/nsDataObj.cpp)
//
// The common protocol for exporting a nsITransferable as an image is:
// - Use nsITransferable::GetTransferData to fetch the stored data.
// - QI a nsISupportsInterfacePointer and get the underlying pointer.
// - QI imgIContainer on the pointer.
// - Convert the image to the native clipboard format.
//
// Below we create a nsITransferable in the above format.
let imgPtr = new SupportsInterfacePointer();
imgPtr.data = container;
let transferable = new Transferable();
transferable.init(null);
transferable.addDataFlavor(mimeType);
// Internal consumers expect the image data to be stored as a
// nsIInputStream. On Linux and Windows, pasted data is directly
// retrieved from the system's native clipboard, and made available
// as a nsIInputStream.
//
// On macOS, nsClipboard::GetNativeClipboardData (nsClipboard.mm) uses
// a cached copy of nsITransferable if available, e.g. when the copy
// was initiated by the same browser instance. Consequently, the
// transferable still holds a nsISupportsInterfacePointer pointer
// instead of a nsIInputStream, and logic that assumes the data to be
// a nsIInputStream instance fails.
// For example HTMLEditor::InsertObject (HTMLEditorDataTransfer.cpp)
// and DataTransferItem::FillInExternalData (DataTransferItem.cpp).
//
// As a work-around, we force nsClipboard::GetNativeClipboardData to
// ignore the cached image data, by passing zero as the length
// parameter to transferable.setTransferData. When the length is zero,
// nsITransferable::GetTransferData will return NS_ERROR_FAILURE and
// conveniently nsClipboard::GetNativeClipboardData will then fall
// back to retrieving the data directly from the system's clipboard.
//
// Note that the length itself is not really used if the data is not
// a string type, so the actual value does not matter.
transferable.setTransferData(mimeType, imgPtr, 0);
Services.clipboard.setData(
transferable, null, Services.clipboard.kGlobalClipboard);
},
},
};
}
};