2

I am trying to write a Tampermonkey userscript that uses a <canvas> to combine pictures into a single image and then auto downloads it.

My script code:

// ==UserScript==
// @name         Picture Download
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Picture Download
// @author       Oray
// @match        https://www.example.com/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js
// @grant        unsafeWindow
// ==/UserScript==
(function(){
  function GM_wait(){
    if(typeof unsafeWindow.jQuery == 'undefined'){
      window.setTimeout(GM_wait,100);
    }else{
      unsafeWindow.jQuery(function() { letsJQuery(unsafeWindow.jQuery); });
  }}
  GM_wait();

  function letsJQuery($){
$('html[lang] > body').prepend('<canvas id="cve"></canvas>');
      var img1 = new Image();
      var img2 = new Image();
      var combined = new Image();
      var nloaded = 0;
      var combinedx;
      var filename;
      var e;
      function checkload(event){
        nloaded++;
        if (nloaded < 2){
          return;
        }
        var canvas = document.getElementById('cve');
        var context = canvas.getContext('2d');
        canvas.width = img1.width;
        canvas.height = img1.height + img2.height;
        context.drawImage(img1, 0, 0);
        context.drawImage(img2, img1.width / 2 - img2.width / 2 , img1.height);
        combinedx = canvas.toDataURL('data/gif');
        filename = 'myimage.png';
        var lnk = document.createElement('a');
        lnk.download = filename;
        lnk.href = combinedx;
          e = document.createEvent("MouseEvents");
          e.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false,false, 0, null);
          lnk.dispatchEvent(e);
      }
      img1.onload = checkload;
      img1.crossOrigin = "Anonymous";
      img1.src ="https://images-na.ssl-images-amazon.com/images/I/81pNr82OTgL._SX679_.jpg";
      img2.onload = checkload;
      img2.crossOrigin = "Anonymous";
      img2.src = "https://images-na.ssl-images-amazon.com/images/I/31W%2BDml7GsL.jpg";
  }
})();


...works in the console but doesn't as a Tampermonkey script.

Why?




Editor's note: By the original title, this would be a very common problem due to AJAX timing (example). But this question covers a different, rarer cause with the same base symptom.

Brock Adams
  • 90,639
  • 22
  • 233
  • 295

1 Answers1

3

If you check the browser console (Ctrl+Shift+J), you should see errors like:

Uncaught TypeError: Failed to execute 'initMouseEvent' on 'MouseEvent': parameter 4 is not of type 'Window'.

Or:

TypeError: Argument 4 of MouseEvent.initMouseEvent does not implement interface Window.


That's because window has the wrong context inside your userscript in this line:

e.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false,false, 0, null);

If you had used unsafeWindow it would work in the script (but not in the console).

If you use e.initMouseEvent("click", true, true); it will work in both.

BUT, initMouseEvent is deprecated, best to use the MouseEvent() constructor; see the code below.

Also:

  1. That (function(){ has been superfluous clutter for years. Userscripts are wrapped by default and fire after jQuery's ready, by default, on all major script engines.
  2. Likewise, the letsJQuery rigmarole is completely unnecessary.
  3. After calls like canvas = document.getElementById('cve');, you need to make sure the variable is defined before using it. See the code below.


So, here is the working userscript with those adjustments:

// ==UserScript==
// @name        Picture Download
// @match       *://YOUR_SERVER.COM/YOUR_PATH/*
// @require     https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js
// @grant       GM_addStyle
// @grant       GM.getValue
// ==/UserScript==
//- The @grant directives are needed to restore the proper sandbox.
/* global $ */

//$('html[lang] > body').prepend ('<canvas id="cve"></canvas>');
$('html > body').prepend ('<canvas id="cve"></canvas>');
var img1 = new Image ();
var img2 = new Image ();
var combined = new Image ();
var nloaded = 0;
var combinedx;
var filename;
var e;

img1.onload = checkload;
img1.crossOrigin = "Anonymous";
img1.src = "https://images-na.ssl-images-amazon.com/images/I/81pNr82OTgL._SX679_.jpg";
img2.onload = checkload;
img2.crossOrigin = "Anonymous";
img2.src = "https://images-na.ssl-images-amazon.com/images/I/31W%2BDml7GsL.jpg";

function checkload (event) {
    nloaded++;
    if (nloaded < 2) {
        return;
    }
    var canvas = document.getElementById ('cve');
    if (!canvas) {
        console.warn ("No canvas.");
        return;
    }
    var context = canvas.getContext ('2d');
    canvas.width = img1.width;
    canvas.height = img1.height + img2.height;
    context.drawImage (img1, 0, 0);
    context.drawImage (img2, img1.width / 2 - img2.width / 2, img1.height);
    combinedx = canvas.toDataURL ('data/gif');
    filename = 'myimage.png';
    var lnk = document.createElement ('a');
    lnk.download = filename;
    lnk.href = combinedx;

    e = new MouseEvent ("click");
    lnk.dispatchEvent (e);
}
Brock Adams
  • 90,639
  • 22
  • 233
  • 295
  • I voted for you but I'm new here my vote is not yet accepted – Gary Andrew Jan 07 '19 at 03:35
  • This was only one part of the puzzle for me. In my case it was also necessary to pass `view: unsafeWindow` in the MouseEvent's options argument (and add the `// @grant unsafeWindow` in the userscript header), because the site (YouTube Music) expected the view property to implement properties that weren't present due to the [isolation by userscript managers.](https://wiki.greasespot.net/UnsafeWindow#Description) – Sv443 Jun 06 '23 at 11:31