Yay a devtools!
This commit is contained in:
parent
8e6294c96f
commit
9124abb749
24
devtools/background.js
Normal file
24
devtools/background.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
console.log("background.js loaded");
|
||||||
|
|
||||||
|
// Listen for messages from the devtools panel
|
||||||
|
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
|
// You could add a check to see if the message is from the devtools panel.
|
||||||
|
if (message.type === "GET_CONTEXT_STACK") {
|
||||||
|
// Forward the message to the content script running in the active tab.
|
||||||
|
browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
|
||||||
|
if (tabs.length) {
|
||||||
|
browser.tabs.sendMessage(tabs[0].id, message).then(sendResponse);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Return true to indicate you wish to send a response asynchronously
|
||||||
|
return true;
|
||||||
|
} else if (message.type === "UPDATE_CONTEXT_VALUE") {
|
||||||
|
// Forward update messages similarly
|
||||||
|
browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
|
||||||
|
if (tabs.length) {
|
||||||
|
browser.tabs.sendMessage(tabs[0].id, message).then(sendResponse);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
27
devtools/content.js
Normal file
27
devtools/content.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
console.log("content.js loaded", window.wrappedJSObject);
|
||||||
|
|
||||||
|
// setTimeout(() => {
|
||||||
|
const getContextStack = () => window.wrappedJSObject.getContextStack();
|
||||||
|
const updateContextValue = (key, value, depth) =>
|
||||||
|
window.wrappedJSObject.updateContextValue(key, value, depth);
|
||||||
|
|
||||||
|
console.log(getContextStack());
|
||||||
|
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
|
if (message.type === "GET_CONTEXT_STACK") {
|
||||||
|
const contextStack = getContextStack();
|
||||||
|
|
||||||
|
sendResponse({ contextStack });
|
||||||
|
} else if (message.type === "UPDATE_CONTEXT_VALUE") {
|
||||||
|
const { key, value, depth } = message;
|
||||||
|
if (glowindow.updateContextValue) {
|
||||||
|
updateContextValue(key, value, depth);
|
||||||
|
sendResponse({ success: true });
|
||||||
|
} else {
|
||||||
|
sendResponse({
|
||||||
|
success: false,
|
||||||
|
error: "updateContextValue not defined",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// }, 0);
|
12
devtools/devtools.html
Normal file
12
devtools/devtools.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<script src="devtools.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body></body>
|
||||||
|
|
||||||
|
</html>
|
7
devtools/devtools.js
Normal file
7
devtools/devtools.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Create a new devtools panel named "Context Stack"
|
||||||
|
browser.devtools.panels.create(
|
||||||
|
"Context Stack", // Tab title
|
||||||
|
"train icon.png", // Icon for your panel (optional)
|
||||||
|
"panel.html", // HTML page for your panel content
|
||||||
|
);
|
||||||
|
console.log("devtools loaded");
|
27
devtools/manifest.json
Normal file
27
devtools/manifest.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "Context Stack DevTools",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "A devtools panel to view and edit context stack values.",
|
||||||
|
"devtools_page": "devtools.html",
|
||||||
|
"background": {
|
||||||
|
"scripts": [
|
||||||
|
"background.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": [
|
||||||
|
"<all_urls>"
|
||||||
|
],
|
||||||
|
"js": [
|
||||||
|
"content.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"permissions": [
|
||||||
|
"devtools",
|
||||||
|
"tabs",
|
||||||
|
"*://*/*"
|
||||||
|
]
|
||||||
|
}
|
39
devtools/panel.html
Normal file
39
devtools/panel.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Context Stack</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
/* margin: 10px; */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #302040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-row {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-key {
|
||||||
|
width: 150px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-value input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Context Stack</h1>
|
||||||
|
<div id="contextContainer"></div>
|
||||||
|
<button id="refresh">Refresh</button>
|
||||||
|
<script src="panel.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
52
devtools/panel.js
Normal file
52
devtools/panel.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
document.getElementById("refresh").addEventListener("click", loadContextStack);
|
||||||
|
|
||||||
|
function loadContextStack() {
|
||||||
|
const container = document.getElementById("contextContainer");
|
||||||
|
container.innerHTML = "";
|
||||||
|
|
||||||
|
browser.runtime.sendMessage({ type: "GET_CONTEXT_STACK" }).then(
|
||||||
|
(response) => {
|
||||||
|
if (!response || !response.contextStack) {
|
||||||
|
container.innerHTML = "<p>No context stack found.</p>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextStack = response.contextStack;
|
||||||
|
for (const key in contextStack) {
|
||||||
|
const row = document.createElement("div");
|
||||||
|
row.classList.add("context-row");
|
||||||
|
|
||||||
|
const keyDiv = document.createElement("div");
|
||||||
|
keyDiv.classList.add("context-key");
|
||||||
|
keyDiv.textContent = key;
|
||||||
|
|
||||||
|
const valueDiv = document.createElement("div");
|
||||||
|
valueDiv.classList.add("context-value");
|
||||||
|
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.value = contextStack[key];
|
||||||
|
input.addEventListener("change", () => {
|
||||||
|
browser.runtime.sendMessage({
|
||||||
|
type: "UPDATE_CONTEXT_VALUE",
|
||||||
|
key,
|
||||||
|
value: input.value,
|
||||||
|
}).then((updateResponse) => {
|
||||||
|
if (updateResponse && updateResponse.success) {
|
||||||
|
console.log(`Updated ${key} to ${input.value}`);
|
||||||
|
} else {
|
||||||
|
console.error(`Failed to update ${key}:`, updateResponse.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
valueDiv.appendChild(input);
|
||||||
|
row.appendChild(keyDiv);
|
||||||
|
row.appendChild(valueDiv);
|
||||||
|
container.appendChild(row);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial load when the panel opens.
|
||||||
|
loadContextStack();
|
BIN
devtools/train icon.png
Normal file
BIN
devtools/train icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
115
lib/context.ts
Normal file
115
lib/context.ts
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
type ContextStore = Record<string, any>;
|
||||||
|
|
||||||
|
const defaultContext: ContextStore = {};
|
||||||
|
const contextStack: ContextStore[] = [defaultContext];
|
||||||
|
|
||||||
|
const debug = JSON.parse(localStorage.getItem("debug") || "false");
|
||||||
|
|
||||||
|
export function setDefaultContext(context: ContextStore) {
|
||||||
|
Object.assign(defaultContext, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function withContext<T>(context: ContextStore, fn: () => T): T {
|
||||||
|
contextStack.push(context);
|
||||||
|
try {
|
||||||
|
return fn();
|
||||||
|
} finally {
|
||||||
|
contextStack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ctx = new Proxy(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get(_, prop: string) {
|
||||||
|
for (let i = contextStack.length - 1; i >= 0; i--) {
|
||||||
|
if (prop in contextStack[i]) return contextStack[i][prop];
|
||||||
|
}
|
||||||
|
if (prop in defaultContext) return defaultContext[prop];
|
||||||
|
throw new Error(`Context variable '${prop}' is not defined.`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
) as Record<string, unknown>;
|
||||||
|
|
||||||
|
export function getContext() {
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
export function getContextItem<T>(prop: string): T {
|
||||||
|
return ctx[prop] as T;
|
||||||
|
}
|
||||||
|
export function getContextItemOrDefault<T>(prop: string, defaultValue: T): T {
|
||||||
|
try {
|
||||||
|
return ctx[prop] as T;
|
||||||
|
} catch {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function setContextItem<T>(prop: string, value: T) {
|
||||||
|
Object.assign(contextStack[contextStack.length - 1] ?? defaultContext, {
|
||||||
|
[prop]: value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
setInterval(() => {
|
||||||
|
let ctxEl = document.getElementById("context");
|
||||||
|
if (!ctxEl) {
|
||||||
|
ctxEl = document.createElement("div");
|
||||||
|
ctxEl.id = "context";
|
||||||
|
document.body.append(ctxEl);
|
||||||
|
}
|
||||||
|
ctxEl.innerHTML = "";
|
||||||
|
const div = document.createElement("div");
|
||||||
|
const pre = document.createElement("pre");
|
||||||
|
const h3 = document.createElement("h3");
|
||||||
|
h3.textContent = "Default";
|
||||||
|
div.append(h3);
|
||||||
|
pre.textContent = safeStringify(defaultContext);
|
||||||
|
div.append(pre);
|
||||||
|
ctxEl.append(div);
|
||||||
|
for (const [idx, ctx] of contextStack.entries()) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
const pre = document.createElement("pre");
|
||||||
|
const h3 = document.createElement("h3");
|
||||||
|
h3.textContent = "CTX " + idx;
|
||||||
|
div.append(h3);
|
||||||
|
pre.textContent = safeStringify(ctx);
|
||||||
|
div.append(pre);
|
||||||
|
ctxEl.append(div);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeStringify(obj: any) {
|
||||||
|
const seen = new WeakSet();
|
||||||
|
return JSON.stringify(obj, (key, value) => {
|
||||||
|
if (typeof value === "object" && value !== null) {
|
||||||
|
if (seen.has(value)) {
|
||||||
|
return "[Circular]"; // Replace circular references
|
||||||
|
}
|
||||||
|
seen.add(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getContextStack = () => contextStack.slice();
|
||||||
|
const updateContextValue = (
|
||||||
|
key: string,
|
||||||
|
value: unknown,
|
||||||
|
depth = contextStack.length - 1,
|
||||||
|
) => {
|
||||||
|
const context = contextStack[depth] ?? defaultContext;
|
||||||
|
context[key] = value;
|
||||||
|
};
|
||||||
|
if (location.hostname === "localhost") {
|
||||||
|
globalThis.getContextStack = getContextStack;
|
||||||
|
globalThis.updateContextValue = updateContextValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("globalThis.getContextStack", globalThis.getContextStack);
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
var getContextStack: () => ContextStore[];
|
||||||
|
var updateContextValue: (key: string, value: unknown, depth?: number) => void;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user