javascriptshellgoogle-chrome-extensionchrome-extension-manifest-v3manifest.json

How can I resolve the error in the code for the chrome-extension that overwrites google-meet's facial images in real time?


  1. The URL after https://meet.google.com/ is not set properly and refers to a js file.

  1. document.body.insertBefore(script, document.body.firstChild) in the loader.js is giving me an error, can't I use it in manifestV3?

Refused to load the script 'https://meet.google.com/tf-core.js' because it violates the Content Security Policy directive


Refused to execute inline script because it violates the Content Security Policy directive


Uncaught (in promise) Event Context https://meet.google.com/ Stack trace loader.js:36 (Unnamed function)



extensions.sh

#!/usr/bin/env bash

#NOTE: if you are on macOS, update to bash v4 i.e brew install bash

rm -rf extension extension.zip
cp -r public extension 
cd extension
 
declare -A scripts0=(
    [file]='tf-core.js'
    [url]='https://unpkg.com/@tensorflow/tfjs-core@2.4.0/dist/tf-core.js'
)
declare -A scripts1=(
    [file]='tf-converter.js'
    [url]='https://unpkg.com/@tensorflow/tfjs-converter@2.4.0/dist/tf-converter.js'
)
declare -A scripts2=(
    [file]='tf-backend-webgl.js'
    [url]='https://unpkg.com/@tensorflow/tfjs-backend-webgl@2.4.0/dist/tf-backend-webgl.js'
)
declare -A scripts3=(
    [file]='face-landmarks-detection.js'
    [url]='https://unpkg.com/@tensorflow-models/face-landmarks-detection@0.0.1/dist/face-landmarks-detection.js'
)

declare -n scripts
for scripts  in ${!scripts@}; do
  curl ${scripts[url]} -o ${scripts[file]}
  sed -i"" -e "s|${scripts[url]}|${scripts[file]}|g" main.js
done

zip -r extension.zip *
mv extension.zip ../



manifest.js

{
  "manifest_version": 3,
  "name": "Meet Face Hack Extension",
  "version": "0.0.1",
  "description": "Meet hack",
    "host_permissions": [
    "https://meet.google.com/"
  ],
  "content_scripts": [
    {
      "js": ["lmMarkImg.js","tf-core.js","tf-converter.js","tf- backend-webgl.js","face-landmarks-detection.js","loader.js","main.js"],
      "matches": ["https://meet.google.com/*"],
      "run_at": "document_start"
    }
  ]
}

loader.js

function loadScript(src) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script')
    script.src = src
    script.onload = () => {
      console.log(`loaded: ${src}`)
      resolve()
    }
    script.onerror = (e) => reject(e)
    document.body.insertBefore(script, document.body.firstChild)
  })
}

async function loadLocalScript(path) {
  const res = await fetch(chrome.runtime.getURL(path), {method: 'GET'})
  const text = await res.text()
  const script = document.createElement('script')
  script.textContent = text
  document.body.insertBefore(script, document.body.firstChild)
}


async function load() {
  loadLocalScript("lmMarkImg.js")
  
  await loadScript("tf-core.js")
  await loadScript("tf-converter.js")
  await loadScript("tf-backend-webgl.js")
  await loadScript("face-landmarks-detection.js")

  loadLocalScript("main.js")
}

window.addEventListener('load', async (evt) => {
  await load()
})

main.js

const video = document.createElement('video')
const canvas = document.createElement('canvas')
const canvasCtx = canvas.getContext('2d')
let model = null
let keepAnimation = false

let imageIndex = 0
function getImage() {
  const image = lmMarkImages[imageIndex]
  imageIndex += 1
  if (imageIndex == lmMarkImages.length) {
    imageIndex = 0
  }

  return image
}

function drawImage(prediction) {
  let imageIndex = 0
  const boundingBox = prediction.boundingBox
  const x = boundingBox.topLeft[0]
  const y = boundingBox.topLeft[1]
  const w = boundingBox.bottomRight[0] - x
  const h = boundingBox.bottomRight[1] - y

  // draw Rectangle for debug
  // canvasCtx.strokeStyle = "rgb(255, 0, 0)";
  // canvasCtx.strokeRect(x, y, w, h)

  const image = getImage()
  canvasCtx.drawImage(image, x, y, w, h)
}

async function updateCanvas() {
  if (!keepAnimation) return

  if (model) {
    canvasCtx.drawImage(video, 0, 0, canvas.width, canvas.height)

    const predictions = await model.estimateFaces({ input: video })
    for (const prediction of predictions) {
      drawImage(prediction)
    }
  }
  requestAnimationFrame(updateCanvas)
}

function isScreenSharing(constraints) {
  return !constraints.video.deviceId
}

function replaceStopFunction(stream, videoTrack) {
  if (!videoTrack) return

  videoTrack._stop = videoTrack.stop
  videoTrack.stop = function () {
    keepAnimation = false
    videoTrack._stop()
    stream.getTracks().forEach((track) => {
      track.stop()
    })
  }
}

const _getUserMedia = navigator.mediaDevices.getUserMedia.bind(
  navigator.mediaDevices
)

navigator.mediaDevices.getUserMedia = async function (constraints) {
  const srcStream = await _getUserMedia(constraints)

  if (isScreenSharing(constraints)) {
    return srcStream
  }

  video.srcObject = srcStream
  video.onloadedmetadata = function (e) {
    video.play()
    video.volume = 0.0
    video.width = video.videoWidth
    video.height = video.videoHeight
    canvas.width = video.width
    canvas.height = video.height

    keepAnimation = true
    updateCanvas()
  }

  const outStream = canvas.captureStream(10)
  const videoTrack = outStream.getVideoTracks()[0]
  replaceStopFunction(srcStream, videoTrack)

  return outStream
}

async function loadModel() {
  model = await faceLandmarksDetection.load(
    faceLandmarksDetection.SupportedPackages.mediapipeFacemesh
  )
  //console.log("model: loaded")
}

function main() {
  loadModel()
}

main()


Because it was necessary to paste a minimal amount of code, main.js was not pasted. I will add it if necessary.


Solution

    1. Content scripts running in the default ISOLATED world in ManifestV3 can't set textContent of a script element. Use src with chrome.runtime.getURL.
    2. Don't call loadScript for tf scripts, because you don't have them, they are part of main.js.

    Here's the entire loader.js:

    const scripts = [
      'lmMarkImg.js',
      'main.js',
    ];
    (function loadSequentially() {
      const el = document.createElement('script')
      el.src = chrome.runtime.getURL(scripts.shift());
      if (scripts[0]) el.onload = loadSequentially;
      document.documentElement.appendChild(el);
      el.remove();
    })();