diff --git a/manifest.json b/manifest.json index 92e1ed6..ae83ec9 100644 --- a/manifest.json +++ b/manifest.json @@ -2,8 +2,8 @@ "manifest_version": 3, "name": "HaramBlur", "description": "Protect your privacy and uphold Islamic values by auto detecting & blurring images and videos of unwanted or impermissible content.", - "version": "0.1.7", - "permissions": ["storage", "offscreen"], + "version": "0.1.8", + "permissions": ["storage", "offscreen","contextMenus"], "author": "md.alganzory@gmail.com", "action": { "default_title": "HaramBlur", diff --git a/package.json b/package.json index 5aef84c..0e3c30b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "scripts": { "build": "vite build", "dev": "vite build --watch", - "release": "bestzip extension.zip dist manifest.json src/assets src/background.js src/popup.html src/popup.js src/offscreen.html src/offscreen.js src/modules/* tfjs/*" + "release": "bestzip extension.zip dist manifest.json src/assets src/background.js src/constants.js src/popup.html src/popup.js src/offscreen.html src/offscreen.js src/modules/* tfjs/*" }, "devDependencies": { "bestzip": "^2.2.1", diff --git a/src/background.js b/src/background.js index 63809b6..2895860 100644 --- a/src/background.js +++ b/src/background.js @@ -45,10 +45,60 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.type === "getSettings") { chrome.storage.sync.get(["hb-settings"], function (result) { sendResponse(result["hb-settings"]); + + const isVideoEnabled = + result["hb-settings"].status && + result["hb-settings"].blurVideos; + chrome.contextMenus.update("enable-detection", { + enabled: isVideoEnabled, + checked: isVideoEnabled, + title: isVideoEnabled + ? "Enabled for this video" + : "Please enable video detection in settings", + }); + }); + return true; + } else if (request.type === "video-status") { + chrome.contextMenus.update("enable-detection", { + checked: request.status, }); return true; } }); +// context menu: "enable detection on this video" +chrome.contextMenus.create({ + id: "enable-detection", + title: "Enable for this video", + contexts: ["all"], + type: "checkbox", + enabled: true, + checked: true, +}); + +chrome.contextMenus.onClicked.addListener((info, tab) => { + console.log("HB== context menu clicked", info, tab); + if (info.menuItemId === "enable-detection") { + if (info.checked) { + chrome.tabs.sendMessage(tab.id, { + type: "enable-detection", + }); + } else { + chrome.tabs.sendMessage(tab.id, { + type: "disable-detection", + }); + } + } + + return true; +}); + +// on install, onboarding +chrome.runtime.onInstalled.addListener(function (details) { + chrome.tabs.create({ + url: "https://onboard.haramblur.com/", + }); +}); + // on uninstall chrome.runtime.setUninstallURL("https://forms.gle/RovVrtp29vK3Z7To7"); diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..0d8732e --- /dev/null +++ b/src/constants.js @@ -0,0 +1,25 @@ +export const DEFAULT_SETTINGS = { + status: true, + blurryStartMode: false, + blurAmount: 20, + blurImages: true, + blurVideos: true, + blurMale: false, + blurFemale: true, + unblurImages: false, + unblurVideos: false, + gray: true, + strictness: 0.5, // goes from 0 to 1 +}; + +export const STATUSES = { + // the numbers are there to make it easier to sort + ERROR: "-1ERROR", + OBSERVED: "0OBSERVED", + QUEUED: "1QUEUED", + LOADING: "2LOADING", + LOADED: "3LOADED", + PROCESSING: "4PROCESSING", + PROCESSED: "5PROCESSED", + DISABLED: "9DISABLED", +}; \ No newline at end of file diff --git a/src/content.js b/src/content.js index 202f5e5..381011c 100644 --- a/src/content.js +++ b/src/content.js @@ -2,16 +2,11 @@ import { attachObserversListener, initMutationObserver, } from "./modules/observers"; -import { - getSettings, - listenForMessages, - toggleOnOffStatus, -} from "./modules/settings"; +import Settings from "./modules/settings"; import { attachStyleListener } from "./modules/style"; const attachAllListeners = () => { // Listen for more settings - listenForMessages(); attachStyleListener(); attachObserversListener(); }; @@ -20,10 +15,10 @@ if (window.self === window.top) { attachAllListeners(); initMutationObserver(); - getSettings() - .then(() => { + Settings.init() + .then((settings) => { // turn on/off the extension - toggleOnOffStatus(); + settings.toggleOnOffStatus(); }) .catch((e) => { console.log("HB==INITIALIZATION ERROR", e); diff --git a/src/modules/detector.js b/src/modules/detector.js index 8037b30..8295aa7 100644 --- a/src/modules/detector.js +++ b/src/modules/detector.js @@ -24,7 +24,7 @@ const HUMAN_CONFIG = { detector: { modelPath: "blazeface.json", maxDetected: 2, - minConfidence: 0.3, + minConfidence: 0.25, }, description: { enabled: true, @@ -204,8 +204,11 @@ const nsfwModelClassify = async (tensor, config = NSFW_CONFIG) => { nsfwCache.predictions.length === 0 ) { // if size is not 224, resize the image - if (tensor.shape[1] !== config.size) { - resized = tf.image.resizeBilinear(tensor, [ + if ( + tensor.shape[1] !== config.size || + tensor.shape[2] !== config.size + ) { + resized = tf.image.resizeNearestNeighbor(tensor, [ config.size, config.size, ]); @@ -300,7 +303,7 @@ const genderPredicate = (gender, score, detectMale, detectFemale) => { ); } if (!detectMale && detectFemale) { - return gender === "female" && score > 0.2; + return gender === "female" && score > 0.25; } return false; @@ -312,7 +315,6 @@ const containsGenderFace = (detections, detectMale, detectFemale) => { } const faces = detections.face; - if (detectMale || detectFemale) return faces.some( (face) => diff --git a/src/modules/helpers.js b/src/modules/helpers.js index 629c917..ad87156 100644 --- a/src/modules/helpers.js +++ b/src/modules/helpers.js @@ -1,12 +1,14 @@ // import { STATUSES } from "./observers"; +import { STATUSES } from "../constants.js"; + const MAX_IMG_HEIGHT = 300; const MAX_IMG_WIDTH = 400; const MIN_IMG_WIDTH = 32; const MIN_IMG_HEIGHT = 32; // maintain 1920x1080 aspect ratio -const MAX_VIDEO_WIDTH = 1920 / 5; -const MAX_VIDEO_HEIGHT = 1080 / 5; +const MAX_VIDEO_WIDTH = 1920 / 4.5; +const MAX_VIDEO_HEIGHT = 1080 / 4.5; const loadImage = async (imgSrc, imgWidth, imgHeight) => { // let { newWidth, newHeight } = calcResize(imgWidth, imgHeight); @@ -149,29 +151,76 @@ const timeTaken = (fnToRun) => { return afterRun - beforeRun; }; -const getCanvas = (width, height) => { - let c = document?.getElementById("hb-in-canvas"); - if (!c) { - c = document?.createElement("canvas"); +const getCanvas = (width, height, offscreen = true) => { + let c; + + if (!offscreen) { + c= document.getElementById("hb-in-canvas") ?? document.createElement("canvas"); c.id = "hb-in-canvas"; - } - c.width = width; - c.height = height; - - // uncomment this to see the canvas (debugging) - // c.style.position = "absolute"; - // c.style.top = "0"; - // c.style.left = "0"; - // c.style.zIndex = 9999; - - // if it's not appended to the DOM, append it - if (!c.parentElement) { - document.body.appendChild(c); + c.width = width; + c.height = height; + // uncomment this to see the canvas (debugging) + // c.style.position = "absolute"; + // c.style.top = "0"; + // c.style.left = "0"; + // c.style.zIndex = 9999; + + // if it's not appended to the DOM, append it + if (!c.parentElement) { + document.body.appendChild(c); + } + } else { + c = new OffscreenCanvas(width, height); } return c; }; +const disableVideo = (video) => { + video.dataset.HBstatus = STATUSES.DISABLED; + video.classList.remove("hb-blur"); +}; + +const enableVideo = (video) => { + video.dataset.HBstatus = STATUSES.PROCESSING; +}; + +function updateBGvideoStatus(videosInProcess) { + // checks if there are any disabled videos in the videosInProcess array, sends a message to the background to disable/enable the extension icon + const disabledVideos = + videosInProcess.filter( + (video) => + video.dataset.HBstatus === STATUSES.DISABLED && + !video.paused && + video.currentTime > 0 + ) ?? []; + + chrome.runtime.sendMessage({ + type: "video-status", + status: disabledVideos.length === 0, + }); +} + +const requestIdleCB = + window.requestIdleCallback || + function (cb) { + var start = Date.now(); + return setTimeout(function () { + cb({ + didTimeout: false, + timeRemaining: function () { + return Math.max(0, 50 - (Date.now() - start)); + }, + }); + }, 1); + }; + +const cancelIdleCB = + window.cancelIdleCallback || + function (id) { + clearTimeout(id); + }; + export { loadImage, loadVideo, @@ -185,4 +234,9 @@ export { resetElement, isImageTooSmall, getCanvas, + disableVideo, + enableVideo, + updateBGvideoStatus, + requestIdleCB, + cancelIdleCB, }; diff --git a/src/modules/observers.js b/src/modules/observers.js index e0cb962..13b400d 100644 --- a/src/modules/observers.js +++ b/src/modules/observers.js @@ -1,28 +1,13 @@ // observers.js // This module exports mutation observer and image processing logic. -import { isImageTooSmall, listenToEvent, processNode } from "./helpers.js"; +import { disableVideo, enableVideo, isImageTooSmall, listenToEvent, processNode, updateBGvideoStatus } from "./helpers.js"; -import { - shouldDetect, - shouldDetectImages, - shouldDetectVideos, -} from "./settings.js"; import { applyBlurryStart } from "./style.js"; import { processImage, processVideo } from "./processing2.js"; +import { STATUSES } from "../constants.js"; +let mutationObserver, _settings; +let videosInProcess = []; -let mutationObserver; - -const STATUSES = { - // the numbers are there to make it easier to sort - ERROR: "-1ERROR", - OBSERVED: "0OBSERVED", - QUEUED: "1QUEUED", - LOADING: "2LOADING", - LOADED: "3LOADED", - PROCESSING: "4PROCESSING", - PROCESSED: "5PROCESSED", - INVALID: "9INVALID", -}; const startObserving = () => { if (!mutationObserver) initMutationObserver(); @@ -56,8 +41,9 @@ const initMutationObserver = () => { }; const attachObserversListener = () => { - listenToEvent("settingsLoaded", () => { - if (!shouldDetect()) { + listenToEvent("settingsLoaded", ({ detail: settings }) => { + _settings = settings; + if (!_settings.shouldDetect()) { mutationObserver?.disconnect(); mutationObserver = null; } else { @@ -65,9 +51,9 @@ const attachObserversListener = () => { if (!mutationObserver) startObserving(); } }); - listenToEvent("toggleOnOffStatus", async () => { - // console.log("HB== Observers Listener", shouldDetect()); - if (!shouldDetect()) { + listenToEvent("toggleOnOffStatus", () => { + // console.log("HB== Observers Listener", _settings.shouldDetect()); + if (!_settings?.shouldDetect()) { // console.log("HB== Observers Listener", "disconnecting"); mutationObserver?.disconnect(); mutationObserver = null; @@ -76,13 +62,45 @@ const attachObserversListener = () => { if (!mutationObserver) startObserving(); } }); + + // listen to message from background to tab + chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.type === "disable-detection") { + videosInProcess + .filter( + // filter videos that are playing, not disabled and in process + (video) => + video.dataset.HBstatus === STATUSES.PROCESSING && + !video.paused && + video.currentTime > 0 + ) + .forEach((video) => { + disableVideo(video); + + }); + } else if (request.type === "enable-detection") { + videosInProcess + .filter( + (video) => + video.dataset.HBstatus === STATUSES.DISABLED && + !video.paused && + video.currentTime > 0 + ) + .forEach((video) => { + enableVideo(video); + }); + } + return true; + }); }; function observeNode(node, srcAttribute) { if ( !( - (node.tagName === "IMG" && shouldDetectImages) || - (node.tagName === "VIDEO" && shouldDetectVideos) + (node.tagName === "IMG" && + (_settings ? _settings.shouldDetectImages() : true)) || + (node.tagName === "VIDEO" && + (_settings ? _settings.shouldDetectVideos() : true)) ) ) return; @@ -100,8 +118,12 @@ function observeNode(node, srcAttribute) { node.dataset.HBstatus = STATUSES.OBSERVED; if (node.src?.length) { // if there's no src attribute yet, wait for the mutation observer to catch it - node.tagName === "IMG" ? processImage(node, STATUSES) : null; - node.tagName === "VIDEO" ? processVideo(node, STATUSES) : null; + if (node.tagName === "IMG") processImage(node, STATUSES); + else if (node.tagName === "VIDEO") { + processVideo(node, STATUSES); + videosInProcess.push(node); + updateBGvideoStatus(videosInProcess); + } } else { // remove the HBstatus if the node has no src attribute delete node.dataset?.HBstatus; diff --git a/src/modules/processing2.js b/src/modules/processing2.js index 6efa9e2..4b2da0d 100644 --- a/src/modules/processing2.js +++ b/src/modules/processing2.js @@ -1,12 +1,19 @@ -import { calcResize, loadVideo, getCanvas, emitEvent } from "./helpers"; +import { + calcResize, + loadVideo, + getCanvas, + emitEvent, + requestIdleCB, +} from "./helpers"; import { removeBlurryStart } from "./style"; +import { STATUSES } from "../constants.js"; const FRAME_RATE = 1000 / 25; // 25 fps // threshold for number of consecutive frames that need to be positive for the image to be considered positive const POSITIVE_THRESHOLD = 1; //at 25 fps, this is 0.04 seconds of consecutive positive detections // threshold for number of consecutive frames that need to be negative for the image to be considered negative -const NEGATIVE_THRESHOLD = 5; //at 25 fps, this is 0.2 seconds of consecutive negative detections +const NEGATIVE_THRESHOLD = 4; //at 25 fps, this is 0.16 seconds of consecutive negative detections /** * Object containing the possible results of image processing. * @typedef {Object} RESULTS @@ -25,11 +32,13 @@ const RESULTS = { let requestCount = 0; let detectedCount = 0; let detectionStarted = false; +let activeFrame = false; +let canv, ctx; const flagDetectionStart = () => { if (detectionStarted) return; // detection is marked as started when at least 1/8th of the images have been processed (arbitrary number) - if ((detectedCount >= requestCount / 8) && !detectionStarted) { + if (detectedCount >= requestCount / 8 && !detectionStarted) { detectionStarted = true; console.log("HaramBlur: Detection started"); emitEvent("detectionStarted"); @@ -73,14 +82,16 @@ const processImage = (node, STATUSES) => { }; const processFrame = async (video, { width, height }) => { - return await new Promise((resolve, reject) => { - const canv = getCanvas(width, height); - const ctx = canv.getContext("2d", { - willReadFrequently: true, - }); + return new Promise((resolve, reject) => { + if (!canv || canv.width !== width || canv.height !== height) { + canv = null; // free up memory (I think?) + canv = getCanvas(width, height); + ctx = canv.getContext("2d", {willReadFrequently: true}); + } + ctx.clearRect(0, 0, width, height); ctx.drawImage(video, 0, 0, width, height); - canv.toBlob((blob) => { + canv.convertToBlob({ type: "image/png" }).then((blob) => { let data = URL.createObjectURL(blob); chrome.runtime.sendMessage( { @@ -111,36 +122,45 @@ const videoDetectionLoop = async (video, { width, height }) => { // calculate the time difference const diffTime = currTime - video.dataset.HBprevTime; - if (!video.paused) { + if (video.dataset.HBstatus === STATUSES.DISABLED) { + video.classList.remove("hb-blur"); + } + if (!video.paused && video.dataset.HBstatus !== STATUSES.DISABLED) { try { if (diffTime >= FRAME_RATE) { // store the current timestamp video.dataset.HBprevTime = currTime; - processFrame(video, { width, height }) - .then(({ result, timestamp }) => { - if (result === "error") { - throw new Error("HB==Error in processFrame"); - } - - // if frame was skipped, don't process it - if (result === "skipped") { - // console.log( "skipped frame"); - return; - } - - // if the frame is too old, don't process it - if (video.currentTime - timestamp > 0.5) { - // console.log("too old frame"); - return; - } - - // process the result - processVideoDetections(result, video); - }) - .catch((error) => { - throw error; - }); + if (!activeFrame) { + activeFrame = true; + processFrame(video, { width, height }) + .then(({ result, timestamp }) => { + if (result === "error") { + throw new Error("HB==Error in processFrame"); + } + + // if frame was skipped, don't process it + if (result === "skipped") { + // console.log( "skipped frame"); + return; + } + + // if the frame is too old, don't process it + if (video.currentTime - timestamp > 0.5) { + // console.log("too old frame"); + return; + } + + // process the result + processVideoDetections(result, video); + }) + .catch((error) => { + throw error; + }) + .finally(() => { + activeFrame = false; + }); + } } } catch (error) { console.log("HB==Video detection loop error", error, video); @@ -180,7 +200,10 @@ const processVideo = async (node, STATUSES) => { ); flagDetectionStart(); removeBlurryStart(node); - videoDetectionLoop(node, { width: newWidth, height: newHeight }); + + requestIdleCB(() => { // requestIdleCallback is used to prevent the video and dom from freezing (I think?) + videoDetectionLoop(node, { width: newWidth, height: newHeight }); + }, {timeout: 1000}); } catch (e) { console.log("HB== processVideo error", e); } diff --git a/src/modules/settings.js b/src/modules/settings.js index 9542a3d..153893a 100644 --- a/src/modules/settings.js +++ b/src/modules/settings.js @@ -1,115 +1,139 @@ -import { emitEvent, listenToEvent } from "./helpers"; +import { emitEvent } from "./helpers.js"; +import { DEFAULT_SETTINGS } from "../constants.js"; + +class Settings { + /* + * @private + */ + constructor(settings = DEFAULT_SETTINGS) { + this._settings = settings; + } -let settings = {}; + shouldDetectMale() { + if (!this._settings.status) return false; + return this._settings.blurMale; + } -let shouldDetectVideos = true; -let shouldDetectImages = true; -let shouldDetectMale = false; -let shouldDetectFemale = false; -let strictness = 0.3; + shouldDetectFemale() { + if (!this._settings.status) return false; + return this._settings.blurFemale; + } -function shouldDetectGender() { - return shouldDetectMale || shouldDetectFemale; -} + shouldDetectGender() { + if (!this._settings.status) return false; + return this.shouldDetectMale() || this.shouldDetectFemale(); + } -function shouldDetect() { - if ((!shouldDetectImages && !shouldDetectVideos)) { - return false; + shouldDetectImages() { + if (!this._settings.status) return false; + return this._settings.blurImages; } - return shouldDetectGender(); -} -function isBlurryStartMode() { - return settings.blurryStartMode; -} + shouldDetectVideos() { + if (!this._settings.status) return false; + return this._settings.blurVideos; + } -function setSettings() { - if (settings.status !== true) { - shouldDetectImages = false; - shouldDetectVideos = false; - } else { - shouldDetectImages = settings.blurImages; - shouldDetectVideos = settings.blurVideos; - shouldDetectMale = settings.blurMale; - shouldDetectFemale = settings.blurFemale; - strictness = settings.strictness; + // alias + shouldBlurImages() { + return this.shouldDetectImages(); } -} -function toggleOnOffStatus() { - // console.log("HB==toggleOnOffStatus", settings.status) + // alias + shouldBlurVideos() { + return this.shouldDetectVideos(); + } - setSettings(); - // console.log("HB==toggleOnOffStatus", settings.status); - emitEvent("toggleOnOffStatus", settings.status); -} + shouldUnblurImages() { + if (!this._settings.status) return false; + return this._settings.unblurImages; + } + shouldUnblurVideos() { + if (!this._settings.status) return false; + return this._settings.unblurVideos; + } + + shouldDetect() { + if (!this._settings.status) return false; + if (!this.shouldDetectImages() && !this.shouldDetectVideos()) + return false; + + // at least 1 gender should be selected for detection to start, + // this could change in the future (if we wanna allow nsfw only detection) + return this.shouldDetectGender(); + } + + isBlurryStartMode() { + if (!this.shouldDetect()) return false; + return this._settings.blurryStartMode; + } -function getSettings() { - return new Promise(function (resolve) { - chrome.storage.sync.get(["hb-settings"], function (storage) { - settings = storage["hb-settings"]; - resolve(); + getBlurAmount() { + if (!this.shouldDetect()) return 0; + return this._settings.blurAmount; + } + + getStrictness() { + if (!this.shouldDetect()) return 0; + return this._settings.strictness; + } + + isGray() { + if (!this.shouldDetect()) return false; + return this._settings.gray; + } + getSettings() { + return this._settings; + } + + setSettings(settings) { + this._settings = settings; + } + + toggleOnOffStatus() { + emitEvent("toggleOnOffStatus", this); + } + + listenForChanges() { + chrome.runtime.onMessage.addListener( + (request, sender, sendResponse) => { + if (request.type === "updateSettings") { + this.updateSettings(request.newSetting); + } + return true; + } + ); + } + // this acts as an async constructor + static async init() { + const loadedSettings = await new Promise((resolve) => { + chrome.runtime.sendMessage({ type: "getSettings" }, (settings) => { + resolve(settings); + }); }); - }); -} + const settings = new Settings(loadedSettings); + settings.listenForChanges(); + emitEvent("settingsLoaded", settings); + return settings; + } -function listenForMessages() { - listenToEvent("settingsLoaded", setSettings); - chrome.runtime.onMessage.addListener(function ( - request, - sender, - sendResponse - ) { - if (request.message?.type === "updateSettings") { - updateSettings(request.message.newSetting); + updateSettings(newSetting) { + const { key, value } = newSetting; + + this._settings[key] = value; + switch (key) { + case "status": + this.toggleOnOffStatus(); + break; + case "blurAmount": + emitEvent("changeBlurAmount", this); + break; + case "gray": + emitEvent("changeGray", this); + break; } - }); + } } -const updateSettings = (newSetting) => { - // console.log("HB==updateSettings", newSetting); - const { key, value } = newSetting; - - // take action based on key - switch (key) { - case "status": - settings.status = value; - toggleOnOffStatus(); - break; - case "blurAmount": - settings.blurAmount = value; - changeBlurAmount(); - break; - case "gray": - settings.gray = value; - changeGray(); - break; - } -}; - -const changeBlurAmount = () => { - // emit event to style.js - emitEvent("changeBlurAmount", settings.blurAmount); -}; - -const changeGray = () => { - // emit event to style.js - emitEvent("changeGray", settings.gray); -}; - - -export { - settings, - isBlurryStartMode, - getSettings, - toggleOnOffStatus, - listenForMessages, - shouldDetect, - shouldDetectGender, - shouldDetectImages, - shouldDetectVideos, - shouldDetectMale, - shouldDetectFemale, - strictness, -}; +export default Settings; diff --git a/src/modules/style.js b/src/modules/style.js index 6305263..8c31dd3 100644 --- a/src/modules/style.js +++ b/src/modules/style.js @@ -2,13 +2,14 @@ // This module exports the style sheet and blur effect functions import { emitEvent, listenToEvent } from "./helpers.js"; -import { settings, shouldDetect, isBlurryStartMode } from "./settings.js"; const BLURRY_START_MODE_TIMEOUT = 7000; // TODO: make this a setting maybe? let hbStyleSheet, blurryStartStyleSheet, - queuingStarted = false; -const initStylesheets = () => { + _settings; + +const initStylesheets = ({detail}) => { + _settings = detail; // console.log("HB==INIT STYLESHEETS") hbStyleSheet = document.createElement("style"); hbStyleSheet.id = "hb-stylesheet"; @@ -17,18 +18,20 @@ const initStylesheets = () => { }; const initBlurryMode = () => { - if (!shouldDetect() || !isBlurryStartMode()) return; + if (!_settings.shouldDetect() || !_settings.isBlurryStartMode()) return; blurryStartStyleSheet = document.createElement("style"); blurryStartStyleSheet.id = "hb-blurry-start-stylesheet"; blurryStartStyleSheet.innerHTML = ` - img:not(#hb-logo), video{ - filter: blur(${settings.blurAmount}px) ${settings.gray ? "grayscale(100%)" : ""} !important; + img, video{ + filter: blur(${_settings.getBlurAmount()}px) ${ + _settings.isGray() ? "grayscale(100%)" : "" + } !important; transition: filter 0.1s ease !important; opacity: unset !important; } - img:not(#hb-logo):hover, video:hover{ - filter: blur(0px) ${settings.gray ? "grayscale(0%)" : ""} !important; + img:hover, video:hover{ + filter: blur(0px) ${_settings.isGray() ? "grayscale(0%)" : ""} !important; transition: filter 0.5s ease !important; transition-delay: 0.5s !important; } @@ -43,19 +46,20 @@ const initBlurryMode = () => { }, BLURRY_START_MODE_TIMEOUT); }; -const setStyle = () => { +const setStyle = ({detail:settings}) => { + _settings = settings; // console.log("HB==SET STYLE") if (!hbStyleSheet) { initStylesheets(); } - if (!shouldDetect()) { + if (!_settings.shouldDetect()) { hbStyleSheet.innerHTML = ""; return; } - const shouldBlurImages = settings.blurImages; - const shouldBlurVideos = settings.blurVideos; - const shouldUnblurImagesOnHover = settings.unblurImages; - const shouldUnblurVideosOnHover = settings.unblurVideos; + const shouldBlurImages = _settings.shouldBlurImages(); + const shouldBlurVideos = _settings.shouldBlurVideos(); + const shouldUnblurImagesOnHover = _settings.shouldUnblurImages(); + const shouldUnblurVideosOnHover = _settings.shouldUnblurVideos(); let blurSelectors = []; if (shouldBlurImages) blurSelectors.push("img" + ".hb-blur"); @@ -70,7 +74,9 @@ const setStyle = () => { unblurSelectors = unblurSelectors.join(", "); hbStyleSheet.innerHTML = ` ${blurSelectors} { - filter: blur(${settings.blurAmount}px) ${settings.gray ? "grayscale(100%)" : ""} !important; + filter: blur(${_settings.getBlurAmount()}px) ${ + _settings.isGray() ? "grayscale(100%)" : "" + } !important; transition: filter 0.1s ease !important; opacity: unset !important; } @@ -79,7 +85,7 @@ const setStyle = () => { if (unblurSelectors) { hbStyleSheet.innerHTML += ` ${unblurSelectors} { - filter: blur(0px) ${settings.gray ? "grayscale(0%)" : ""} !important; + filter: blur(0px) ${_settings.isGray() ? "grayscale(0%)" : ""} !important; transition: filter 0.5s ease !important; transition-delay: 1s !important; } @@ -91,10 +97,19 @@ const setStyle = () => { animation: hb-blur-temp ${BLURRY_START_MODE_TIMEOUT}ms ease-in-out forwards !important; } + #hb-in-canvas { + display: none !important; + visibility: hidden !important; + } + @keyframes hb-blur-temp { - 0% { filter: blur(${settings.blurAmount}px) ${settings.gray ? "grayscale(100%)" : ""}; } - 95% { filter: blur(${settings.blurAmount}px) ${settings.gray ? "grayscale(100%)" : ""}; } - 100% { filter: blur(0px) ${settings.gray ? "grayscale(0%)" : ""}; } + 0% { filter: blur(${_settings.getBlurAmount()}px) ${ + _settings.isGray() ? "grayscale(100%)" : "" + }; } + 95% { filter: blur(${_settings.getBlurAmount()}px) ${ + _settings.isGray() ? "grayscale(100%)" : "" + }; } + 100% { filter: blur(0px) ${_settings.isGray() ? "grayscale(0%)" : ""}; } } `; }; @@ -105,25 +120,22 @@ const turnOffBlurryStart = (e) => { }; const applyBlurryStart = (node) => { - if (!isBlurryStartMode()) return; - node.classList.add("hb-blur-temp"); + if (_settings?.isBlurryStartMode()) { + node.classList.add("hb-blur-temp"); + } }; const removeBlurryStart = (node) => { - if (!isBlurryStartMode()) return; + node.classList.remove("hb-blur-temp"); }; -const setQueuingStarted = () => { - queuingStarted = true; -}; const attachStyleListener = () => { listenToEvent("settingsLoaded", initStylesheets); listenToEvent("toggleOnOffStatus", setStyle); listenToEvent("changeBlurAmount", setStyle); listenToEvent("changeGray", setStyle); - listenToEvent("queuingStarted", setQueuingStarted); listenToEvent("detectionStarted", turnOffBlurryStart); // listenToEvent("queuingStarted", turnOffBlurryStart); listenToEvent("blurryStartModeTimeout", turnOffBlurryStart); diff --git a/src/offscreen.js b/src/offscreen.js index 72c0eb4..5ebfb70 100644 --- a/src/offscreen.js +++ b/src/offscreen.js @@ -9,15 +9,11 @@ import { containsGenderFace, } from "./modules/detector.js"; import Queue from "./modules/queues.js"; +import Settings from "./modules/settings.js"; -// 4. define handleVideoDetection var settings; var queue; -const loadSettings = async () => { - settings = await chrome.runtime.sendMessage({ type: "getSettings" }); -}; - const loadModels = async () => { try { await initHuman(); @@ -50,7 +46,7 @@ const handleVideoDetection = async (request, sender, sendResponse) => { } activeFrame = true; frameImage.onload = () => { - runDetection(frameImage) + runDetection(frameImage, true) .then((result) => { activeFrame = false; sendResponse({ type: "detectionResult", result, timestamp }); @@ -69,7 +65,8 @@ const handleVideoDetection = async (request, sender, sendResponse) => { frameImage.src = data; }; -const start = () => { +const startListening = () => { + settings.listenForChanges(); chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.type === "imageDetection") { handleImageDetection(request, sender, sendResponse); @@ -81,30 +78,32 @@ const start = () => { }); }; -const runDetection = async (img) => { +const runDetection = async (img, isVideo = false) => { + if (!settings?.shouldDetect() || !img) return false; const tensor = human.tf.browser.fromPixels(img); // console.log("tensors count", human.tf.memory().numTensors); const nsfwResult = await nsfwModelClassify(tensor); // console.log("offscreen nsfw result", nsfwResult); - if (containsNsfw(nsfwResult, settings.strictness)) { + const strictness = settings.getStrictness() * (isVideo ? 0.75 : 1); // makes detection less strict for videos (to reduce false positives) + if (containsNsfw(nsfwResult, strictness)) { human.tf.dispose(tensor); return "nsfw"; } const predictions = await humanModelClassify(tensor); // console.log("offscreen human result", predictions); human.tf.dispose(tensor); - if (containsGenderFace(predictions, settings.blurMale, settings.blurFemale)) + if (containsGenderFace(predictions, settings.shouldDetectMale(), settings.shouldDetectFemale())) return "face"; return false; }; const init = async () => { - await loadSettings(); + settings = await Settings.init(); console.log("Settings loaded", settings); await loadModels(); console.log("Models loaded", human, nsfwModel); queue = new Queue(runDetection); - start(); + startListening(); }; init(); diff --git a/src/popup.js b/src/popup.js index dfbbcf5..b9efdcd 100644 --- a/src/popup.js +++ b/src/popup.js @@ -162,17 +162,18 @@ function updateCheckbox(key) { /* sendUpdatedSettings - Send updated settings object to tab.js to modify active tab blur CSS */ function sendUpdatedSettings(key) { + const message = { + type: "updateSettings", + newSetting: { + key: key, + value: settings[key], + }, + }; + + chrome.runtime.sendMessage(message); chrome.tabs.query({ currentWindow: true, active: true }, function (tabs) { var activeTab = tabs[0]; - chrome.tabs.sendMessage(activeTab.id, { - message: { - type: "updateSettings", - newSetting: { - key: key, - value: settings[key], - }, - }, - }); + chrome.tabs.sendMessage(activeTab.id, message); if (refreshableSettings.includes(key)) { refreshMessage.classList.remove("hidden");