How does image pasting in imgur work in Firefox?
Asked Answered
F

1

13
  1. Open an image editing program, copy an image (don't copy from a browser - I'll explain why later).
  2. Open Firefox and go to http://imgur.com
  3. Press Ctrl+V
  4. Look with utter amazement at the image you copied before being uploaded.

I know about the HTML5 Clipboard API, that works great with Chrome. In Chrome, when you paste binary image data, the browser fires a paste event containing event.clipboardData.types which is equal to ['Files'], I can therefore get my image in the clipboard with

var index = event.clipboardData.types.indexOf('Files');
if(index !== -1) {
    var blob = event.clipboardData.items[index].getAsFile();
}

In Firefox, when I paste binary image data, the browser also fires a paste event, but event.clipboardData.types is empty (has length === 0) and event.clipboardData.getData('Files') returns obviously "".

By the way, copying an image from a browser also sets a "text/html" item in clipboardData that holds the copied <img> element. So in those cases I could work around the problem by sending the remote url to the server, which would then download the image itself (if the server has access to the remote location, which isn't really guaranteed).

StackOverflow suggests this: How to obtain data from clipboard in Firefox (create a contenteditable <div>, ask the user to paste into that and then copy the contents.)

However, imgur does not do that. How does that work?

Flagellate answered 27/1, 2014 at 10:0 Comment(0)
D
18

Disclaimer: I didn't know the imgur upload system before posting, but I hope it'll help you. This post is written just by looking into the code and with some HTML/JS knowledge :)

imgur seems to use a different paste option for Firefox (and Gecko browsers in general). Indeed, there's a div with the upload-global-FF-paste-box ID in the index HTML, with the attribute contenteditable="true". In the main js, we can found the initialization of a property $FFPasteBox:

init: function (a) {
    this._ = $.extend({
        el: {
            $computerButton: $("#gallery-upload-buttons #file-wrapper"),
            $webButton: $("#gallery-upload-buttons #url"),
            $computerButtonGlobal: $("#upload-global-file"),
            $FFPasteBox: $("#upload-global-FF-paste-box")  // <--- this line
        }
    }, a)
},

Then, by digging a little in the global.js file, I found these functions (see above). Basically, the process is:

  1. Wait the user to make the Ctrl-V action
  2. When it's fired, make the contenteditable div seen before in focus, to retrieve data
  3. Wait data are completely sent (it can be long with high-res photo)
  4. Analyze the base64 string obtained.

Here's the code extracted from the global.js and commented by myself:

// When a paste action is trigger, set the focus on this div and wait the data to be sent
initPasteUploadMozilla: function () {
    $(document).on("paste", _.bind(function (a) {
        $(a.target).is("input") || this.isInView() && (Imgur.Upload.Index && this.showColorBox(), this._.el.$FFPasteBox.focus(), this.waitForPasteData(this._.el.$FFPasteBox[0]))
    }, this))
},

// Listen the user, and waiting that the node is created by the paste action
waitForPasteData: function (a) {
    a.childNodes && a.childNodes.length > 0 ? this.processPaste(a) : setTimeout(this.waitForPasteData.bind(this, a), 20)
},

// Check data sent
processPaste: function (a) {
    var b = a.innerHTML,
        // Check if the thing pasted is a <img tag or a png or at least base64 stuff
        c = {
            image: -1 != b.indexOf("<img") && -1 != b.indexOf("src="),
            base64: -1 != b.indexOf("base64,"),
            png: -1 != b.indexOf("iVBORw0K")
        };
    if (c.image && c.base64 && c.png) {
        var d = b.split('<img src="');
        d = d[1].split('" alt="">');
        var b = {
            dataURL: d[0],
            file: {
                size: this.bytesizeBase64String(d[0].length)
            }
        };
        this.addBase64(b)
    } else {
        var e = $(a).find("img"),
            f = null;
        if (e.length > 0 && e.attr("src").length > 0 ? f = e.attr("src") : b.length > 0 && (f = b), null !== f && f.match(this._.urlRegex)) this.queueUrl(f);
        else if (null !== f) {
            var g = $(f).filter("a");
            g.length && g.attr("href").match(this._.urlRegex) ? this.queueUrl(g.attr("href")) : this.showError(this._.messages.urlInvalidError)
        }
    }
    a.innerHTML = ""
},
Dichroite answered 27/1, 2014 at 10:53 Comment(1)
oh, so in the end it IS pasting the content into a contenteditable div. Thank you :)Flagellate

© 2022 - 2024 — McMap. All rights reserved.