proxifying modifyGetContext

Bug reports and enhancement requests
Post Reply
skriptimaahinen
Senior Member
Posts: 239
Joined: Wed Jan 10, 2018 7:37 am

proxifying modifyGetContext

Post by skriptimaahinen » Mon May 03, 2021 10:53 am

Been looking at this function a while and didn't realize this https://github.com/hackademix/nscl/comm ... 7946672329. Sigh. Oh well, never did say I understand regex ;)

Anyhow... Lets tackle some error messages.

Btw, can't move the call to getContext to the front because if we return null we can't have the canvas initialized to webgl as it still needs to be able to provide other contexts like 2d. i.e.

Code: Select all

// Native behaviour. Assume webgl disabled from about:config. We perform these two consecutive calls:
canvas.getContext("webgl") -> null
canvas.getContext("2d") -> 2d context

// Assume webgl enabled.
canvas.getContext("webgl") -> webgl context
canvas.getContext("2d") -> null
So to achive this, getCanvas should not be called when the type is webgl. We just must make best effort to not return null when there should be an error.

Different types of errors I have encountered:

Code: Select all

TypeError: 'getContext' called on an object that does not implement interface HTMLCanvasElement.    HTMLCanvasElement.prototype.getContext()
TypeError: HTMLCanvasElement.getContext: At least 1 argument required, but only 0 passed            canvas.getContext()
TypeError: getContext is not a constructor                                                          new canvas.getContext
The first one should be handled by checking that the object is instance of HTMLCanvasElement. Using proxy will take care of the two others.

webglHook.js:

Code: Select all

function modifyGetContext(scope, env) {
  let dispatchEvent = EventTarget.prototype.dispatchEvent;
  let { Event } = scope;
  for (let canvas of ["HTMLCanvasElement", "OffscreenCanvas"]) {
    if (!(canvas in scope)) continue;
    
    const getContext = scope[canvas].prototype.getContext;

    const handler = cloneInto({
      apply: function(targetObj, thisArg, argumentsList) {
        if (thisArg instanceof HTMLCanvasElement && /webgl/i.test(argumentsList[0])) {
          let target = canvas === "HTMLCanvasElement" && document.contains(thisArg) ? thisArg : scope;
          env.port.postMessage("webgl", target);
          return null;
        }
        return getContext.call(thisArg, ...argumentsList);
      }
    }, scope, {cloneFunctions: true});
    
    const proxy = new scope.Proxy(getContext, handler);
    scope[canvas].prototype.getContext = proxy;
  }
}
The need to use a proxy stems mainly from the error message thrown by new operator. For reason unknown, if used on exported getContext, it seems the error gets initialized in contentscript context and while propagating back to the page, causes another error (DOMException).

Generating the error in the exported function for the right scope works around this, but there is no way to make the error message completely correct as the message should contain the line that is executed and this info is not available in the exported function. e.g.

Code: Select all

new canvas.getContext           -> TypeError: canvas.getContext is not a constructor
new windows.canvas.getContext   -> TypeError: windows.canvas.getContext is not a constructor
So proxy seems must. Note that the proxy is created in the page scope. Otherwise the error message from "new" will still be initialized in the contentscript context. Only the proxy handler is exported.

Also HTMLCanvasElement.prototype.getContext.length is now 1 as it should be.


Oh! Does line "let target = canvas === "HTMLCanvasElement" && document.contains(thisArg) ? thisArg : scope;" have some planned use? postMessage does not appear to use the target for anything.
Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0

User avatar
Giorgio Maone
Site Admin
Posts: 9133
Joined: Wed Mar 18, 2009 11:22 pm
Location: Palermo - Italy
Contact:

Re: proxifying modifyGetContext

Post by Giorgio Maone » Mon May 03, 2021 8:05 pm

skriptimaahinen wrote:
Mon May 03, 2021 10:53 am
Oh! Does line "let target = canvas === "HTMLCanvasElement" && document.contains(thisArg) ? thisArg : scope;" have some planned use? postMessage does not appear to use the target for anything.
It uses it to retrieve the canvas element and create its placeholder, if needed (i.e. if the node is still attached to the document and it's not an OffscreenCanvas).
Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0

skriptimaahinen
Senior Member
Posts: 239
Joined: Wed Jan 10, 2018 7:37 am

Re: proxifying modifyGetContext

Post by skriptimaahinen » Tue May 04, 2021 6:48 am

Should the target be passed to the fire() in postMessage()?

Also little patch for my patch:

"thisArg instanceof HTMLCanvasElement" test should be "thisArg instanceof window[canvas]" so it catches Offscreencanvas too.
Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0

User avatar
Giorgio Maone
Site Admin
Posts: 9133
Joined: Wed Mar 18, 2009 11:22 pm
Location: Palermo - Italy
Contact:

Re: proxifying modifyGetContext

Post by Giorgio Maone » Tue May 04, 2021 7:05 am

skriptimaahinen wrote:
Tue May 04, 2021 6:48 am
Should the target be passed to the fire() in postMessage()
That's correct, thanks.
Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0

skriptimaahinen
Senior Member
Posts: 239
Joined: Wed Jan 10, 2018 7:37 am

Re: proxifying modifyGetContext

Post by skriptimaahinen » Wed May 05, 2021 7:51 am

Code: Select all

-   const getContext = scope[canvas].prototype.getContext;
+   const CanvasClass = window[canvas];
+   const getContext = CanvasClass.prototype.getContext;
Unfortunately this change affects the "target" getContext for Proxy which needs to be from page context, otherwise there will be e.g. "Permission denied" error when trying to run getContext.call() on the page.

Though even if just the "target" getContext is from page context, using page script like:

HTMLCanvasElement.prototype.getContext()

causes a

TypeError: 'getContext' called on an object that does not implement interface HTMLCanvasElement. (line 23: return getContext.call...)

As it should. But since it's thrown by the getContext from the privileged context, it will cause a DOMException:InvalidStateError when propagating to the page.

Is there a compelling reason to use privileged getContext?
Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0

Post Reply