diff --git a/AutoplayStopper/README.txt b/AutoplayStopper/README.txt new file mode 100644 index 0000000..fc8f2fc --- /dev/null +++ b/AutoplayStopper/README.txt @@ -0,0 +1,5 @@ +This is an extracted copy of https://chrome.google.com/webstore/detail/autoplaystopper/ejddcgojdblidajhngkogefpkknnebdh version 1.8.7. I then loaded it into Chrome as an unpacked extension. +This was done to bypass auto extension updates that may eventually be injected with malware. +The extraction was done using the "Chrome extension source viewer" addon (https://github.com/Rob--W/crxviewer). + +I reviewed the code for malicious changes and made other small tweaks. \ No newline at end of file diff --git a/AutoplayStopper/_locales/en/messages.json b/AutoplayStopper/_locales/en/messages.json new file mode 100644 index 0000000..ea520d1 --- /dev/null +++ b/AutoplayStopper/_locales/en/messages.json @@ -0,0 +1,201 @@ +{ + "extensionDescription": { + "message": "Stops video autoplay gracefully." + }, + + "allowSite": { + "message": "Allow autoplay for %S" + }, + + "allowSession": { + "message": "Allow session autoplay for %S" + }, + + "disableSite": { + "message": "Block flash detection for %S" + }, + + "allowAll": { + "message": "Disable everywhere" + }, + + "settings": { + "message": "Settings" + }, + + "version": { + "message": "Version" + }, + + "autoplayTabLabel": { + "message": "Autoplay" + }, + + "permissions": { + "message": "Websites Permissions:" + }, + + "manageExceptions": { + "message": "Exceptions..." + }, + + "defaultMode": { + "message": "Default Mode:" + }, + + "disableAutoplay": { + "message": "Block Autoplay" + }, + + "allowAutoplay": { + "message": "Allow Autoplay" + }, + + "flashTabLabel": { + "message": "Flash" + }, + + "allowFlash": { + "message": "Allow Detection" + }, + + "disableFlash": { + "message": "Block Detection" + }, + + "autoplayExceptions": { + "message": "Autoplay exceptions" + }, + + "flashExceptions": { + "message": "Flash detection exceptions" + }, + + "exceptionHostnameHeader": { + "message": "Hostname" + }, + + "exceptionBehaviorHeader": { + "message": "Behavior" + }, + + "exceptionAllow": { + "message": "Allow" + }, + + "exceptionBlock": { + "message": "Block" + }, + + "exceptionPrompt": { + "message": "Block (strict)" + }, + + "exceptionSession": { + "message": "Session" + }, + + "exceptionUndefined": { + "message": "Undefined" + }, + + "apply": { + "message": "Apply" + }, + + "removeAll": { + "message": "Remove All" + }, + + "done": { + "message": "Done" + }, + + "play": { + "message": "Play" + }, + + "pause": { + "message": "Pause" + }, + + "selector": { + "message": "Selector" + }, + + "text": { + "message": "Text" + }, + + "reset": { + "message": "Reset" + }, + + "script": { + "message": "Script" + }, + + "userScript": { + "message": "User Script" + }, + + "path": { + "message": "Path" + }, + + "actions": { + "message": "Actions" + }, + + "edit": { + "message": "Edit" + }, + + "load": { + "message": "Load" + }, + + "export": { + "message": "Export..." + }, + + "setPath": { + "message": "Set Path" + }, + + "selectFile": { + "message": "File..." + }, + + "advanced": { + "message": "Advanced" + }, + + "devtools": { + "message": "Devtools Panel" + }, + + "overwrite": { + "message": "Disable overwrite on update" + }, + + "debug": { + "message": "Log debug info" + }, + + "loadError": { + "message": "Load script error: " + }, + + "allowFileUrls": { + "message": "Error: Cannot use user script without permission to access file URLs." + }, + + "fileNotFound": { + "message": "AutoplayStopper - error: File not found.\n Check that the script folder is in the workspace and verify the path." + }, + + "evalError": { + "message": "AutoplayStopper - exception: " + } +} diff --git a/AutoplayStopper/background.js b/AutoplayStopper/background.js new file mode 100644 index 0000000..7a0c3ad --- /dev/null +++ b/AutoplayStopper/background.js @@ -0,0 +1,186 @@ + + +"use strict"; + +var Background = new function() { + + console.log("loading background.js ..."); + + const {js, css} = chrome.runtime.getManifest().content_scripts[0]; + const badgeColor = "#646464"; + const playMsg = chrome.i18n.getMessage("play"); + const pauseMsg = chrome.i18n.getMessage("pause"); + const sArea = "local"; + const permsAutoplayKey = Permissions.key("autoplay"); + const permsAutoplayDefaultKey = Permissions.defaultKey("autoplay"); + const permsFlashKey = Permissions.key("flash"); + const permsFlashDefaultKey = Permissions.defaultKey("flash"); + const sKeys = ["disabled", "debug", permsFlashKey, permsFlashDefaultKey, permsAutoplayKey, + permsAutoplayDefaultKey, "selector-css", "uhandler", "handler", "disableOverwrite", "devtools"]; + + var selector = null; + var permissions = null; + var permsAutoplayCache = {}; + var permsFlashCache = {}; + var tabs = { get(id) { return this[id] || (this[id] = {id, count: 0})}}; + var menuitem = null; + + var storage = new Storage(sKeys, sArea); + storage.addChangeListener(function(changes){ + if (changes.indexOf("selector-css") != -1) selector = storage.data["selector-css"].split("{")[0]; + if (changes.indexOf(permsAutoplayKey) != -1 || changes.indexOf(permsAutoplayDefaultKey) != -1) permsAutoplayCache = {}; + if (changes.indexOf(permsFlashKey) != -1 || changes.indexOf(permsFlashDefaultKey) != -1) permsFlashCache = {}; + if (changes.indexOf("disabled") != -1) updateIcon(); + }); + + storage.ready.then(function() { + console.log("storage.ready"); + updateIcon(); + permissions = new Permissions(storage.data); + if (permissions.default("autoplay") === undefined) permissions.setDefault("autoplay", Permission.DENY_ACTION); + if (permissions.default("flash") === undefined) permissions.setDefault("flash", Permission.ALLOW_ACTION); + chrome.webNavigation.onCommitted.addListener(onCommitted, {url: [{schemes: ["http", "https", "about"]}]}); + if (!storage.data.uhandler) + loadScript("uhandler", chrome.extension.getURL("script/userhandler.js")); + if (!storage.data.handler) + loadScript("handler", chrome.extension.getURL("script/handler.js")); + if (!storage.data["selector-css"]) + loadScript("selector-css", chrome.extension.getURL("script/selector.css")); + selector = storage.data["selector-css"].split("{")[0]; + js.forEach(function(a) { execScript(a, null, 0)}); + }); + + chrome.browserAction.setBadgeBackgroundColor({color: badgeColor}); + chrome.tabs.onRemoved.addListener(function(tabid){ delete tabs[tabid]; }); + chrome.tabs.onActivated.addListener(resetMenuitem); + chrome.runtime.onMessage.addListener(handleMessage); + chrome.runtime.onInstalled.addListener(function(details) { + if (details.reason == "update" && !storage.data.disableOverwrite) { + loadScript("handler", chrome.extension.getURL("script/handler.js")); + loadScript("selector-css", chrome.extension.getURL("script/selector.css")); + selector = storage.data["selector-css"].split("{")[0]; + } + }); + + return { + get i18n() { return i18n; }, + get storage() { return storage; }, + get permissions() { return permissions; }, + openOptionsPage: function openOptionsPage(hash) { + chrome.runtime.openOptionsPage(); + if (hash) addEventListener("message", function(e) { + if (location.origin == e.origin && e.data == "optionsPageActive") + e.source.location.hash = hash; + }, { once: true}); + } + }; + + function resetMenuitem(){ if (menuitem) menuitem = chrome.contextMenus.remove("play-menuitem"); } + + function updateIcon() { chrome.browserAction.setIcon({ path: "/icons/" + (storage.data.disabled ? "icon32d.png" : "icon32.png")})}; + + function handleMessage(request, sender, sendResponse) + { + if (!storage.data.handler) { + console.log("handleMessage delayed -", request, sender); + storage.ready.then(function() { handleMessage(request, sender, sendResponse)}); + return true; + } + if (request == "permission") { + var data = {debug: !!storage.data.debug}; + var allow = true, host = sender.tab && new URL(sender.tab.url).origin; + if (host && !storage.data.disabled) { + var permission = permsAutoplayCache[host] || + (permsAutoplayCache[host] = permissions.testPermission("autoplay", sender.tab.url)); + allow = permission == Permission.ALLOW_ACTION || + (permission == Permission.ACCESS_SESSION && tabs.get(sender.tab.id).last == host); + data.strict = permission == Permission.PROMPT_ACTION; + } + var msg = {msg: "permission", data, allow, selector, handler: storage.data.handler, uhandler: storage.data.uhandler}; + // console.log("background send:", msg, sender.url, sender.tab.url); + sendResponse(msg); + } + if (request == "count") + chrome.browserAction.setBadgeText({text: String(++tabs.get(sender.tab.id).count), tabId: sender.tab.id}); + if (request.msg == "contextmenu") { + resetMenuitem(); + if (request.media) + menuitem = chrome.contextMenus.create({ id: "play-menuitem", title: request.paused ? playMsg : pauseMsg, + contexts: ["all"], onclick: function onClick(info, tab) { if (info.menuItemId == "play-menuitem") sendResponse();} + }) + return !!request.media; + } + if (request.msg == "select") { + fetch(request.url, {cache: "no-store"}).then(function(res) { + res.blob().then(function(file){ sendResponse({url: URL.createObjectURL(file), name: file.name}); }); + }); + return true; + } + if (request.msg == "load") { + var res = loadScript(request.script, request.file); + sendResponse(res); + } + }; + + function onCommitted(details) + { + var allow = true, url = new URL(details.url); + if (url.protocol.search("^chrome") != -1) return; + + var tab = tabs.get(details.tabId); + if (details.frameId == 0) { + tab.last = tab.host; + tab.host = url.origin; + tab.url = details.url; + tab.count = 0; + chrome.browserAction.setBadgeText({text:'', tabId: tab.id}); + chrome.tabs.query({ active: true, currentWindow: true}, function(tabs) { + if (tabs[0] && tabs[0].id == tab.id) resetMenuitem(); + }); + } + if (!storage.data.disabled && tab.url) { + var permission = permsFlashCache[tab.host] || + (permsFlashCache[tab.host] = permissions.testPermission("flash", tab.url)); + if (permission == Permission.DENY_ACTION) + execScript("content/navigator.js", details.tabId, details.frameId); + } + }; + + function execScript(file, tabId, frameId) { + chrome.tabs.executeScript(tabId, {file, frameId, matchAboutBlank: true, runAt: "document_start"}, function() { + if (chrome.runtime.lastError && storage.data.debug) + console.error("chrome.tabs.executeScript - error", chrome.runtime.lastError, {tabId, frameId, file}); + }); + }; + + function insertCSS(file, tabId, frameId) { + chrome.tabs.insertCSS(tabId, {file, frameId, matchAboutBlank: true, runAt: "document_start"}, function() { + if (chrome.runtime.lastError && storage.data.debug) + console.error("chrome.tabs.insertCSS - error", chrome.runtime.lastError, {tabId, frameId, file}); + }); + }; + + function loadScript(script, url){ + try { + var request = new XMLHttpRequest(); + request.overrideMimeType("text/plain"); + request.open("GET", url, false); + request.setRequestHeader("pragma", "no-cache"); + request.send(); + if (request.statusText == "Not Found" || !request.responseURL) + throw new DOMException(`Failed to load '${url}'.`, "NetworkError"); + + storage.data[script] = request.responseText; + storage.commit([script]); + } catch (e) { + chrome.extension.isAllowedFileSchemeAccess(function(allowed) { + if(!allowed && url.search(/^blob:/) == -1) + return alert(chrome.i18n.getMessage("allowFileUrls")); + alert(`${chrome.i18n.getMessage("loadError")} ${e.name}\n ${e.message}`); + }); + return e; + } + }; +}; + +// diff --git a/AutoplayStopper/content/content.js b/AutoplayStopper/content/content.js new file mode 100644 index 0000000..2a32cbc --- /dev/null +++ b/AutoplayStopper/content/content.js @@ -0,0 +1,310 @@ + + + +const ContentScript = new function() { + + "use strict"; + + const start = Date.now(); + const any = (window.wrappedJSObject ? "-moz" : "-webkit") + "-any"; + const frameSelector = `iframe:${any}(:not([src]), [src^='javascript:' i], [src^='about:blank' i]):not([flashstopped])`; + + var data = {debug: false}; + const _trace = function TRACE(format, ...etc) { + if (!data.debug) return TRACE._noop || (TRACE._noop = function(){}); + return console.log.bind(console, "### " + format + " ###", ...etc); + }; + + var selector = null; + var handler = null; //loadURL(chrome.extension.getURL("script/handler.js")); + var href = frameElement !== null ? frameElement.src || "about:blank" : location.href; + var ret = 0; + + _trace("ready(%s) content.js - iframe: %s href: %s loc: %s fs: %s")(start - Date.now(), + !!frameElement && frameElement.id, href == location.href || href, location.href, window.flashstopped); + + if (window.flashstopped) return; + window.flashstopped = true; + + chrome.runtime.sendMessage("permission", handlePermission); + function handlePermission(response) { + if (chrome.runtime.lastError && ret <= 3) { + console.error(`handlePermission - error ret: ${ret} msg: ${chrome.runtime.lastError.message}`); + window.setTimeout(function() { chrome.runtime.sendMessage("permission", handlePermission)}, ret++ * 250); + }; + if (!response) return; + data = response.data; + selector = response.selector; + handler = `${response.uhandler}\n\n${response.handler}`; + if (!response.allow) load(window); + addEventListener("message", function(e) { + if (e.data && e.data.id == "userinput") e.stopImmediatePropagation(); + }, true); + }; + + if (parent && parent != window) + parent.postMessage({id: "userinput"}, "*"); + + function load({window, frameElement, location}) + { + var href = frameElement !== null ? frameElement.src || "about:blank" : location.href; + _trace("loading(%s) content.js - iframe: %s href: %s loc: %s")(start - Date.now(), + !!frameElement && frameElement.id, href == location.href || href, location.href); + + var match = 0, query = 0; + var injected = false; + var body = window.document.body; + var userInput = null; + + var n = handleDocument(); + _trace("body: %s n: %s")(!!body, n); + + var docObserver = new MutationObserver(function(mutations) { + + _trace("!!! docObserver - body: %s neb: %s !!!")(!!body, body != window.document.body); + if (window.document.body && body != window.document.body) { + if (body) observer.observe(window.document.body, { subtree: true, childList: true}); + body = window.document.body; + requestAnimationFrame(function() { + var n = handleDocument(); + _trace(">>>>>>> requestAnimationFrame n: %s <<<<<<<")(n); + }); + window.setTimeout(function() { + var n = handleDocument(); + _trace(">>>>>>> setting body observer - n: %s <<<<<<<<")(n); + }, 500); + var n = handleDocument(); + _trace("!!! docObserver - out n: %s !!!")(n); + } + }); + + var observer = new MutationObserver(function(mutations) { + + for (var i = 0; i < mutations.length; i++) + for (var j = 0; j < mutations[i].addedNodes.length; j++) { + var addedNode = mutations[i].addedNodes[j]; + if (addedNode.nodeType === 1){ + match++; + if (addedNode.children.length) query++; + if (handleNodesDeep(addedNode, true)) { + _trace(">>>>>>> MutationObserver: tag: %s id: %s <<<<<<<")(addedNode.localName, addedNode.id); + } + var frames = addedNode.matches(frameSelector) ? [addedNode] : []; + if (frames.push(...addedNode.querySelectorAll(frameSelector))) + handleFrames(frames); + } + } + }); + + if (window.document.body) + observer.observe((href.search("^https?:") == -1) ? window.document : window.document.body + , { subtree: true, childList: true}); + else + docObserver.observe(window.document.documentElement, { childList: true}); + + // workaround for chrome no events on blank iframe ... + setTimeout( function() { + window.addEventListener("mousedown", onContextMenu, true); + ["mousedown","click"].forEach((a) => window.addEventListener(a, function(e) { + if (e.isTrusted && e.button == 0) { + userInput = {e: e, time: Date.now()}; + if (injected) dispatchUserInput(userInput); + } + }, true)); + if (data.debug) chrome.storage.local.get(["selector-css"], function(data) { + window.document.head.appendChild(document.createElement("style")) + .textContent = data["selector-css"]; + }); + }, 1000); + + window.addEventListener("DOMContentLoaded", function() { + observer.observe(window.document.body, { subtree: true, childList: true}); + var n = handleDocument(); + _trace(">>>>>>> DOMContentLoaded n: %s match: %s query: %s iframe: %s loc: %s <<<<<<<") + (n , match, query, !!frameElement && frameElement.id, location.href); + }, true); + + window.addEventListener("message", function(e) { + if (!e.data || e.data.id != "userinput") return; + if (e.source == window.parent) { + _trace("@@@ handleMessage(userinput) @@@@")(); + var si = new MouseEvent("siminput", Object.assign({view: window}, e.data)); + userInput = {e: Object.defineProperty(si, "target", {value: window}), time: e.data.time}; + if (injected) dispatchUserInput(userInput); + } + if (e.source && e.source.parent == window && userInput) { + var iframe, iframes = [...window.document.querySelectorAll("iframe")]; + if (userInput.e.target.shadowRoot) iframes.push(...userInput.e.target.shadowRoot.querySelectorAll("iframe")); + if (iframe = iframes.find((a) => a.contentWindow == e.source)) { + var ue = userInput.e, time = userInput.time, r = iframe.getBoundingClientRect(); + var data = {id: "userinput", clientX: ue.pageX - r.left - scrollX, clientY: ue.pageY - r.top - scrollY, time}; + e.source.postMessage(data, "*"); + _trace("@@@ iframe.postMessage(userinput) @@@")(); + } + } + }, true); + + function handleDocument() + { + var nodes = handleNodesDeep(window.document.body || window.document.documentElement); + var frames = (window.document.body || window.document.documentElement).querySelectorAll(frameSelector); + if (frames.length) handleFrames(frames); + return nodes; + }; + + function handleNodes(nodes, delayed) + { + if (!injected) { + if (href.search("^https?:") == -1 && !delayed) + return window.setTimeout(function(){ handleNodes(nodes, true)}); + injected = loadScript(); + if (userInput) dispatchUserInput(userInput); + } + nodes.forEach(function(node) { + node.setAttribute("flashstopped", true); + try { node.parentNode.setAttribute("flashstopped_p", true)} catch(e){}; + if (!node.flashstopped) { + node.dispatchEvent(new Event("flashstop:bind", {bubbles: true, composed: true})); + chrome.runtime.sendMessage("count"); + _trace("match: %s query: %s")(match, query); + } + node.flashstopped = true; + }); + }; + + function handleFrames(frames) + { + if (!window.wrappedJSObject) frames.forEach(function(frame) { + if (!frame.contentWindow) return; + _trace("!!! iframe - id: %s src: %s fs: %s !!!")(frame.id, frame.src, frame.contentWindow.flashstopped); + if (!frame.contentWindow.flashstopped) { + frame.setAttribute("flashstopped", true); + frame.contentWindow.flashstopped = true; + frame.contentWindow.Function('parent.postMessage({id: "userinput"}, "*")')(); + window.setTimeout(function(){ load(frame.contentWindow)}); + }; + }); + }; + + function handleNodesDeep(node, match) + { + var nodes = match && (node.matches(selector) || node.shadowRoot) ? [node] : []; + var count = nodes.push(...node.querySelectorAll(selector)); + for (var el of nodes.filter((a) => a.shadowRoot)) { + nodes.splice(nodes.indexOf(el), 1)[0].setAttribute("flashstopped", true); + count += handleNodesDeep(el.shadowRoot) - 1; + observer.observe(el.shadowRoot, { subtree: true, childList: true}); + } + if (nodes.length) handleNodes(nodes); + return count; + }; + + function loadScript() + { + var code = `(function(data){ ${_trace} ${Handlers} ${handler} (${init})()})(${JSON.stringify(data)})`; + var script = window.document.createElement("script"); + script.textContent = code; + if (window.wrappedJSObject) + script.textContent += "\ndocument.currentScript.dispatchEvent(new Event('load'));"; + script.onload = function() { this.executed = true}; + (window.document.head || window.document.documentElement).appendChild(script); + script.remove(); + + if (window.wrappedJSObject && !script.executed) // fff + try { window.eval(code); } catch(e) {}; + + return true; + }; + // initilaizer & adapter for the injection code... + function init() + { + var handlingUserInput = false + var lastUserInput = null; + var handlers = new Handlers(TRACE); + function isHandlingUserInput() { return handlingUserInput = handlingUserInput && Date.now() - lastUserInput.time < 100 }; + + registerUserHandlers(handlers, TRACE, isHandlingUserInput, () => TRACE("setHandlingUserInput")(), () => lastUserInput, data); + registerHandlers(handlers, TRACE, isHandlingUserInput, () => TRACE("setHandlingUserInput")(), () => lastUserInput, data); + + window.addEventListener("userinput", function(e) { + handlingUserInput = true; + setTimeout(function() { handlingUserInput = false;}); + lastUserInput = {e: Object.assign({target: e.target}, e.detail.e), time: e.detail.time}; + }, true); + window.addEventListener("flashstop:bind", function onbind(e){ + var aElement = e.target, path = e.composedPath(); + if (path && path[0] != aElement) aElement = path[0]; + TRACE("onElementBinding - tag: %s id: %s loc: %s")(aElement.localName, aElement.id, window.location.href); + window.wrappedJSObject = window; + try { + handlers.apply(wrapper(aElement)); + } catch(e) { + aElement.addEventListener("playing", () => { + if (!lastUserInput || Date.now() - lastUserInput.time > 1500) aElement.pause(); + })}; + delete window.wrappedJSObject; + }, true); + + function wrapper(node) + { + var proxy = new Proxy(node, { get: function(target, prop, receiver) { + var proto = Object.getPrototypeOf(node); + var res = Reflect.get(proto, prop, node); + return typeof res == "function" && res.bind ? res.bind(node) : res; + }, set: function(target, prop, value, receiver) { + var proto = Object.getPrototypeOf(node); + return Reflect.set(proto, prop, value, Reflect.has(proto, prop) ? node : receiver); + }}); + return Object.create(proxy, {wrappedJSObject: { value: node}}); + }; + + TRACE("addEventListener - flashstop:bind")(); + }; + + function inRect(x, y, el){ var b = el.getBoundingClientRect(); return (x > b.left && x < b.right && y > b.top && y < b.bottom) ? el : null}; + function matchDeep(el, x, y, res) { + if (!el.shadowRoot) return el.matches("video, audio") ? el : null; + for (var n of [...el.shadowRoot.elementsFromPoint(x, y)].filter((a) => a.getRootNode().host == el)) + if (res = matchDeep(n, x, y)) return res; + }; + function getMediaElement(aWin, aPrev, x, y) + { + try {aWin.document} catch(e) { _trace("getMediaElement - %s")(e); return }; + var res, sx = x + aWin.mozInnerScreenX, sy = y + aWin.mozInnerScreenY, round = Math.round; + _trace("sc(%s,%s) win(%s,%s) win: %s prev: %s")(round(sx), round(sy), round(x), round(y), aWin.location.host, aPrev && aPrev.location.host); + for (var node of aWin.document.elementsFromPoint(x, y)) + if (res = (node.localName != "iframe" || node.contentWindow == aPrev) ? matchDeep(node, x, y) : + getMediaElement(node.contentWindow, null, x - node.getBoundingClientRect().left, y - node.getBoundingClientRect().top)) + return (res.localName != "iframe" && _trace("getMediaElement - success id: %s")(res.id), res); + if (res = [...aWin.document.querySelectorAll("video:not([src^='data:'])")].find((node) => inRect(x, y, node))) + return (_trace("getMediaElement - success id: %s")(res.id), res); + return (aPrev && aWin != aWin.parent && aWin.frameElement) && getMediaElement(aWin.parent, aWin, x + + aWin.frameElement.getBoundingClientRect().left, y + aWin.frameElement.getBoundingClientRect().top); + }; + + function onContextMenu(e) + { // before + if (e.isTrusted && e.button == 2){ + var media = e.target instanceof HTMLMediaElement ? e.target : getMediaElement(e.view, e.view, e.clientX, e.clientY); + var paused = media && media.paused; + if (media) { + chrome.runtime.sendMessage({ msg: "contextmenu", media: true, paused }, + function(){ if (!chrome.runtime.lastError) paused ? media.play() : media.pause(); }); + if (e.ctrlKey) + e.stopImmediatePropagation(); + } + else + chrome.runtime.sendMessage({ msg: "contextmenu", media: false}); + } + }; + }; +}; + +function dispatchUserInput({e, time, e: {view: window}}) +{ // fff! + with(e) var Obj = window.Object, data = Object.assign(new Obj, {clientX, clientY, pageX, pageY}); + var target = e.target != window && window.document.contains(e.target) ? e.target : window; + target.dispatchEvent(new CustomEvent("userinput", {detail: Object.assign(new Obj,{e: data, time})})); +}; + +// \ No newline at end of file diff --git a/AutoplayStopper/content/handler-utils.js b/AutoplayStopper/content/handler-utils.js new file mode 100644 index 0000000..7880e87 --- /dev/null +++ b/AutoplayStopper/content/handler-utils.js @@ -0,0 +1,49 @@ + +// handler-utils.js See license.txt for terms of usage and credits + +"use strict"; + +/** +* Handlers - content handlers manager... +* ---------------------------------------- +* @bYO! +*/ + +function Handlers(TRACE){ + + var _handlers = [], _mapRemoved = new Map(); + return { + unload: function unload(){ + for (var win of _mapRemoved.keys()) { + win.removeEventListener("unload", onUnload, false); + TRACE("Handlers.unload - remove wnd loc: %s")(win.location); + }; + _handlers = []; + _mapRemoved = new Map(); + }, + add: function add(handler) { _handlers.push(handler); }, + remove: function remove(win, handler) { + if (!_mapRemoved.has(win)){ + win.addEventListener("unload", onUnload, false); + _mapRemoved.set(win, []); + } + _mapRemoved.get(win).push(handler); + TRACE("Handlers.remove - handler: %s")(handler.name); + }, + apply: function apply(element){ + var removed = _mapRemoved.get(element.ownerDocument.defaultView); + for (var handler, i = 0; i < _handlers.length; i++) + if ((handler = _handlers[i]) && (!removed || removed.indexOf(handler) == -1)) + try { + if (handler(element)) break; + } catch (e) { TRACE("!!!!!!!!! handler: %s Exception: %s !!!!!!!!!")(handler.name, e); }; + }, + get length() { return _handlers.length; } + }; + + function onUnload(e){ + var win = e.target.defaultView; + TRACE("Handlers.onUnload - remove wnd has: %s loc: %s")(_mapRemoved.has(win), win.location); + _mapRemoved.delete(win); + } +}; diff --git a/AutoplayStopper/content/navigator.js b/AutoplayStopper/content/navigator.js new file mode 100644 index 0000000..48fa38c --- /dev/null +++ b/AutoplayStopper/content/navigator.js @@ -0,0 +1,39 @@ + + +"use strict"; + +(function() { + + +// setTimeout(function() { + var script = document.createElement("script"); + script.textContent = `(${setNavigator})()`; + (document.head || document.documentElement || document).appendChild(script); + script.remove(); +// }); + + function setNavigator(nav = { plugins: navigator.plugins, mimeTypes: navigator.mimeTypes}) { + + console.log("setNavigator - uri: " + location); + + var plugins = Object.assign(Object.create(PluginArray.prototype), [...nav.plugins]); + Object.defineProperties(plugins, { item: {value: (a) => plugins[a]}, namedItem: {value: (a) => plugins[a]}, + refresh: {value: () => { nav.plugins.refresh(); setNavigator(nav)}}, + length: { writable: true, value: nav.plugins.length}}); + for (var a of plugins) { Object.defineProperty(plugins, a.name, {configurable: true, value: a}); }; + [].splice.call(plugins, [].findIndex.call(plugins, (a) => a.name == "Shockwave Flash"), 1); + delete plugins["Shockwave Flash"]; + Object.defineProperty(navigator, "plugins", {configurable: true, value: plugins}); + + var mimetypes = Object.assign(Object.create(MimeTypeArray.prototype), [...nav.mimeTypes]); + Object.defineProperties(mimetypes, { item: {value: (a) => mimetypes[a]}, namedItem: {value: (a) => mimetypes[a]}, + refresh: {value: () => { plugins.refresh()}}, length: { writable: true, value: nav.mimeTypes.length}}); + for (var a of mimetypes) { Object.defineProperty(mimetypes, a.type, {configurable: true, value: a}); }; + [].forEach.call(nav.plugins["Shockwave Flash"] || [], ({type}) => + delete mimetypes[[].splice.call(mimetypes, [].findIndex.call(mimetypes, (a) => a.type == type), 1)[0].type]); + Object.defineProperty(navigator, "mimeTypes", {configurable: true, value: mimetypes}); + }; +})(); + +// + diff --git a/AutoplayStopper/devtools.js b/AutoplayStopper/devtools.js new file mode 100644 index 0000000..1d09aea --- /dev/null +++ b/AutoplayStopper/devtools.js @@ -0,0 +1,144 @@ + + +"use strict"; + +const DevTools = new function() { + + const sArea = "local"; + const sKeys = ["debug", "devtools", "codefile", "ucodefile", "codefilename", "ucodefilename", "disableOverwrite", "selector-css", "uhandler", "handler"]; + + var storage = new Storage(sKeys, sArea); + + storage.ready.then(function() { + + if (!storage.data.devtools) + return; + // devtools page - create panel ... + if (location.search == "?view=page") + return chrome.devtools.panels.create("AutoplayStopper", "/icons/icon48.png", "/skin/devtools.html?view=panel"); + + // devtools panel ... + var fileinput = Object.assign(document.createElement("input"), {type: "file", hidden: true}); + document.body.appendChild(fileinput); + document.readyState == "complete" ? load() : addEventListener("load", load); + + function load() { + + i18n.process(document); + document.getElementById("ver").textContent = chrome.runtime.getManifest().version; + setCheckbox("overwrite", "disableOverwrite"); + setCheckbox("debug"); + setSelector(); + setup(""); + setup("u"); + }; + + function setSelector() + { + var selector = document.getElementById("selector"); + var apply = document.getElementById("apply"); + selector.value = storage.data["selector-css"]; + selector.oninput = function() { apply.disabled = selector.value == storage.data["selector-css"]; }; + apply.onclick = function() { + storage.data["selector-css"] = selector.value; + storage.commit(["selector-css"]); + apply.disabled = true; + }; + document.getElementById("reset").onclick = function() { + var url = chrome.extension.getURL("script/selector.css"); + chrome.runtime.sendMessage({ msg: "load", script: "selector-css", file: url }); + }; + storage.addChangeListener(function(changes){ + if (changes.includes("selector-css")) selector.value = storage.data["selector-css"]; + apply.disabled = true; + }); + }; + + function setup(pfx) + { + var file = pfx + "codefile"; + var filename = file + "name"; + + document.getElementById(pfx + "path-input").textContent = storage.data[file]; + document.getElementById(pfx + "filename").textContent = storage.data[filename]; + updateState(); + + document.getElementById(pfx + "load-button").onclick = function() { + var url = storage.data[file] || chrome.extension.getURL("script/") + (pfx && "user") + "handler.js"; + chrome.runtime.sendMessage({ msg: "load", script: pfx + "handler", file: url}); + }; + document.getElementById(pfx + "export-button").onclick = function() { + var a = document.getElementById("export-link"); + a.download = (pfx && "user") + "handler.js"; + a.href = "data:application/javascript," + encodeURIComponent(storage.data[pfx + "handler"]); + a.click(); + }; + document.getElementById(pfx + "edit-button").onclick = function() { + var open = chrome.devtools.panels.openResource || function(){}; + open(getURL(file), null, function(res){ + if (res.isError && res.code == "E_NOTFOUND") + alert(chrome.i18n.getMessage("fileNotFound")); + }); + }; + var committed = chrome.devtools && chrome.devtools.inspectedWindow.onResourceContentCommitted; + committed && committed.addListener(function(resource){ + if (resource.url == getURL(file)) + resource.getContent(function(content, encoding) { + chrome.devtools.inspectedWindow.eval(content, function(result, isException) { + if (isException) + alert(chrome.i18n.getMessage("evalError") + isException.value); + }); + }); + }); + document.getElementById(pfx + "path-input").oninput = updateState; + document.getElementById(pfx + "path-button").onclick = function(e) { + storage.data[filename] = document.getElementById(pfx + "filename").textContent = ""; + storage.data[file] = document.getElementById(pfx + "path-input").textContent; + storage.commit([file, filename]); + updateState(); + }; + document.getElementById(pfx + "select-button").onclick = function(e) { + fileinput.onchange = function() { + if (!chrome.extension.getBackgroundPage) { // fff... + var data = {msg: "select", url: URL.createObjectURL(this.files[0])}; + chrome.runtime.sendMessage(data, function(res) { updateSelected(res.url, res.name); }); + } + else { + var url = chrome.extension.getBackgroundPage().URL.createObjectURL(this.files[0]); + updateSelected(url, this.files[0].name); + } + }; + fileinput.click(); + }; + + function updateSelected(url, name) { + storage.data[file] = document.getElementById(pfx + "path-input").textContent = url; + storage.data[filename] = document.getElementById(pfx + "filename").textContent = name; + storage.commit([file, filename]); + updateState(); + }; + + function updateState() + { + var a = document.getElementById(pfx + "path-input").textContent == (storage.data[file] || ""); + document.getElementById(pfx + "path-button").disabled = a; + document.getElementById(pfx + "load-button").disabled = !a; + document.getElementById(pfx + "edit-button").disabled = !a || !storage.data[file]; + }; + }; + + function getURL(file) { try { return new URL(storage.data[file]).href } catch(e) {}; }; + + function setCheckbox(id, key = id) + { + var node = document.getElementById(id); + node.checked = !!storage.data[key]; + node.onchange = function() { + storage.data[key] = this.checked; + storage.commit([key]); + }; + }; + }); +}; + +// \ No newline at end of file diff --git a/AutoplayStopper/icons/icon32.png b/AutoplayStopper/icons/icon32.png new file mode 100644 index 0000000..f91dbab Binary files /dev/null and b/AutoplayStopper/icons/icon32.png differ diff --git a/AutoplayStopper/icons/icon32d.png b/AutoplayStopper/icons/icon32d.png new file mode 100644 index 0000000..fdeb585 Binary files /dev/null and b/AutoplayStopper/icons/icon32d.png differ diff --git a/AutoplayStopper/icons/icon48.png b/AutoplayStopper/icons/icon48.png new file mode 100644 index 0000000..711d8f5 Binary files /dev/null and b/AutoplayStopper/icons/icon48.png differ diff --git a/AutoplayStopper/manifest.json b/AutoplayStopper/manifest.json new file mode 100644 index 0000000..42c3d07 --- /dev/null +++ b/AutoplayStopper/manifest.json @@ -0,0 +1,53 @@ +{ + "description": "[MODIFIED BY ME] Stops video autoplay gracefully.", + "manifest_version": 2, + "name": "AutoplayStopper", + "version": "1.8.7", + + "default_locale": "en", + + "icons": { + "32": "icons/icon32.png", + "48": "icons/icon48.png" + }, + + "permissions": [ + "http://*/*", "https://*/*", + "tabs", + "contextMenus", + "storage", + "webNavigation" + ], + + "background": { + "scripts": ["utils.js", "background.js"] + }, + + "browser_action": { + "default_icon": { + "32": "icons/icon32.png" + }, + "default_title": "AutoplayStopper", + "default_popup": "skin/popup.html", + "browser_style": true + }, + + "content_scripts": [ + { + "run_at": "document_start", + "all_frames": true, + "match_about_blank": true, + "matches": ["http://*/*", "https://*/*"], + "js": ["content/handler-utils.js", "content/content.js"] + }], + + "options_ui": { + "chrome_style": true, + "browser_style": true, + "page": "skin/options.html" + }, + + "devtools_page": "skin/devtools.html?view=page", + + "optional_permissions": ["file://*/*"] +} diff --git a/AutoplayStopper/options.js b/AutoplayStopper/options.js new file mode 100644 index 0000000..516bb7a --- /dev/null +++ b/AutoplayStopper/options.js @@ -0,0 +1,292 @@ + + +"use strict"; + +const Options = new function() { + + const {i18n, storage, permissions: perms} = chrome.extension.getBackgroundPage().Background; + const permsAutoplayKey = perms.key("autoplay"); + const permsAutoplayDefaultKey = perms.defaultKey("autoplay"); + const permsFlashKey = perms.key("flash"); + const permsFlashDefaultKey = perms.defaultKey("flash"); + const data = { flash: { type: "flash", dirty: false}, autoplay: { type: "autoplay", dirty: false}}; + const { autoplay, flash } = data; + + var tabId = null; + var selected = null; + +// chrome.tabs.getCurrent(function(tab) { tabId = tab.id; }); // chrome workaround ... + chrome.tabs.query({ active: true, currentWindow: true}, function(tabs) { tabId = tabs[0].id; }); + chrome.tabs.onActivated.addListener(function(info){ if (tabId == info.tabId) postActive(); }); + addEventListener("load", init); + addEventListener("hashchange", function() { updateState(location.hash.slice(1)); }); + + function init() + { + i18n.process(document); + var listener = storage.addChangeListener(function(changes){ + if (changes.indexOf(permsAutoplayKey) != -1) { + autoplay.exceptions.init(perms.entries("autoplay")); + autoplay.dirty = false; + updateApply(); + } + if(changes.indexOf(permsAutoplayDefaultKey) != -1) { + document.getElementById("autoplay-default").value = perms.default("autoplay"); + } + if (changes.indexOf(permsFlashKey) != -1) { + flash.exceptions.init(perms.entries("flash")); + flash.dirty = false; + updateApply(); + } + if(changes.indexOf(permsFlashDefaultKey) != -1) { + document.getElementById("flash-default").value = perms.default("flash"); + } + }); + addEventListener("unload", function(){ storage.removeChangeListener(listener); }); + + var manifest = chrome.runtime.getManifest(); + document.getElementById("extension-version").appendChild(document.createTextNode(manifest.version)); + document.getElementById("autoplay-default").value = perms.default("autoplay"); + document.getElementById("flash-default").value = perms.default("flash"); + document.getElementById("devtools").checked = storage.data.devtools; + autoplay.exceptions = new ExceptionsList(document.getElementById("autoplay-list")); + autoplay.exceptions.init(perms.entries("autoplay")); + document.getElementById("autoplay-default").addEventListener("change", function(e) { + perms.setDefault("autoplay", e.target.value); + storage.commit([permsAutoplayDefaultKey]); + }); + flash.exceptions = new ExceptionsList(document.getElementById("flash-list")); + flash.exceptions.init(perms.entries("flash")); + document.getElementById("flash-default").addEventListener("change", function(e) { + perms.setDefault("flash", e.target.value); + storage.commit([permsFlashDefaultKey]); + }); + document.querySelectorAll(".exceptions-list-button").forEach(function(button) { + button.onclick = function(e) { location.hash = `${button.getAttribute("contenttype")}`; }; + }); + autoplay.exceptions.addDirtyListener(function(d) { autoplay.dirty = d; updateApply(); }); + flash.exceptions.addDirtyListener(function(d) { flash.dirty = d; updateApply(); }); + document.querySelector(".close-button").onclick = function() { location.hash = ""; }; + document.getElementById("exceptions-clear").onclick = function() { selected.exceptions.clear(); }; + document.getElementById("exceptions-apply").onclick = apply; + document.getElementById("exceptions-confirm").onclick = function() { apply(); location.hash = ""; }; + document.getElementById("devtools").onclick = function(e) { + storage.data.devtools = this.checked; + storage.commit(["devtools"]); + }; + if (location.hash) updateState(location.hash.slice(1)); + postActive(); + }; + + function postActive() { chrome.extension.getBackgroundPage().postMessage("optionsPageActive", "*"); }; + function updateApply() { document.getElementById("exceptions-apply").disabled = !selected || !selected.dirty; }; + + function apply() + { + if (selected.dirty) { + perms.clear(selected.type); + for (var [url, perm] of selected.exceptions.entries()) { perms.set(selected.type, url, perm); }; + storage.commit([perms.key(selected.type)], function(err){ + if (err) { selected.dirty = true; }; + updateApply(); + }); + selected.dirty = false; + } + }; + + function updateState(state) + { +// console.log(`updateState(${state})`); + if (state) { + document.querySelector("div[contenttype]:not([hidden])").hidden = true; + document.querySelector("h2[contenttype]:not([hidden])").hidden = true; + document.querySelector(`div[contenttype=${state}]`).hidden = false; + document.querySelector(`h2[contenttype=${state}]`).hidden = false; + var overlay = document.querySelector(".overlay"); + overlay.hidden = false; + setTimeout(function() { overlay.classList.remove("transparent"); }, 20); + selected = data[state]; + updateApply(); + } + else { + var overlay = document.querySelector(".overlay"); + overlay.classList.add("transparent"); + setTimeout(function() { overlay.hidden = true; }, 500); + } + } +}; + +const ExceptionsList = function ExceptionsList(list){ + + var itemid = 0; + var baseitem = list.querySelector("#listitem"); + var inputitem = list.querySelector("#inputitem"); + var selected = list.querySelector("[selected]"); + var listeners = []; + var dirty = false; + + baseitem.remove(); + list.addEventListener("mousedown", handleMousedown.bind(this)); + list.addEventListener("click", handleClick.bind(this)); + list.addEventListener("keydown", handleKeyDown.bind(this)); + list.addEventListener("change", handleChange.bind(this)); + list.addEventListener("focus", function(e) { + if (!e.target.classList.contains("row-delete-button")) { + list.setAttribute("has-element-focus", "hasElementFocus"); + handleSelection(e.target.closest("[role=listitem]")); + if (e.target.getAttribute("displaymode") == "static") e.target.nextSibling.focus(); + } + }, true); + list.addEventListener("blur", function(e) { + if (!list.contains(e.relatedTarget) && !e.target.classList.contains("row-delete-button")) { + list.removeAttribute("has-element-focus"); + handleSelection(); + if (!e.relatedTarget) list.parentNode.closest("[tabindex]").focus(); + } + }, true); + + return { + init: function init(entries) { + this.clear(); + entries.sort(function(a, b) { return a[0].localeCompare(b[0]); }); + for (var [key, type] of entries) { appendItem(key, type); }; + dirty = false; + }, + entries: function * entries() + { + for (var node of list.children) + if (node.id.search("listitem") == 0 && node != inputitem) + yield [node.querySelector("#perms-host").textContent, parseInt(node.querySelector("#perms-type-select").value)]; + }, + clear: function clear() + { + for (var node of [].slice.apply(list.children)) + if (node.id.search("listitem") == 0 && node != inputitem) node.remove(); + inputitem.querySelector("#perms-host-input").focus(); + fireDirty(true); + }, + get dirty() { return dirty; }, + addDirtyListener: function(listener) { listeners.push(listener); }, + removeDirtyListener: function(listener) { var idx = listeners.indexOf(listener); if (idx != -1) listeners.splice(idx); } + }; + + function appendItem(host, type, scroll) + { + var item = baseitem.cloneNode(true); + item.id = item.id + "-" + (++itemid); + item.querySelector("#perms-host-input").value = item.querySelector("#perms-host").textContent = host; + var select = item.querySelector("#perms-type-select"); + select.value = type; + if (type != 0) item.querySelector("#perms-type").textContent = select[select.selectedIndex].text; + list.insertBefore(item, inputitem); + if (scroll) scrollToBottom(inputitem); + }; + + function scrollToBottom(node) + { + if (node.clientHeight < node.scrollHeight) + return node.scrollTop = node.scrollHeight - node.clientHeight; + if (node.parentNode) scrollToBottom(node.parentNode); + }; + + function handleMousedown(e) + { + var target = e.target; + var item = target.closest("[role=listitem]"); + if (target.classList.contains("row-delete-button")) return; + if (target.id == "perms-host") { + var x = e.clientX, y = e.clientY, offset = document.caretPositionFromPoint ? + document.caretPositionFromPoint(x, y).offset : document.caretRangeFromPoint(x, y).startOffset; + var input = item.querySelector("#perms-host-input"); + var inside = x < inputitem.querySelector("#perms-host-input").getBoundingClientRect().right; + input.selectionStart = input.selectionEnd = inside ? 0 : input.value.length; + setTimeout(function() { input.selectionStart = input.selectionEnd = offset; }, 25); + } + if (item && !target.hasAttribute("tabindex") && !item.hasAttribute("editing")) { + item.querySelector("[id^=perms-host]").focus(); + e.preventDefault(); + }; + }; + + function handleSelection(item) + { + if (selected && item != selected) { + selected.removeAttribute("lead"); + selected.removeAttribute("editing"); + if (item) selected.removeAttribute("selected"); + if (selected == inputitem) handleInput(!item); + selected.querySelectorAll("[id^=perms]").forEach(function(a) { a.tabIndex = item ? -1 : 0; }); + } + if (item && !item.hasAttribute("editing")) { + item.setAttribute("selected", "selected"); + item.setAttribute("lead", "lead"); + item.setAttribute("editing", ""); + item.querySelectorAll("[id^=perms]") + .forEach(function(a) { a.tabIndex = a.getAttribute("displaymode") == "static" ? -1 : 0; }); + selected = item; + } + }; + + function handleClick(e) + { + var target = e.target; + if (target.classList.contains("row-delete-button")) { + var item = target.closest("[role=listitem]"); + if (item == selected) handleSelection(inputitem); + item.remove(); + list.focus({preventScroll: true}); + fireDirty(true); + return; + }; + }; + + function handleKeyDown(e) + { + if (selected && selected != inputitem && e.target.id != "perms-host-input" && e.keyCode == 46) + selected.querySelector(".row-delete-button").click(); + if (selected && e.key == "Enter") selected == inputitem ? handleInput(true) : list.focus({preventScroll: true}); + if (selected && e.key == "ArrowUp" && e.target.id != "perms-type-select" && selected != list.querySelector("[role=listitem]")) + selected.previousSibling.querySelector("[id^=perms-host]").focus(); + if (selected && e.key == "ArrowDown" && e.target.id != "perms-type-select" && selected != inputitem) + selected.nextSibling.querySelector("[id^=perms-host]").focus(); + }; + + function handleChange(e) + { + var target = e.target; + var item = target.closest("[role=listitem]"); + if (item != inputitem) { + if (target.id == "perms-type-select") { + item.querySelector("#perms-type").textContent = + target.value != 0 ? target[target.selectedIndex].text : ""; + fireDirty(true); + } + if (target.id == "perms-host-input"){ + try { + var url = new URL(target.value); + target.value = url.origin; + item.querySelector("#perms-host").textContent = target.value; + fireDirty(true); + } + catch (e) { target.value = item.querySelector("#perms-host").textContent; }; + } + } + }; + + function handleInput(scroll) + { + try { + var target = inputitem.querySelector("#perms-host-input") + var url = new URL(target.value); + target.value = url.origin; + appendItem(target.value, inputitem.querySelector("#perms-type-select").value, scroll); + target.value = null; + fireDirty(true); + } + catch (e) {}; + }; + + function fireDirty(d) { dirty = d; for (var listener of listeners) listener(d); }; +}; + +// \ No newline at end of file diff --git a/AutoplayStopper/popup.js b/AutoplayStopper/popup.js new file mode 100644 index 0000000..eef9466 --- /dev/null +++ b/AutoplayStopper/popup.js @@ -0,0 +1,94 @@ + + +"use strict"; + +const Popup = new function(){ + + const start = Date.now(); + const {i18n, storage, permissions: perms, openOptionsPage} = chrome.extension.getBackgroundPage().Background; + + while (Date.now() - start < 50); + setTimeout(function() { + while (Date.now() - start < 150); + addEventListener("unload", function() { + if (Date.now() - start < 250) { + storage.data.disabled = !storage.data.disabled; + storage.commit(["disabled"]); + } + }); + }); + + const permsAutoplayKey = perms.key("autoplay"); + const permsFlashKey = perms.key("flash"); + + var url = null; + var ready = new Promise( function(resolve) { + chrome.tabs.query({ active: true, currentWindow: true}, function(tabs) { + url = new URL(tabs[0].url); + resolve(); + }) + }); + + addEventListener("load", function() { ready.then(init); }); + + function init() + { + i18n.process(document); + var permission = perms.testPermission("autoplay", url); + var permissionFlash = perms.testPermission("flash", url); + initItem( document.getElementById("allow_site"), !!permission.origin && permission != perms.ACCESS_SESSION && permission, [url.origin]); + initItem( document.getElementById("allow_session"), !!permission.origin && permission == perms.ACCESS_SESSION, [url.origin]); + initItem( document.getElementById("disable_site"), !!permissionFlash.origin && permissionFlash, [url.origin]); + initItem( document.getElementById("allow_all"), storage.data.disabled); + initItem( document.getElementById("settings")); + document.getElementById("popupmenu") + .classList.toggle("restricted", url.protocol.search("^http") == -1 || !!storage.data.disabled); + + window.onclick = function(e) { + + switch (e.target.id) { + case "settings": + openOptionsPage(); + close(); + break; + case "allow_site": + if ( [undefined, perms.UNKNOWN_ACTION].indexOf(perms.get("autoplay", url)) != -1 + && permission != perms.default("autoplay")) { openOptionsPage("#autoplay"); close(); break; }; + perms.set("autoplay", url, (permission == perms.ALLOW_ACTION) ? perms.DENY_ACTION : perms.ALLOW_ACTION); + storage.commit([permsAutoplayKey]); + init(); + break; + case "allow_session": + if ( [undefined, perms.UNKNOWN_ACTION].indexOf(perms.get("autoplay", url)) != -1 + && permission != perms.default("autoplay")) { openOptionsPage("#autoplay"); close(); break; }; + (permission == perms.ACCESS_SESSION) ? perms.remove("autoplay", url) : + perms.set("autoplay", url, perms.ACCESS_SESSION); + storage.commit([permsAutoplayKey]); + init(); + break; + case "disable_site": + if ( [undefined, perms.UNKNOWN_ACTION].indexOf(perms.get("flash", url)) != -1 + && permissionFlash != perms.default("flash")) { openOptionsPage("#flash"); close(); break; }; + perms.set("flash", url, (permissionFlash == perms.ALLOW_ACTION) ? perms.DENY_ACTION : perms.ALLOW_ACTION); + storage.commit([permsFlashKey]); + init(); + break; + case "allow_all": + storage.data.disabled = !storage.data.disabled; + storage.commit(["disabled"]); + init(); + break; + } + }; + }; + + function initItem(item, check, args) + { + if (check !== undefined) check ? item.setAttribute("checked", check) : item.removeAttribute("checked"); + if (args) { var i = 0; item.textContent = item.textContent.replace(/%S/g, function () { return args[i++]; }); }; + }; +}; + + + +// diff --git a/AutoplayStopper/script/handler.js b/AutoplayStopper/script/handler.js new file mode 100644 index 0000000..3ae7c77 --- /dev/null +++ b/AutoplayStopper/script/handler.js @@ -0,0 +1,289 @@ +"use strict"; + +function registerHandlers(handlers, TRACE, isHandlingUserInput, setHandlingUserInput, lastUserInput, data = {}) +{ + var dummyid = Math.floor(Math.random() * 100); + + function testUserInput(el, delay) + { + var win = el.ownerDocument.defaultView, _last = lastUserInput(win), e = _last && _last.e; + if (!e || Date.now() - _last.time > delay) return false; + var px = e.pageX - win.scrollX, py = win.scrollY ? e.pageY - win.scrollY : e.clientY, r = el.getBoundingClientRect(), {width ,height} = r; + for (var i = 0; i < 5 && el.parentNode && (r.top + win.scrollY < 0 || r.left + win.scrollX < 0 || r.width < 10 || r.height < 10); i++) r = (el = el.parentNode).getBoundingClientRect(); + r.width = Math.max(r.width, width), r.height = Math.max(r.height, height); + return px >= r.left && px <= r.right && py >= r.top && py <= r.bottom; + }; + // htmlmedia + handlers.add(function handleHtmlMedia(aElement){ + + const simEvents = {play: 0, playing: 50, pause: 350}, eventTypes = Object.keys(simEvents), props = ["preload","autoplay","poster","setAttribute","removeAttribute"]; + const document = aElement.ownerDocument, window = document.defaultView; + if (aElement.localName == "video" || aElement.localName == "audio") + { + aElement.id || (aElement.id = "dummyid" + (++dummyid % 100)); + TRACE('handleElement html5 media - tag: %s id: %s autoplay: %s preload: "%s" poster: %s paused: %s')(aElement.localName, aElement.id, aElement.autoplay, aElement.preload, !!aElement.poster, aElement.paused); + if (aElement.wrappedJSObject.play !== window.wrappedJSObject.HTMLMediaElement.prototype.play) TRACE('handleElement html5 media - %s.play() != HTMLMediaElement.play !!!')(aElement.id); + // Yendifplayer .autoplay = 1 => ... + var autoplay = aElement.autoplay || !aElement.paused, count = 0, released = false; + Object.defineProperties(aElement.wrappedJSObject, { preload: {configurable: true, set: (a) => TRACE('%s.preload = "%s"')(aElement.id, a)/* || (aElement.preload = "metadata")*/, get: () => aElement.preload} + , autoplay: {configurable: true, set: (a) => TRACE("%s.autoplay = %s")(aElement.id, a) || (a && aElement.wrappedJSObject.play()), get: () => aElement.autoplay}}); + aElement.wrappedJSObject.setAttribute = (a, b) => (props.indexOf(a) != -1) ? TRACE("%s.setAttribute(%s, %s)")(aElement.id, a, b) || (aElement.wrappedJSObject[a] = b): aElement.setAttribute(a, b); + aElement.wrappedJSObject.removeAttribute = (a) => (props.indexOf(a) != -1) ? TRACE("%s.removeAttribute(%s)")(aElement.id, a) : aElement.removeAttribute(a); + + function simPlay(e) { + e && aElement.removeEventListener(e.type, simPlay); + if (aElement.simPlay !== false && aElement.paused) + eventTypes.forEach((key) => window.setTimeout(() => !released && (TRACE("simPlay - %s.%s")(aElement.id, key) || aElement.dispatchEvent(new window.Event(key))), simEvents[key])); + }; + + aElement.wrappedJSObject.play = () => { + var force = aElement.allowPlay || testUserInput(aElement, 5000); + var userInput = isHandlingUserInput(window); + TRACE("%s.play(%s) - force: %s state: %s user: %s")(aElement.id, count, !!force, aElement.readyState, userInput); + if (!userInput && !force) { + count++ == 0 && (aElement.preload = "metadata") && (aElement.simPlay || aElement.readyState >= 2 ? simPlay() : aElement.addEventListener("loadeddata", simPlay)); + return count < 5 ? Promise.reject(new window.DOMException("The play method is not allowed by the user agent.", "NotAllowedError")) : simPlay() || Promise.resolve(); + } + released = !data.strict && delete aElement.wrappedJSObject.play; // jwplayer: zapiks.fr rottentomatoes baeblemusic + force && !userInput && setHandlingUserInput(window); + return aElement.play(); + }; + window.wrappedJSObject.Object.defineProperty(aElement.wrappedJSObject.play, "toString", window.Object.assign(window.Object(), {value: () => "[native code]"})); + aElement.autoplay = false; + aElement.preload != "none" && (aElement.preload = "metadata"); // mediaelement.js + !data.strict && aElement.addEventListener("play", function cleanup(e){ e.isTrusted && !aElement.paused && (aElement.removeEventListener("play", cleanup), aElement.muted = false, + TRACE("%s cleanup...")(aElement.id), props.forEach((prop) => delete aElement.wrappedJSObject[prop]), delete aElement.wrappedJSObject.play)}); + autoplay && aElement.wrappedJSObject.play(); // flowplayer + !aElement.paused ? !released && aElement.pause() : window.setTimeout(() => !released && aElement.pause(), 0); // dbtv.no html5box + }; + }); + // simplay + const hosts = [{host: "cnn.com$", val: true}, {host: "twitch.tv$"}, {host: "yastatic.net$"}, + {host: "bbc.(com|co.uk)$"}, {host: "(yahoo|yimg).com$", class: "html5-video", val: true}, {class: "\\b(video-js|vjs-tech)\\b"}]; + handlers.add(function handleSimplay(aElement){ + if (aElement.localName == "video") + for (var a of hosts) if (aElement.ownerDocument.location.hostname.match(a.host) && aElement.className.match(a.class)) aElement.simPlay = !!a.val; + }); + // YouTube + handlers.add(function handleYouTube(aElement){ + + const document = aElement.ownerDocument, window = document.defaultView, jsWin = window.wrappedJSObject; + if (document.location.hostname.search("\.youtube(-nocookie)?\.com$") != -1) + { + TRACE("handleElement ytplayer - tag: %s player: %s")(aElement.localName, aElement.id); + if (document.location.search.search("feature=youtube-anywhere-player") == -1) + { + function stopPlayer(ytplayer){ + if (testUserInput(ytplayer, 5000)) return !TRACE("ytplayer released...")(); + var vid = ytplayer.getVideoData && ytplayer.getVideoData().video_id; + TRACE("ytplayer stopPlayer(%s) - videoId: %s t: %s")(ytplayer.id || "", vid, ytplayer.getCurrentTime()); + ytplayer.cueVideoById(vid, ytplayer.getCurrentTime()); + var nop = false, _seekTo = ytplayer.seekTo, _playVideo = ytplayer.playVideo; + ytplayer.playVideo = function doNothing() { nop || (nop = testUserInput(ytplayer, 1000)) ? _playVideo.apply(this) : TRACE("@@@@@@ ytplayer doNothing @@@@@")()}; + ytplayer.seekTo = function seekTo(tm) { nop || (nop = testUserInput(ytplayer, 1000)) ? _seekTo.apply(this, arguments) : ytplayer.cueVideoById(vid, tm), TRACE("ytplayer seekTo(%s)")(tm); }; + function onstate(a) { if (a == 1) { nop = true; ytplayer.playVideo = _playVideo; ytplayer.seekTo = _seekTo; TRACE("ytplayer - removed doNothing")(); ytplayer.removeEventListener("onStateChange", onstate); }}; + ytplayer.addEventListener("onStateChange", onstate); + } + if (aElement.localName == "video" && aElement.parentNode.classList.contains("html5-video-container")) { + if (!data.strict && aElement.parentNode.parentNode.id == "movie_player") + window.setTimeout(() => aElement.allowPlay = true, document.location.search.search("feature=youtube-anywhere-player") == -1 ? 5000 : 0); + aElement.simPlay = false; + if (aElement.parentNode.parentNode.classList.contains("html5-video-player")) + window.setTimeout(() => stopPlayer(aElement.wrappedJSObject.parentNode.parentNode)); + } + } + return true; + } + }); + // googleads + handlers.add(function handleGoogleAds(aElement){ + const document = aElement.ownerDocument, window = document.defaultView; + var host = aElement.getRootNode().host; + if (host && host.localName == "lima-video") { + TRACE("handleElement googleads - tag: %s")(aElement.localName); + window.addEventListener("visibilitychange", (e) => e.stopPropagation(), true); + aElement.wrappedJSObject.addEventListener("play", function(){ if (!testUserInput(this, 100)) this.pause()}, {once: true}); + return true; + } + }); + // ifunny.co + handlers.add(function handleIFunny(aElement){ + const document = aElement.ownerDocument, window = document.defaultView; + if (document.location.hostname.match("ifunny.co$") && aElement.paused) { + (isHandlingUserInput(window) || testUserInput(aElement.parentNode, 1000)) ? aElement.play() : aElement.src = ""; + return true; + } + }); + // msn.com + handlers.add(function handleMsn(aElement){ + + const document = aElement.ownerDocument, window = document.defaultView, jsWin = window.wrappedJSObject; + if (document.location.hostname.search(/\.msn\.com$/) != -1) + { + var obj = aElement.wrappedJSObject; + if (aElement.className.indexOf("vxFlashPlayer") != -1 && aElement.localName == "embed"){ + obj.MsnVideoCallback = function(msg) { msg == "playVideo" ? delete obj.MsnVideoCallback : obj.__proto__.MsnVideoCallback.apply(obj, arguments)}; + var flashvars = (aElement.getAttribute("flashvars") || "").replace(/(&ap=)true/gi, "$1false") + aElement.setAttribute("flashvars", flashvars); + TRACE("handleElement msnplayer - tag: %s id: %s ")(aElement.localName, aElement.id); + return true; + } + } + }); + // BBC + handlers.add(function handleBBC(aElement){ + + const document = aElement.ownerDocument, window = document.defaultView, jsWin = window.wrappedJSObject; + if (jsWin.embeddedMedia && jsWin.embeddedMedia.players) + for (var player of jsWin.embeddedMedia.players || []) if (player._swf && player._swf.id == aElement.id) { + TRACE("handleElement BBC - embeddedMedia.players id: %s autoplay: %s")(player._swf.id, player._settings.autoplay); + Object.defineProperty(player._settings, "autoplay", {get: () => false}); + player.setData = (info) => ( info.data && (info.data.autoPlayFirstItem = false), player.__proto__.setData.call(player, info)); + window.setTimeout(function(){ player._settings.autoplay = true; handlers.remove(window, handleBBC); }, 2500); + return true; + }; + }); + // metacafe + handlers.add(function handleMetacafe(aElement){ + + if (aElement.localName == "object" && (aElement.data || "").search(/s.mcstatic.com\/Flash\/.*\.swf/) != -1) + { + var param = aElement.querySelector("#" + aElement.id + ">param[name=flashvars]"); + param.value = param.value.replace(/&beacons=.*(?=&)/,""); + var id = param.value.match(/itemID=([^&]*)(?=&)/)[1]; + var data = aElement.data; + aElement.data = "http://www.metacafe.com/fplayer/" + id + "/.swf"; + function onClick(aEvent){ + aEvent.stopImmediatePropagation(); + aElement.removeEventListener(aEvent.type, onClick, true); + aElement.data = data; + } + aElement.addEventListener("mouseup", onClick, true); + TRACE("handleElement metacafe - data: %s")(aElement.data); + return true; + } + }); + // jwplayer + handlers.add(function handleJWPlayer(aElement){ // return; + + const document = aElement.ownerDocument, window = document.defaultView, jsWin = window.wrappedJSObject; + aElement.id || (aElement.id = "dummyid" + (++dummyid % 100)); + if (jsWin.jwplayer && !aElement.querySelector("[id='" + aElement.id + "']>param[name=flashvars]")) + { + if (testUserInput(aElement, 5000)) return !TRACE("jwplayer released...")(); + function closest(node, selector) { while(node && !node.matches(selector)) node = node.parentElement; return node}; + var res, ar, player = jsWin.jwplayer(aElement).config ? + jsWin.jwplayer(aElement) : (res = closest(aElement, ".jwplayer")) ? jsWin.jwplayer(res) : null; + + if (player && (player.config || (player.config = player.getConfig())) && player.config.autostart != false) + { + TRACE("handleElement jwplayer setup - id: %s autostart: %s")(player.id, player.config.autostart); + + player.config.autoStart = player.config.autostart = false; + (ar = player.config.aspectratio) && ar.indexOf("%") != -1 && (player.config.aspectratio = "100:" + ar.slice(0,-1)); + aElement.localName == "video" && window.setTimeout(() => aElement.src = ""); + player.setup(player.config); + } + TRACE("handleElement jwplayer - id: %s config: %s")((player && player.id), !!(player && player.config)); + return player && player.config; + } + }); + // flowplayer + handlers.add(function handleFlowplayer(aElement){ //return; + + aElement.id || (aElement.id = "dummyid" + (++dummyid % 100)); + var param = aElement.querySelector("[id='" + aElement.id + "']>param[name=flashvars]") + var config, flashvars = param ? param.value : aElement.getAttribute("flashvars"); + if (flashvars && flashvars.search(/^config={/) != -1) + if ((config = JSON.parse(flashvars.slice(7).replace(/'/g,'"').replace(/("autoPlay":)true/g,"$1false"))) && (config.playlist || config.clip)) + { + var playlist = (config.playlist = (config.playlist || [config.clip])); + (typeof playlist[0] == 'string') && (playlist[0] = {url: playlist[0]}); + playlist[0].autoPlay = (typeof playlist[0].url == 'string' && playlist[0].url.search(/(.png|.jpg)/) != -1); + (playlist[0].autoBuffering && (playlist[0].autoBuffering = false)) || (playlist[0].autoPlay && playlist[1] && (playlist[1].autoPlay = false)); + flashvars = "config=" + JSON.stringify(config); + param ? param.value = flashvars : aElement.setAttribute("flashvars", flashvars); + if (aElement.data) aElement.data = aElement.data; + TRACE("handleElement flowplayer - id: %s flashvars: %s")(aElement.id, flashvars); + return true; + } + }); + // aol + handlers.add(function handleAol(aElement){ + + if (aElement.localName == "object" && (aElement.data || "").search(/cdn(-ssl)?.vidible.tv\/.*\.swf/) != -1) + { + aElement.id || (aElement.id = "dummyid" + (++dummyid % 100)); + var param = aElement.querySelector("[id='" + aElement.id + "']>param[name=flashvars]"); + if (param) + { + param.value = param.value.replace(/(initialization%22%3A%22)autoplay/, "$1click"); + aElement.wrappedJSObject.doPlay = function() { TRACE("doNothing - doPlay")()}; + aElement.ownerDocument.defaultView.setTimeout(() => {delete aElement.wrappedJSObject.doPlay; TRACE("handleElement aol - removed doPlay")()}, 5000); + return !TRACE("handleElement aol - data: %s")(aElement.data); + } + } + }); + // general + var colValues = {"true":"false", "1":"0", "yes":"no", "on":"off", "y":"n", "Y":"N"} + function replace(match,p0,p1,p2) { + var res = p0 + (!p1 && p2 && colValues[p2] ? colValues[p2] : !!p1); + TRACE(">>> handleElement replace - match: %s p0: %s p1: %s p2: %s res: %s <<<")(match, p0, p1, p2, res); + return res; + } + function modifyParams(str) { return (str || "").replace(/((no)?auto_?(?:play|start|run)\w*(?:["']?\s?[=:]\s?["']?|%22%3A(?!\W)))(true|1|yes|y|on|null|)(?=\W|$)/gi, replace)}; + + function handleGeneral(aElement){ + + if (aElement.localName != "object" && aElement.localName != "embed") + return; + + var data = modifyParams(aElement.getAttribute("data")); + if (data != aElement.getAttribute("data") && data != "") + { + aElement.setAttribute("data", data); + TRACE("handleElement - %s.data: %s")(aElement.localName, data); + } + + var flashvars = modifyParams(aElement.getAttribute("flashvars")); // cnn | (remove) neulion nhl embed + if (aElement.localName == "embed" ) + { + aElement.setAttribute("menu","true"); + + if (flashvars == aElement.getAttribute("flashvars") || flashvars == "" ) + { + flashvars += "&autoplay=false&autostart=false&autoPlay=0"; // youtube(autoplay) | ustream live + aElement.setAttribute("play","false"); // basic flash + } + aElement.setAttribute("flashvars", flashvars); + if (aElement.src) + { + var src = modifyParams(aElement.src); // embed dailymotion + if (src != aElement.src) { + aElement.src = src; + var parent = aElement.parentNode, next = aElement.nextSibling; // workaround for chrome embed.src = + if (aElement.src && parent) { aElement.remove(); parent.insertBefore(aElement.wrappedJSObject, next); }; + TRACE("handleElement embed.src: %s")(src); + } + } + } + + aElement.id || (aElement.id = "dummyid" + (++dummyid % 100)); // [id =''] works with numeric ids!!! + var param = aElement.querySelector("[id='" + aElement.id + "']>param[name=flashvars], [id='" + aElement.id + "']>param[name=FlashVars], [id='" + aElement.id + "']>param[name=flashVars]"); + if (param) + { + flashvars = modifyParams(param.value); // ustream(autoplay=false) | bbc / metacafe.embed(no) / tv.com(vid) + if (flashvars == param.value) { + if (flashvars.search(/auto(_?play(vid)?|start|run)["']?\s?[=:]/i) != -1) return; // false already exists... + flashvars += "&autoplay=false&autoPlay=0&autoStart=0&_autoPlay=no&auto_start=off"; // justin.tv(autoPlay=0)|nba.com(autostart)|espn(!autoplay)|discovery(_autoPlay=no)|56.com(auto_start) + } + param.value = flashvars; + if (aElement.data) aElement.data = aElement.data; + } + (param = aElement.querySelector("[id='" + aElement.id + "']>param[name=play]")) && (param.value = false); + if (param && aElement.data) aElement.data = aElement.data; + TRACE("handleElement - id: %s %s.flashvars: %s")(aElement.id, aElement.localName, flashvars); + }; + + handlers.add(handleGeneral); +}; \ No newline at end of file diff --git a/AutoplayStopper/script/selector.css b/AutoplayStopper/script/selector.css new file mode 100644 index 0000000..84b0ce5 --- /dev/null +++ b/AutoplayStopper/script/selector.css @@ -0,0 +1,24 @@ +object[classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"]:not([flashstopped]), +object[classid*=":d27cdb6e-ae6d-11cf-96b8-444553540000"]:not([flashstopped]), +object[codebase*="swflash.cab"]:not([flashstopped]), +object[data*=".swf"]:not([flashstopped]), +object[type="application/x-shockwave-flash"]:not([flashstopped]), +object[src*=".swf"]:not([flashstopped]), +embed[type="application/x-shockwave-flash"]:not([flashstopped]), +embed[src*=".swf"]:not([flashstopped]), +embed[allowscriptaccess]:not([flashstopped]), +embed[flashvars]:not([flashstopped]), +embed[wmode]:not([flashstopped]), +lima-video:not([flashstopped]), +/*--------- !(html5media) ---------*/ +*:not([flashstopped_p]) > video, +video:not([flashstopped]), +audio:not([flashstopped]) { + border: 5px red solid !important; + box-sizing: border-box; +} + +*[flashstopped] { + border: 3px blue solid !important; + box-sizing: border-box; +} \ No newline at end of file diff --git a/AutoplayStopper/script/userhandler.js b/AutoplayStopper/script/userhandler.js new file mode 100644 index 0000000..3be2f54 --- /dev/null +++ b/AutoplayStopper/script/userhandler.js @@ -0,0 +1,9 @@ + +// userhandler.js - handler that doesn't get overwritten by new versions + +"use strict"; + +function registerUserHandlers(handlers, TRACE) +{ + // add your handlers here... +}; \ No newline at end of file diff --git a/AutoplayStopper/skin/IDR_CLOSE_DIALOG b/AutoplayStopper/skin/IDR_CLOSE_DIALOG new file mode 100644 index 0000000..9e2956d Binary files /dev/null and b/AutoplayStopper/skin/IDR_CLOSE_DIALOG differ diff --git a/AutoplayStopper/skin/IDR_CLOSE_DIALOG_H b/AutoplayStopper/skin/IDR_CLOSE_DIALOG_H new file mode 100644 index 0000000..aeafd96 Binary files /dev/null and b/AutoplayStopper/skin/IDR_CLOSE_DIALOG_H differ diff --git a/AutoplayStopper/skin/arrow_down.svg b/AutoplayStopper/skin/arrow_down.svg new file mode 100644 index 0000000..c3435ca --- /dev/null +++ b/AutoplayStopper/skin/arrow_down.svg @@ -0,0 +1 @@ + diff --git a/AutoplayStopper/skin/devtools.html b/AutoplayStopper/skin/devtools.html new file mode 100644 index 0000000..abce7b2 --- /dev/null +++ b/AutoplayStopper/skin/devtools.html @@ -0,0 +1,157 @@ + + + + + + + + + +
+
+ +

AutoplayStopper

+

1.0.0

+
+
+

Selector

+
+ + +
+
+ + + +
+
+
+

Script

+
+ + +
+
+ + + + + + +
+
+

User Script

+
+ + +
+
+ + + + + + +
+
+
+ + +
+
+ + +
+ + + diff --git a/AutoplayStopper/skin/options.css b/AutoplayStopper/skin/options.css new file mode 100644 index 0000000..d3e7a4e --- /dev/null +++ b/AutoplayStopper/skin/options.css @@ -0,0 +1,615 @@ + +/* chrome://resources/css/list.css */ + +list, +grid { + display: block; + outline: none; + overflow: auto; + position: relative; /* Make sure that item offsets are relative to the + list. */ +} + +list > *, +grid > * { + -webkit-user-select: none; + background-color: rgba(255, 255, 255, 0); + border: 1px solid rgba(255, 255, 255, 0); /* transparent white */ + border-radius: 2px; + cursor: default; + line-height: 20px; + margin: -1px 0; + overflow: hidden; + padding: 0 3px; + position: relative; /* to allow overlap */ + text-overflow: ellipsis; + white-space: pre; +} + +list > * { + display: block; +} + +grid > * { + display: inline-block; +} + +list > [lead], +grid > [lead] { + border-color: transparent; +} + +list:focus > [lead], +grid:focus > [lead] { + border-color: hsl(214, 91%, 65%); + z-index: 2; +} + +list > [anchor], +grid > [anchor] { + +} + +list:not([disabled]) > :hover, +grid:not([disabled]) > :hover { + background-color: hsl(214, 91%, 97%); + border-color: hsl(214, 91%, 85%); + z-index: 1; +} + +list > [selected], +grid > [selected] { + background-color: hsl(0, 0%, 90%); + background-image: -webkit-linear-gradient(rgba(255, 255, 255, 0.8), + rgba(255, 255, 255, 0)); + border-color: hsl(0, 0%, 85%); + z-index: 2; +} + +list:focus > [selected], +grid:focus > [selected] { + background-color: hsl(214, 91%, 89%); + border-color: hsl(214, 91%, 65%); +} + +list:focus > [lead][selected], +list > [selected]:hover, +grid:focus > [lead][selected], +grid > [selected]:hover { + background-color: hsl(214, 91%, 87%); + border-color: hsl(214, 91%, 65%); +} + +list > .spacer, +grid > .spacer { + border: 0; + box-sizing: border-box; + display: block; + margin: 0; + overflow: hidden; + visibility: hidden; +} + +list :-webkit-any( + input[type='input'], + input[type='password'], + input[type='search'], + input[type='text'], + input[type='url']), +list :-webkit-any( + button, + input[type='button'], + input[type='submit'], + select):not(.custom-appearance) { + line-height: normal; + margin: 0; + vertical-align: middle; +} + +list input[type='text'] { + margin-left: -4px; + padding: 3px; + width: 190px; +} + +/* chrome://resources/css/overlay.css */ + +[hidden] { + display: none !important; +} + +.overlay { + -webkit-box-align: center; + -webkit-box-orient: vertical; + -webkit-box-pack: center; + -webkit-perspective: 1px; + -webkit-transition: 200ms opacity; + background-color: rgba(255, 255, 255, 0.75); +/* bottom: 50px; */ + display: -webkit-box; + left: 0; + overflow: auto; + padding: 20px; + position: fixed; + right: 0; + top: 0; +} + +.overlay.transparent .page { + transform: scale(0.99) translateY(-20px); +} + +.transparent { + opacity: 0; +} + +.overlay .page { + max-height: 335.5px; +/* display: -webkit-box; + -webkit-box-orient: vertical; +*/ + -webkit-border-radius: 3px; + -webkit-box-orient: vertical; + -webkit-transition: 200ms transform; + background: white; + box-shadow: 0 4px 23px 5px rgba(0, 0, 0, 0.2), 0 2px 6px rgba(0,0,0,0.15); + color: #333; + display: -webkit-box; + min-width: 400px; + padding: 0px; + position: relative; + z-index: 0; +} + +.overlay .page .content-area { + -webkit-box-flex: 1; + overflow: auto; + overflow: overlay; /* auto */ + padding: 0px 20px 0px; /* 0px 17px 6px */ + position: relative; + min-height: 226px; + outline: 0; +} + +.overlay .page > .close-button { + background-image: url(IDR_CLOSE_DIALOG); + background-position: center; + background-repeat: no-repeat; + height: 14px; + position: absolute; + right: 7px; + top: 7px; + width: 14px; + z-index: 1; +} + +.overlay .page > .close-button:hover { + background-image: url(IDR_CLOSE_DIALOG_H); +} + +.overlay .page > .close-button:focus:not(:hover) { + background-image: url(IDR_CLOSE_DIALOG_H); + filter: opacity(75%); +} + +div.settings-row { + margin: 2px 18px; + display: flex; + padding: 2px 0; +} + +label.settings-label { + width: 130px; + display: inherit; +} + +.overlay .page h2 { + padding: 4px; +} + +/* chrome://settings/contentExceptions#... */ + +body { + position: relative; +} + +.hbox { + -webkit-box-orient: horizontal; + display: -webkit-box; +} + +.vbox { + -webkit-box-orient: vertical; + display: -webkit-box; +} + +.box-align-center { + -webkit-box-align: center; +} + +.stretch { + -webkit-box-flex: 1; +} + +.raw-button, +.raw-button:hover, +.raw-button:active { + background-color: transparent; + background-repeat: no-repeat; + border: none; + box-shadow: none !important; + min-width: 0; + min-height: 0; + padding: 1px 6px; +} + +.page list { + /* Min height is a multiple of the list item height (32) */ + box-sizing: content-box; + min-height: 192px; +} + +.option { + margin-top: 0; +} + +.settings-list, +.settings-list-empty { + border: 1px solid #d9d9d9; + border-radius: 2px; +} + +/* Editable List properties */ +list > * { + -webkit-box-align: center; + -webkit-transition: 150ms background-color; + border: none; + border-radius: 0; /* TODO(dbeam): Is this necessary? */ + box-sizing: border-box; + display: -webkit-box; + height: 32px; + margin: 0; +} + +list > .spacer { + /* The above height rule should not apply to spacers. When redraw is called + on the list they will be given an explicit element height but this ensures + they have 0 height to begin with. */ + height: 0; +} + +list:not([disabled]) > :hover { + background-color: rgb(228, 236, 247); +} + +/* Note: If this becomes the list style for other WebUI pages these rules can be + * simplified (since they wont need to override other rules). */ + +list:not([has-element-focus]) > [selected], +list:not([has-element-focus]) > [lead][selected] { + background-color: #d0d0d0; + background-image: none; +} + +list[has-element-focus] > [selected], +list[has-element-focus] > [lead][selected], +list:not([has-element-focus]) > [selected]:hover, +list:not([has-element-focus]) > [selected][lead]:hover { + background-color: rgb(187, 206, 233); + background-image: none; +} + +.settings-list[has-element-focus] > [lead], +.settings-list[has-element-focus] > [lead][selected] { + border-bottom: 1px solid rgb(120, 146, 180); + border-top: 1px solid rgb(120, 146, 180); +} + +.settings-list[has-element-focus] > [lead]:nth-child(2), +.settings-list[has-element-focus] > [lead][selected]:nth-child(2) { + border-top: 1px solid transparent; +} + +.settings-list[has-element-focus] > [lead]:nth-last-child(2), +.settings-list[has-element-focus] > [lead][selected]:nth-last-child(2) { + border-bottom: 1px solid transparent; +} + +.settings-list[disabled] > [lead][selected], +.settings-list[disabled]:focus > [lead][selected] { + border: none; +} + +list .deletable-item { + -webkit-box-align: center; + outline: none; +} + +list .deletable-item > :first-child { + -webkit-box-align: center; + -webkit-box-flex: 1; + -webkit-padding-end: 5px; + -moz-padding-end: 5px; + display: -webkit-box; +} + +list .row-delete-button { + -webkit-transition: 150ms opacity; + background-color: transparent; + background-image: + url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAiElEQVR42r2RsQrDMAxEBRdl8SDcX8lQPGg1GBI6lvz/h7QyRRXV0qUULwfvwZ1tenw5PxToRPWMC52eA9+WDnlh3HFQ/xBQl86NFYJqeGflkiogrOvVlIFhqURFVho3x1moGAa3deMs+LS30CAhBN5nNxeT5hbJ1zwmji2k+aF6NENIPf/hs54f0sZFUVAMigAAAABJRU5ErkJggg==); + border: none; + display: block; + height: 16px; + opacity: 1; + width: 16px; +} + +list > *:not(:hover):not([selected]):not([lead]) .row-delete-button, +list:not([has-element-focus]) > *:not(:hover):not([selected]) + .row-delete-button, +list[disabled] .row-delete-button, +list .row-delete-button[disabled] { + opacity: 0; + pointer-events: none; +} + +list .row-delete-button:hover { + background-image: + url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAqUlEQVR4XqWRMQqEMBBF/1E8Ra6x6V3FRnS9QbCxtJg6Z7CzE9lTiIXXyUb3C8EULixDIMM8Zt4kcDfxM5A45U+cgeXnC1tREgkzAgob3hiq3CUHvGLG4FTQoSgxQGDrzN8WTLBGnx2IVDksen9GH7Z9hA5E6uxABMJyCHDMCEGHzugLQPPlBCBNGq+5YtpnGw1Bv+te15ypljTpVzdak5Opy+z+qf//zQ+Lg+07ay5KsgAAAABJRU5ErkJggg==) !important; +} + +list .row-delete-button:active { + background-image: + url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAQklEQVR4AWP4TwBSTQGDHcMZIIYAKA9VwRkwtINJgyCaCTAlCBaKAoQ+hFmoCqBKENKkK8C0gpAjCXuTyICiQ2QBAPSwyG3ByZlCAAAAAElFTkSuQmCC) + , + url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAA/ElEQVR4Xu3UsWrCUBiG4efGlIBoIMFbcnYolYJ3pg4iKGrGYFTRwaUFhYAekiDt0EG++X2W83N8/3J/DbwBMJJSsdQItcDY1VlCOImzq3Ed8OmicHASB3ns5KBw8VUNpDJrW7uAiJ3sbK1l0mqArpmFTUlQ5jYWZrrUAUSmT0SZm4qoA56JvVhs/5g3A7RLolA85A1ASOTye65NMxASK6syfxGITMzvMxG9CvRkliWwlOm9AsSOcitzU1NzK7mjuBkQvHtLK7iLBiB5PhttJSGpB8I8vM6kDuiHeUjoVwMfYR4SRtUAw1veIZzOjRhSBzCoyKFjgH/3K7+BHzg+Cgw0eSW3AAAAAElFTkSuQmCC) + ; +} + +list .static-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +/* outline: none; bYO! inner rect on selection*/ +} + +list[type='text'][inlineeditable] input { + box-sizing: border-box; + margin: 0; + width: 100%; +} + +list > :not([editing]) [displaymode='edit'] { + display: none; +} + +list > [editing] [displaymode='static'] { + /* Don't use display:none or visibility:hidden because we need to keep an + * element focusable. + * We shrink only height. We don't shrink width to avoid to change the size + * of containing boxes. */ + border-bottom: 0 !important; + border-top: 0 !important; + height: 0 !important; + width: auto; + margin-bottom: 0 !important; + margin-top: 0 !important; + overflow: hidden; + pointer-events: none; +} + +list > [editing] input:invalid { + background-color: pink; +} + +/* UI Controls */ + +/* LIST */ /* bYO! top level outline... */ +.settings-list[has-element-focus]:after { +outline: 1px solid rgba(0, 128, 256, 0.5); + outline-offset: -1px; + content: ""; + position: absolute; + top: 0px; + left: 0px; + bottom: 0px; + right: 0px; + pointer-events: none; + z-index: 3; +} + +/* chrome://settings/contentExceptions#... */ + +#content-settings-exceptions-area { + min-width: 540px; + width: 540px; +} + +.exception-pattern { + -webkit-box-flex: 1; + -webkit-margin-end: 10px; + -moz-margin-end: 10px; + -webkit-margin-start: 14px; + -moz-margin-start: 14px; + width: 200px; +} + +.exception-setting { + display: inline-block; + width: 120px; +} + +select.exception-setting { + vertical-align: middle; + -webkit-padding-start: 3px; + width: calc(120px + 4px); +} + +.overruled .exception-setting { + width: calc(120px - 24px); +} + +.overruled .overruleable:focus { + outline:auto !important; +} + +.overruled .overruleable { + text-decoration: line-through; +} + +#exception-column-headers { + margin-left: 17px; + -webkit-margin-start: 17px; + display: -webkit-box; + margin-top: 10px; /* bYO! 6 lines on FF */ + line-height: 16px; +} + +#exception-column-headers > div { + font-weight: bold; +} + +#exception-pattern-column { + -webkit-box-flex: 1; +} + +.exception-value-column-header { + width: 145px; +} + +#content-settings-exceptions-area list { + /*margin-bottom: 10px; bYO! keep list area consistent with scrolling */ + margin-top: 6px; +} + /****** new ui compat... ******/ +button:not(.custom-appearance) { + --google-blue-500: #528de0; /* #3b72ce #4285f4 #0a84ff */ + font-weight: 500; + line-height: 100%; /* 154% */ + padding: 8px 10px; + color: white !important; + border: 1px solid var(--google-blue-500); + border-radius: 3px; + box-shadow: none !important; + font-family: inherit; + font-size: inherit; + text-shadow: none; +} + +button:enabled:not(.custom-appearance) { + background: var(--google-blue-500) !important; +} + +button:enabled:not(.custom-appearance):focus { + background: rgb(58, 117, 215) !important; +} + +select { + --md-arrow-width: 0.9em; + --md-arrow-offset: 0%; + --md-select-side-padding: 3px; + --md-select-width: 110px; + -webkit-appearance: none; + -moz-appearance: none; +/* -webkit-margin-end: calc(-1 * var(--md-select-side-padding)); */ +/* -moz-margin-end: var(--md-select-side-padding); */ +/* -webkit-padding-end: calc(var(--md-select-side-padding) + var(--md-arrow-offset) + var(--md-arrow-width) + 3px); */ + -webkit-padding-start: var(--md-select-side-padding); + -moz-padding-start: calc(var(--md-select-side-padding) - 1px); + background: url(arrow_down.svg) calc(100% - var(--md-arrow-offset) - var(--md-select-side-padding)) center no-repeat !important; + background-image: url(arrow_down.svg) !important; + background-size: var(--md-arrow-width) !important; + border: none; + font-family: inherit; + font-size: inherit; + outline: none; + padding-bottom: 2px; + padding-top: 3px; + border-bottom: 1px solid rgb(224, 224, 224); + width: var(--md-select-width); +/* width: calc(var(--md-select-width, 200px) + 2 * var(--md-select-side-padding)); */ + box-shadow: none !important; + text-shadow: none; + margin: 3px 0; +} + +select:focus { + border-color: rgb(77, 144, 254); +} + +select.exception-setting { + -webkit-padding-start: var(--md-select-side-padding); + width: calc(120px + var(--md-select-side-padding)); +} + +html > body { + height: 376px; + width: 500px; + margin: 16px 40px 14px 40px; +} + +.overlay { + background-color: rgba(255, 255, 255,.5); + bottom: 0px; + padding-top: 30px; +} + +.overlay .page { + max-height: 341.5px; + box-shadow: 0 0px 1px 0px rgba(0, 0, 0, 0.2), 0 1px 6px rgba(0,0,0,0.15); +} + +body > div:first-child { + padding-bottom: 5px; +} + +.action-area { + padding-top: .3em !important; +} + +label.settings-label { + width: 80px; + margin: 7px 0px; +} + +input.settings-checkbox ~ label.settings-label { + -webkit-margin-start: 3px; + -moz-margin-start: 3px; +} + +select ~ button.exceptions-list-button{ + -webkit-margin-start: auto; + -moz-margin-start: auto; +} + +input.settings-checkbox { + display: -moz-box; +} + +button:enabled:not(.custom-appearance):not(:focus):hover { + border: 1px solid rgba(0, 0, 0, 0.1); /* #3b72ce */ +} + +button:disabled:not(.custom-appearance) { + border-color: rgba(80, 80, 80, 0.1); +} + +list input[type="text"] { + font-size: inherit; + font-family: inherit; +} + +#perms-type { + -moz-padding-start: 3px; + height: 20px; +} + +button::-moz-focus-inner { + border: 0; +} + +select:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 #000; +} \ No newline at end of file diff --git a/AutoplayStopper/skin/options.html b/AutoplayStopper/skin/options.html new file mode 100644 index 0000000..564c2cf --- /dev/null +++ b/AutoplayStopper/skin/options.html @@ -0,0 +1,124 @@ + + + + + + + + + + +
+ Version + +
+

Settings

+

Autoplay

+ +
+ + + +
+
+

Flash

+
+ + + +
+ +
+

Advanced

+
+ + +
+ + diff --git a/AutoplayStopper/skin/popup.css b/AutoplayStopper/skin/popup.css new file mode 100644 index 0000000..f8ae519 --- /dev/null +++ b/AutoplayStopper/skin/popup.css @@ -0,0 +1,53 @@ +body { + margin: 8px; +} + +.popupmenu { + display: flex; + flex-direction: column; + color: #303942; + background: #f9fafb; + -webkit-user-select: none; +} + +.popupmenu .popupitem { + cursor: pointer; + display: block; + white-space: nowrap; +} + +.popupmenu .popupitem:hover { + background: rgb(235, 235, 235); /*#565a63; + color: white;*/ +} + +.popupmenu .popupitem.separator { + background-color: rgba(0,0,0,0.15); + height: 1px; + pointer-events: none; + margin: 2px 0; +} + +.popupmenu .popupitem:not(.separator) { + padding: 7px 10px; + border-radius: 2px; +} + +.popupmenu .popupitem[checked] { + background: rgb(80, 135, 90); /*#54975f;*/ + color: white; +} + +.popupmenu .popupitem[checked="2"], .popupmenu .popupitem[checked="3"] { + background: rgb(135, 80, 90); /*#54975f;*/ + color: white; +} + +.popupmenu.restricted #allow_site, .popupmenu.restricted #allow_session, .popupmenu.restricted #disable_site { + opacity: 0.5; + pointer-events: none; +} + +.popupmenu #allow_all[checked] { + background: darkgray; +} \ No newline at end of file diff --git a/AutoplayStopper/skin/popup.html b/AutoplayStopper/skin/popup.html new file mode 100644 index 0000000..8fccd28 --- /dev/null +++ b/AutoplayStopper/skin/popup.html @@ -0,0 +1,24 @@ + + + + + + + + + +
+
+
Disable flash for %S
+
Allow autoplay for %S
+
Allow session autoplay for %S
+
+
+
+ +
Allow everywhere
+
Settings
+
+
+ + diff --git a/AutoplayStopper/utils.js b/AutoplayStopper/utils.js new file mode 100644 index 0000000..8e9c883 --- /dev/null +++ b/AutoplayStopper/utils.js @@ -0,0 +1,103 @@ + + +"use strict"; + +const i18n = new function(){ + + return { + process: function (doc) { + var elements = doc.querySelectorAll("[i18n-content]"); + for (var i = 0; i < elements.length; ++i) { + var element = elements[i]; + var message = chrome.i18n.getMessage(element.getAttribute("i18n-content")); + if (message) element.textContent = message; + else + console.warn("i18n: no message for " + element.getAttribute("i18n-content")); + } + } + }; +}; + +const Storage = function Storage(keys, areaname){ + + var listeners = []; + var data = {}; + var ready = new Promise(function (resolve, reject) { + chrome.storage[areaname].get(keys, function(items){ + Object.assign(data, items); + if (!chrome.runtime.lastError) resolve(); + }) + }); + + chrome.storage.onChanged.addListener(function(changes, area) { + if (area == areaname) { + var updated = []; + for (var key of Object.keys(changes)) + if (keys.indexOf(key) != -1) { data[key] = changes[key].newValue; updated.push(key); }; + fireChanged(updated); + } + }); + + return { + get ready() { return ready; }, + get data() { return data; }, + addChangeListener: function(listener) { listeners.push(listener); return listener; }, + removeChangeListener: function(listener) { var idx = listeners.indexOf(listener); if (idx != -1) listeners.splice(idx); }, + commit: function commit(keys, callback) { + var items = {}; + for (var key of keys) items[key] = data[key]; + chrome.storage[areaname].set(items, function(){ + if (callback) callback(chrome.runtime.lastError); + }); + } + }; + + function fireChanged(updated) { for (var listener of listeners) listener(updated); }; +}; + +const Permission = { + UNKNOWN_ACTION: 0, + ALLOW_ACTION: 1, + DENY_ACTION: 2, + PROMPT_ACTION: 3, + ACCESS_SESSION: 8 +}; + +const Permissions = class Permissions { + + constructor(data) { + + function getData(type){ return data[Permissions.key(type)] || (data[Permissions.key(type)] = {}); }; + function getDefault(type){ return data[Permissions.defaultKey(type)]; }; + + Object.assign(this, { + testPermission: function testPermission(type, uri) { + var origin, url = new window.URL(uri); + var hostTokens = url.host.split("."); + do { + var permission = getData(type)[origin = url.protocol + "//" + hostTokens.join(".")]; + if (permission != undefined && permission != Permission.UNKNOWN_ACTION) + return Object.assign(Number(permission), {origin}); + } while (hostTokens.shift()); + return getDefault(type); + }, + clear: function clear(type) { data[Permissions.key(type)] = {}; }, + set: function set(type, url, perm) { getData(type)[new window.URL(url).origin] = perm; }, + remove: function remove(type, url) { delete getData(type)[new window.URL(url).origin]; }, + get: function get(type, url) { return getData(type)[new window.URL(url).origin]; }, + setDefault: function setDefault(type, perm) { data[Permissions.defaultKey(type)] = perm; }, + default: function _default(type) { return getDefault(type); }, + entries: function entries(type) { return Object.entries(getData(type)); }, + }); + }; + + key(type) { return Permissions.key(type); }; + defaultKey(type) { return Permissions.defaultKey(type); }; + + static key(type) { return `perms:${type}`; }; + static defaultKey(type) { return `perms:${type}Default`; }; +}; + +Object.assign(Permissions.prototype, Permission); + +// \ No newline at end of file