sadn1ck
sadn1ck

Reputation: 21

Monkey patch getUserMedia in a WebExtension?

What I'm trying to do:

First question here, sorry for any mistakes.

Basically what I want to do is implement filters for the webcam so that everyone you are in a video conference/call/meeting with can see it if you have webcam on. My first thought was to use CSS filters, which was stupid in hindsight, since it won't be seen by everybody.

Then I did some research(say, googling) and found out about a similar WebExtension, ZomboCam,(whose source I saw with crxviewer) which, basically intercepts the mediaStream, edits it, and sets the edited mediaStream as source of your webcam. I looked at its source, and found out that it Monkey Patches (or is it monkeypatches) the getUserMedia() function. In the source,

var originalMediaDevicesGetUserMedia = navigator.mediaDevices.getUserMedia;
if (navigator.mediaDevices.getUserMedia) {
        navigator.mediaDevices.getUserMedia = function getUserMedia(constraints) {
          return new Promise(function (resolve, reject) {
            originalMediaDevicesGetUserMedia.bind(navigator.mediaDevices)(constraints).then(function (stream) {
              return resolve(enhance(stream));
            }).catch(reject);
          });
        };
      }

The enhance() function works on a mediaStream passed to it as parameter.

What I tried:

I did the monkeypatching code in the content script of the WebExtension, if that helps.

I tried monkeypatching like this, in a webpage with a video element to display the webcam. It reached the Video devices found!, but not any further.

change() will be my function to work on mediaStream(any helpful tips for that?), and it doesn't do anything as of now.

const OGgum =  navigator.mediaDevices.getUserMedia;
    if(navigator.mediaDevices.getUserMedia){
        console.log('Video devices found!');
        navigator.mediaDevices.getUserMedia = (constraints) => {
            OGgum(constraints).then((stream)=> {
              console.log('gum Patched🎉');
              video.srcObject = change(stream);
            })
            .catch((error)=>{
              console.log('This Went wrong: '+error);
            })
          }
    }
    const change = (st) => {
      return st;
    }

Now with the disappointment of that not working, I tried monkey patching console.log() in the content script of the webextension to add some text in front of every log, like this:

const OGlog = console.log;
console.log = (params) => {
  OGlog('🥳 PATCHING: '+params);
}

console.log('Testing');

Outputs:

🥳 PATCHING: Testing

which WORKED but ONLY for console.log() INSIDE the content script. How do I make it work for every page? Meaning, suppose I do a console.log(), I expect it to have 🥳 PATCHING in front.

I supposed if I manage to get console.log() to work, I can probably work on the getUserMedia() myself.

Relevant Reading

What am I missing here? Also if you have alternatives for this task, please share them. Thanks!

Upvotes: 2

Views: 567

Answers (2)

James Arnott
James Arnott

Reputation: 56

To interact with the javascript on a webpage, you need to use scripting API instead of using a content script.

This is because the scripting API executes the JS in the "MAIN" world as opposed to the "ISOLATED" world which isn't able to interact with the javascript running on the page and therefore can't monkey-patch any native APIs.

Here's one example I just found of someone using the native chrome scripting API.

Here's another example executing JS in the "MAIN" world with Plasmo - It abstracts the scripting API away with the world ENUM ("MAIN" | "ISOLATED") so just keep that in mind.

Executing JS in the "MAIN" world in FireFox works a little differently so I'd recommend reading the docs beforehand if you're aiming for cross browser compatibility here.

Upvotes: 0

woxxom
woxxom

Reputation: 73766

In Firefox you should use wrappedJSObject to access the page context and exportFunction to expose your code into the page context. More info on MDN: Sharing objects with page scripts.

const pWindow = window.wrappedJSObject;
const pMediaDevices = pWindow.navigator.mediaDevices;
const pGUM = pMediaDevices.getUserMedia;
const myGUM = (...args) =>
  pGUM.apply(pMediaDevices, args).then(stream => {
    // .........
    return stream;
  });
pMediaDevices.getUserMedia = exportFunction(myGUM, pWindow);

If you thought it's complicated, in Chrome/Chromium browsers it's even worse.

Upvotes: 0

Related Questions