/* -*- 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);
        },
      },
    };
  }
};
