From abbc33b4acbbf87c0afb3f6212a2e9ff33c00967 Mon Sep 17 00:00:00 2001 From: Geluchat Date: Tue, 10 Jun 2025 12:25:54 +0200 Subject: [PATCH] Vibe coded manifest v3 support --- chrome/background.js | 130 ++++++++++++++----------- chrome/content_script.js | 13 ++- chrome/injected.js | 202 +++++++++++++++++++++++++++++++++++++++ chrome/manifest.json | 24 +++-- chrome/popup.js | 83 ++++++++-------- 5 files changed, 339 insertions(+), 113 deletions(-) create mode 100644 chrome/injected.js diff --git a/chrome/background.js b/chrome/background.js index dfaefdb..0cd045d 100644 --- a/chrome/background.js +++ b/chrome/background.js @@ -2,26 +2,25 @@ var tab_listeners = {}; var tab_push = {}, tab_lasturl = {}; var selectedId = -1; -function refreshCount() { - txt = tab_listeners[selectedId] ? tab_listeners[selectedId].length : 0; - chrome.tabs.get(selectedId, function() { - if (!chrome.runtime.lastError) { - chrome.browserAction.setBadgeText({"text": ''+txt, tabId: selectedId}); - if(txt > 0) { - chrome.browserAction.setBadgeBackgroundColor({ color: [255, 0, 0, 255]}); - } else { - chrome.browserAction.setBadgeBackgroundColor({ color: [0, 0, 255, 0] }); - } - } - }); +async function refreshCount(tabId) { + const key = `tab_${tabId}`; + const data = await chrome.storage.session.get(key); + const listeners = data[key] ? data[key].listeners : []; + const txt = listeners ? listeners.length : 0; + chrome.action.setBadgeText({ "text": '' + txt, tabId: tabId }); + if (txt > 0) { + chrome.action.setBadgeBackgroundColor({ color: [255, 0, 0, 255], tabId: tabId }); + } else { + chrome.action.setBadgeBackgroundColor({ color: [0, 0, 255, 0], tabId: tabId }); + } } function logListener(data) { chrome.storage.sync.get({ log_url: '' - }, function(items) { + }, function (items) { log_url = items.log_url; - if(!log_url.length) return; + if (!log_url.length) return; data = JSON.stringify(data); try { fetch(log_url, { @@ -31,67 +30,88 @@ function logListener(data) { }, body: data }); - } catch(e) { } + } catch (e) { } }); } -chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) { - console.log('message from cs', msg); - tabId = sender.tab.id; - if(msg.listener) { - if(msg.listener == 'function () { [native code] }') return; +chrome.runtime.onMessage.addListener(async function (msg, sender, sendResponse) { + const tabId = sender.tab.id; + const key = `tab_${tabId}`; + let tabData = (await chrome.storage.session.get(key))[key] || {}; + + if (msg.listener) { + if (msg.listener == 'function () { [native code] }') return; msg.parent_url = sender.tab.url; - if(!tab_listeners[tabId]) tab_listeners[tabId] = []; - tab_listeners[tabId][tab_listeners[tabId].length] = msg; + if (!tabData.listeners) tabData.listeners = []; + tabData.listeners.push(msg); logListener(msg); } - if(msg.pushState) { - tab_push[tabId] = true; + if (msg.pushState) { + tabData.pushState = true; } - if(msg.changePage) { - delete tab_lasturl[tabId]; + if (msg.changePage) { + delete tabData.lastUrl; } - if(msg.log) { - console.log(msg.log); - } else { - refreshCount(); + await chrome.storage.session.set({ [key]: tabData }); + + if (!msg.log) { + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + if (tab && tab.id === tabId) { + refreshCount(tabId); + } } }); -chrome.tabs.onUpdated.addListener(function(tabId, props) { - console.log(props); +chrome.tabs.onUpdated.addListener(async function (tabId, props) { + const key = `tab_${tabId}`; + let tabData = (await chrome.storage.session.get(key))[key] || {}; + if (props.status == "complete") { - if(tabId == selectedId) refreshCount(); - } else if(props.status) { - if(tab_push[tabId]) { - //this was a pushState, ignore - delete tab_push[tabId]; + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + if (tab && tab.id === tabId) { + refreshCount(tabId); + } + } else if (props.status) { + if (tabData.pushState) { + delete tabData.pushState; } else { - //if(props.url && tab_lasturl[tabId] && props.url.split('#')[0] == tab_lasturl[tabId]) { - //same url as before, only a hash change, ignore - //} else - if(!tab_lasturl[tabId]) { - //wipe on other statuses, but only if lastpage is not set (aka, changePage did not run) - tab_listeners[tabId] = []; + if (!tabData.lastUrl) { + tabData.listeners = []; } } } - if(props.status == "loading") - tab_lasturl[tabId] = true; + if (props.status == "loading") { + tabData.lastUrl = true; + } + + await chrome.storage.session.set({ [key]: tabData }); }); -chrome.tabs.onActivated.addListener(function(activeInfo) { - selectedId = activeInfo.tabId; - refreshCount(); +chrome.tabs.onActivated.addListener(function (activeInfo) { + refreshCount(activeInfo.tabId); }); -chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { - selectedId = tabs[0].id; - refreshCount(); +chrome.tabs.onRemoved.addListener(async (tabId) => { + const key = `tab_${tabId}`; + await chrome.storage.session.remove(key); }); -chrome.extension.onConnect.addListener(function(port) { - port.onMessage.addListener(function(msg) { - port.postMessage({listeners:tab_listeners}); +chrome.runtime.onConnect.addListener(function (port) { + port.onMessage.addListener(async function (msg) { + if (msg === "get-stuff") { + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + if (tab) { + const key = `tab_${tab.id}`; + const data = await chrome.storage.session.get(key); + port.postMessage({ listeners: data[key] ? data[key].listeners : [] }); + } + } }); -}) \ No newline at end of file +}) + +chrome.runtime.onInstalled.addListener(async () => { + const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); + if (tabs.length > 0) { + refreshCount(tabs[0].id); + } +}); \ No newline at end of file diff --git a/chrome/content_script.js b/chrome/content_script.js index aa5aed4..34dc30f 100644 --- a/chrome/content_script.js +++ b/chrome/content_script.js @@ -225,13 +225,16 @@ window.addEventListener('beforeunload', function(event) { document.dispatchEvent(storeEvent); }); -(function() { +function injectScript() { switch(document.contentType) { case 'application/xml': return; } var script = document.createElement("script"); - script.setAttribute('type', 'text/javascript') - script.appendChild(document.createTextNode(injectedJS)); - document.documentElement.appendChild(script); -})(); + script.src = chrome.runtime.getURL('injected.js'); + (document.head || document.documentElement).appendChild(script); + script.onload = function() { + script.remove(); + }; +} +injectScript(); \ No newline at end of file diff --git a/chrome/injected.js b/chrome/injected.js new file mode 100644 index 0000000..450d492 --- /dev/null +++ b/chrome/injected.js @@ -0,0 +1,202 @@ +(function (pushstate, msgeventlistener, msgporteventlistener) { + var loaded = false; + var originalFunctionToString = Function.prototype.toString; + var m = function (detail) { + var storeEvent = new CustomEvent('postMessageTracker', { 'detail': detail }); + document.dispatchEvent(storeEvent); + }; + var h = function (p) { + var hops = ""; + try { + if (!p) p = window; + if (p.top != p && p.top == window.top) { + var w = p; + while (top != w) { + var x = 0; + for (var i = 0; i < w.parent.frames.length; i++) { + if (w == w.parent.frames[i]) x = i; + }; + hops = "frames[" + x + "]" + (hops.length ? '.' : '') + hops; + w = w.parent; + }; + hops = "top" + (hops.length ? '.' + hops : '') + } else { + hops = p.top == window.top ? "top" : "diffwin"; + } + } catch (e) { + + } + return hops; + }; + var jq = function (instance) { + if (!instance || !instance.message || !instance.message.length) return; + var j = 0; while (e = instance.message[j++]) { + listener = e.handler; if (!listener) return; + m({ window: window.top == window ? 'top' : window.name, hops: h(), domain: document.domain, stack: 'jQuery', listener: listener.toString() }); + }; + }; + var l = function (listener, pattern_before, additional_offset) { + offset = 3 + (additional_offset || 0) + try { throw new Error(''); } catch (error) { stack = error.stack || ''; } + stack = stack.split('\n').map(function (line) { return line.trim(); }); + fullstack = stack.slice(); + if (pattern_before) { + nextitem = false; + stack = stack.filter(function (e) { + if (nextitem) { nextitem = false; return true; } + if (e.match(pattern_before)) + nextitem = true; + return false; + }); + stack = stack[0]; + } else { + stack = stack[offset]; + } + listener_str = listener.__postmessagetrackername__ || listener.toString(); + m({ window: window.top == window ? 'top' : window.name, hops: h(), domain: document.domain, stack: stack, fullstack: fullstack, listener: listener_str }); + }; + var jqc = function (key) { + m({ log: ['Found key', key, typeof window[key], window[key] ? window[key].toString() : window[key]] }); + if (typeof window[key] == 'function' && typeof window[key]._data == 'function') { + m({ log: ['found jq function', window[key].toString()] }); + ev = window[key]._data(window, 'events'); + jq(ev); + } else if (window[key] && (expando = window[key].expando)) { + m({ log: ['Use expando', expando] }); + var i = 1; while (instance = window[expando + i++]) { + jq(instance.events); + } + } else if (window[key]) { + m({ log: ['Use events directly', window[key].toString()] }); + jq(window[key].events); + } + }; + var j = function () { + m({ log: 'Run jquery fetcher' }); + var all = Object.getOwnPropertyNames(window); + var len = all.length; + for (var i = 0; i < len; i++) { + var key = all[i]; + if (key.indexOf('jQuery') !== -1) { + jqc(key); + } + } + loaded = true; + }; + History.prototype.pushState = function (state, title, url) { + m({ pushState: true }); + return pushstate.apply(this, arguments); + }; + var original_setter = window.__lookupSetter__('onmessage'); + window.__defineSetter__('onmessage', function (listener) { + if (listener) { + l(listener.toString()); + } + original_setter(listener); + }); + var c = function (listener) { + var listener_str = originalFunctionToString.apply(listener) + if (listener_str.match(/\.deep.*apply.*captureException/s)) return 'raven'; + else if (listener_str.match(/arguments.*(start|typeof).*err.*finally.*end/s) && listener["nr@original"] && typeof listener["nr@original"] == "function") return 'newrelic'; + else if (listener_str.match(/rollbarContext.*rollbarWrappedError/s) && listener._isWrap && + (typeof listener._wrapped == "function" || typeof listener._rollbar_wrapped == "function")) return 'rollbar'; + else if (listener_str.match(/autoNotify.*(unhandledException|notifyException)/s) && typeof listener.bugsnag == "function") return 'bugsnag'; + else if (listener_str.match(/call.*arguments.*typeof.*apply/s) && typeof listener.__sentry_original__ == "function") return 'sentry'; + else if (listener_str.match(/function.*function.*\.apply.*arguments/s) && typeof listener.__trace__ == "function") return 'bugsnag2'; + return false; + } + + var onmsgport = function (e) { + var p = (e.ports.length ? '%cport' + e.ports.length + '%c ' : ''); + var msg = '%cport%c→%c' + h(e.source) + '%c ' + p + (typeof e.data == 'string' ? e.data : 'j ' + JSON.stringify(e.data)); + if (p.length) { + console.log(msg, "color: blue", '', "color: red", '', "color: blue", ''); + } else { + console.log(msg, "color: blue", '', "color: red", ''); + } + }; + var onmsg = function (e) { + var p = (e.ports.length ? '%cport' + e.ports.length + '%c ' : ''); + var msg = '%c' + h(e.source) + '%c→%c' + h() + '%c ' + p + (typeof e.data == 'string' ? e.data : 'j ' + JSON.stringify(e.data)); + if (p.length) { + console.log(msg, "color: red", '', "color: green", '', "color: blue", ''); + } else { + console.log(msg, "color: red", '', "color: green", ''); + } + }; + window.addEventListener('message', onmsg) + MessagePort.prototype.addEventListener = function (type, listener, useCapture) { + if (!this.__postmessagetrackername__) { + this.__postmessagetrackername__ = true; + this.addEventListener('message', onmsgport); + } + return msgporteventlistener.apply(this, arguments); + } + + Window.prototype.addEventListener = function (type, listener, useCapture) { + if (type == 'message') { + var pattern_before = false, offset = 0; + if (listener.toString().indexOf('event.dispatch.apply') !== -1) { + m({ log: 'We got a jquery dispatcher' }); + pattern_before = /init\.on|init\..*on\]/; + if (loaded) { setTimeout(j, 100); } + } + var unwrap = function (listener) { + found = c(listener); + if (found == 'raven') { + var fb = false, ff = false, v = null; + for (key in listener) { + var v = listener[key]; + if (typeof v == "function") { ff++; f = v; } + if (typeof v == "boolean") fb++; + } + if (ff == 1 && fb == 1) { + m({ log: 'We got a raven wrapper' }); + offset++; + listener = unwrap(f); + } + } else if (found == 'newrelic') { + m({ log: 'We got a newrelic wrapper' }); + offset++; + listener = unwrap(listener["nr@original"]); + } else if (found == 'sentry') { + m({ log: 'We got a sentry wrapper' }); + offset++; + listener = unwrap(listener["__sentry_original__"]); + } else if (found == 'rollbar') { + m({ log: 'We got a rollbar wrapper' }); + offset += 2; + } else if (found == 'bugsnag') { + offset++; + var clr = null; + try { clr = arguments.callee.caller.caller.caller } catch (e) { } + if (clr && !c(clr)) { + m({ log: 'We got a bugsnag wrapper' }); + listener.__postmessagetrackername__ = clr.toString(); + } else if (clr) { offset++ } + } else if (found == 'bugsnag2') { + offset++; + var clr = null; + try { clr = arguments.callee.caller.caller.arguments[1]; } catch (e) { } + if (clr && !c(clr)) { + listener = unwrap(clr); + m({ log: 'We got a bugsnag2 wrapper' }); + listener.__postmessagetrackername__ = clr.toString(); + } else if (clr) { offset++; } + } + if (listener.name.indexOf('bound ') === 0) { + listener.__postmessagetrackername__ = listener.name; + } + return listener; + }; + + if (typeof listener == "function") { + listener = unwrap(listener); + l(listener, pattern_before, offset); + } + } + return msgeventlistener.apply(this, arguments); + }; + window.addEventListener('load', j); + window.addEventListener('postMessageTrackerUpdate', j); +})(History.prototype.pushState, Window.prototype.addEventListener, MessagePort.prototype.addEventListener); diff --git a/chrome/manifest.json b/chrome/manifest.json index 2d6f321..63984a5 100644 --- a/chrome/manifest.json +++ b/chrome/manifest.json @@ -1,12 +1,10 @@ { - "manifest_version": 2, + "manifest_version": 3, "name": "postMessage-tracker", "description": "Monitors and indicates postMessage-listeners in the current window.", "version": "1.0.0", "background": { - "scripts": [ - "background.js" - ] + "service_worker": "background.js" }, "content_scripts": [ { @@ -21,16 +19,22 @@ } ], "options_ui": { - "page": "options.html", - "chrome_style": true + "page": "options.html" }, - "browser_action": { + "action": { "default_popup": "popup.html" }, "permissions": [ "tabs", - "storage", - "http:\/\/*\/", - "https:\/\/*\/" + "storage" + ], + "host_permissions": [ + "*://*/*" + ], + "web_accessible_resources": [ + { + "resources": [ "injected.js" ], + "matches": [ "" ] + } ] } \ No newline at end of file diff --git a/chrome/popup.js b/chrome/popup.js index 617236a..a88e9d6 100644 --- a/chrome/popup.js +++ b/chrome/popup.js @@ -1,56 +1,53 @@ -var port = chrome.extension.connect({ +var port = chrome.runtime.connect({ name: "Sample Communication" }); - function loaded() { port.postMessage("get-stuff"); - port.onMessage.addListener(function(msg) { - console.log("message recieved yea: ", msg); - chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { - selectedId = tabs[0].id; - listListeners(msg.listeners[selectedId]); - }); + port.onMessage.addListener(function (msg) { + listListeners(msg.listeners); }); } window.onload = loaded -//addEventListener('DOMContentLoaded', loaded); function listListeners(listeners) { - var x = document.getElementById('x'); - x.parentElement.removeChild(x); - x = document.createElement('ol'); - x.id = 'x'; - //console.log(listeners); - document.getElementById('h').innerText = listeners.length ? listeners[0].parent_url : ''; - - for(var i = 0; i < listeners.length; i++) { - listener = listeners[i] - el = document.createElement('li'); - - bel = document.createElement('b'); - bel.innerText = listener.domain + ' '; - win = document.createElement('code'); - win.innerText = ' ' + (listener.window ? listener.window + ' ' : '') + (listener.hops && listener.hops.length ? listener.hops : ''); - el.appendChild(bel); - el.appendChild(win); - - sel = document.createElement('span'); - if(listener.fullstack) sel.setAttribute('title', listener.fullstack.join("\n\n")); - seltxt = document.createTextNode(listener.stack); - - sel.appendChild(seltxt); - el.appendChild(sel); - - pel = document.createElement('pre'); - pel.innerText = listener.listener; - el.appendChild(pel); - - x.appendChild(el); + var container = document.getElementById('content'); + var oldList = document.getElementById('x'); + if (oldList) { + container.removeChild(oldList); + } + var listElement = document.createElement('ol'); + listElement.id = 'x'; + + if (!listeners || listeners.length === 0) { + document.getElementById('h').innerText = ''; + } else { + document.getElementById('h').innerText = listeners[0].parent_url; + for (var i = 0; i < listeners.length; i++) { + var listener = listeners[i] + var el = document.createElement('li'); + + var bel = document.createElement('b'); + bel.innerText = listener.domain + ' '; + var win = document.createElement('code'); + win.innerText = ' ' + (listener.window ? listener.window + ' ' : '') + (listener.hops && listener.hops.length ? listener.hops : ''); + el.appendChild(bel); + el.appendChild(win); + + var sel = document.createElement('span'); + if (listener.fullstack) sel.setAttribute('title', listener.fullstack.join("\n\n")); + var seltxt = document.createTextNode(listener.stack); + + sel.appendChild(seltxt); + el.appendChild(sel); + + var pel = document.createElement('pre'); + pel.innerText = listener.listener; + el.appendChild(pel); + + listElement.appendChild(el); + } } - document.getElementById('content').appendChild(x); - /*setTimeout(function() { - document.body.style.display = 'block'; - }, 150);*/ + container.appendChild(listElement); } \ No newline at end of file