1

I appreciate if anyone can tell me how to intercept a function call in javascript. I know it is possible with making use of proxies.

for example I tried the code below to intercept it but now I want to intercept toDataURL(). in order to call toDataURL you need to create a canvas element first.So, now I want to know how is this possible to define a proxy to intercept toDataURL().

Example code to intercept it :

window.x = 0;
    let calls = (function(){
        let canvas = document.createElement('canvas');
        let fun = canvas.toDataURL;
        canvas.toDataURL = function(){       
            window.x++;         
            return fun.apply(document, arguments);
        }
        return ()=>calls;
    })();
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Bathooman
  • 103
  • 2
  • 13
  • To me it looks like You already did modify `canvas.toDataURL` and not, as you say, `document.createElement`. I just wonder what `return ()=>calls;` is for, inside the IIFE. Besides that this approach also is not a generic one; it modifies the method of exactly the created element. One might try modifying `HTMLCanvasElement.prototype.toDataURL` instead. – Peter Seliger Mar 06 '18 at 09:39
  • Hi, I tried the code above but it did not work. could you please give an example? – Bathooman Mar 06 '18 at 09:53
  • Try Events, much simpler and faster than intercepting – Naramsim Mar 06 '18 at 10:27
  • @Bathooman ... there are now two approaches ... one based on method-modification/function-composition and another, `Proxy` based one. Each proving itself by working example code. – Peter Seliger Mar 06 '18 at 15:45

1 Answers1

1

Though one always should have a good reason for modifying standard methods of standard types, the base approach of method modification in JS is wrapping. One might even think about standardizing Function.prototype[before|after|around|afterThrowing|afterFinally]. Then modifying any given method or function will be as easy as with the next provided example that also might be the answer the OP is looking for ...

(function (Function) {
  var
    isFunction = function (type) {
      return (
           (typeof type == "function")
        && (typeof type.call == "function")
        && (typeof type.apply == "function")
      );
    },
    getSanitizedTarget = function (target) {
      return ((target != null) && target) || null;
    };

  Function.prototype.before = function (handler, target) { // before
    target = getSanitizedTarget(target);
    var proceed = this ;

    return (isFunction(handler) && isFunction(proceed) && function () {
      var args = arguments;

      handler.call((target || this), args);
      return proceed.apply((target || this), args);

    }) || proceed;
  };
  Function.prototype.after = function (handler, target) { // afterReturning
    target = getSanitizedTarget(target);
    var proceed = this ;

    return (isFunction(handler) && isFunction(proceed) && function () {
      var ret, args = arguments;

      ret = proceed.apply((target || this), args);
      handler.call((target || this), ret, args);

      return ret;

    }) || proceed;
  };
  Function.prototype.around = function (handler, target) { // around
    target = getSanitizedTarget(target);

    var proceed = this ;
    return (isFunction(handler) && isFunction(proceed) && function () {

      return handler.call((target || this), proceed, handler, arguments);

    }) || proceed;
  };
}(Function));


function modifyCanvasToDataUrlAfter(returnValue, thisArgs) {
  console.log('modifyCanvasToDataUrlAfter :: thisArgs : ', thisArgs);
  console.log('modifyCanvasToDataUrlAfter :: returnValue : ', returnValue);
}
HTMLCanvasElement.prototype.toDataURL = HTMLCanvasElement.prototype.toDataURL.after(modifyCanvasToDataUrlAfter);


var elmToJpgLow = document.getElementById('canvasToLowResolutionJpg');
var elmToJpgMedium = document.getElementById('canvasToMediumResolutionJpg');

console.log("elmToJpgLow.toDataURL('image/jpeg', 0.1) : ", elmToJpgLow.toDataURL('image/jpeg', 0.1));
console.log('elmToJpgLow.toDataURL : ', elmToJpgLow.toDataURL);

console.log("elmToJpgMedium.toDataURL('image/jpeg', 0.1) : ", elmToJpgMedium.toDataURL('image/jpeg', 0.5));
console.log('elmToJpgMedium.toDataURL : ', elmToJpgMedium.toDataURL);
.as-console-wrapper { max-height: 100%!important; top: 0; }
<canvas id="canvasToLowResolutionJpg" width="5" height="5"></canvas>
<canvas id="canvasToMediumResolutionJpg" width="5" height="5"></canvas>

Edit

A Proxy based approach might look like the following provided example ...

const canvasToDataUrlModifier = {
  apply: function(target, thisArg, argumentsList) {

    let returnValue = target.apply(thisArg, argumentsList);

    console.log('toDataUrlAfterModifier :: argumentsList : ', argumentsList);
    console.log('toDataUrlAfterModifier :: returnValue : ', returnValue);

    return returnValue;
  }
};

var elmToJpgLow = document.getElementById('canvasToLowResolutionJpg');
var elmToJpgMedium = document.getElementById('canvasToMediumResolutionJpg');

var proxyToJpgLow = new Proxy(elmToJpgLow.toDataURL, canvasToDataUrlModifier);
var proxyToJpgMedium = new Proxy(elmToJpgMedium.toDataURL, canvasToDataUrlModifier);

console.log("proxyToJpgLow.call(elmToJpgLow, 'image/jpeg', 0.1) : ", proxyToJpgLow.call(elmToJpgLow, 'image/jpeg', 0.1));

console.log("proxyToJpgMedium.call(elmToJpgMedium, 'image/jpeg', 0.5) : ", proxyToJpgMedium.call(elmToJpgMedium, 'image/jpeg', 0.5));
.as-console-wrapper { max-height: 100%!important; top: 0; }
<canvas id="canvasToLowResolutionJpg" width="5" height="5"></canvas>
<canvas id="canvasToMediumResolutionJpg" width="5" height="5"></canvas>
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • Thank you for your response peter, You know what I'm actually going to do is to be noticed when `canvas.toDataURL()` is called. It seems I cannot get that using your proposed way. I already did this for `document.createElement` by using code below: `let calls = (function(){ let fun = document.createElement; document.createElement = function(){ window.doc.push(arguments); return fun.apply(document, arguments); } return ()=>calls; })();` – Bathooman Mar 07 '18 at 04:09
  • @Bathooman ... you're welcome. And please describe, what from both approaches does not work for you. All given examples do provide running code. Thus one should be able to more easily understand how each approach does operate. On the other hand, I do not get what the code provided in the Q (see my comment there) as well as the one from your comment above intend to achieve. I don't know what you expect it to do and where, why and with which error messages it fails. Being provided with a bigger picture will be more helpful. – Peter Seliger Mar 07 '18 at 11:08
  • Dear peter, I just want to be informed when canvas.toDataURL() is called. i really don't know what to do. please help!! – Bathooman Mar 18 '18 at 10:13
  • document.createElement can be overridden with: `var origParseFloat = parseFloat; parseFloat = function(str) { alert("And I'm in your floats!"); return origParseFloat(str); }` – Bathooman Mar 18 '18 at 11:09
  • @Bathooman ... please first respond to the comment from March 7, 11:08 ... any further attempt of answering was just guessing as long as one does not know which part of the code invokes `canvas.toDataURL()` and which part has to intercept or needs to be notified about it. My answer from March 6, 10:38 offers two different approaches, each approach providing working example code. What did you try? Where got you stuck? – Peter Seliger Mar 19 '18 at 09:00