Skip to content

Commit 037ba3f

Browse files
committed
Add new API and fix illegal invocation
1 parent c9733b9 commit 037ba3f

6 files changed

Lines changed: 182 additions & 4 deletions

File tree

Chrome/editor.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,10 @@ <h4 class="category-title">Browser & UI</h4>
247247
<input type="checkbox" id="gmAddStyle" class="form-checkbox">
248248
<label for="gmAddStyle">GM_addStyle</label>
249249
</div>
250+
<div class="form-group-checkbox">
251+
<input type="checkbox" id="gmAddElement" class="form-checkbox">
252+
<label for="gmAddElement">GM_addElement</label>
253+
</div>
250254
<div class="form-group-checkbox">
251255
<input type="checkbox" id="gmRegisterMenuCommand" class="form-checkbox">
252256
<label for="gmRegisterMenuCommand">GM_registerMenuCommand</label>

Chrome/editor.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ class ScriptEditor {
9696
name: "GM_addStyle",
9797
el: "gmAddStyle",
9898
},
99+
GM_addElement: {
100+
signature:
101+
"declare function GM_addElement(parent: Node, tag: string, attributes?: { [key: string]: string }): Node;",
102+
name: "GM_addElement",
103+
el: "gmAddElement",
104+
},
99105
GM_registerMenuCommand: {
100106
signature:
101107
"declare function GM_registerMenuCommand(caption: string, onClick: () => any, accessKey?: string): string;",
@@ -190,6 +196,7 @@ class ScriptEditor {
190196
"gmGetResourceText",
191197
"gmGetResourceURL",
192198
"gmAddStyle",
199+
"gmAddElement",
193200
"gmRegisterMenuCommand",
194201
"gmSetClipboard",
195202
"gmXmlhttpRequest",
@@ -675,6 +682,8 @@ class ScriptEditor {
675682
this.elements.gmSetClipboard.checked = !!script.gmSetClipboard;
676683
if (this.elements.gmAddStyle)
677684
this.elements.gmAddStyle.checked = !!script.gmAddStyle;
685+
if (this.elements.gmAddElement)
686+
this.elements.gmAddElement.checked = !!script.gmAddElement;
678687
if (this.elements.gmRegisterMenuCommand)
679688
this.elements.gmRegisterMenuCommand.checked = !!script.gmRegisterMenuCommand;
680689
if (this.elements.gmXmlhttpRequest)
@@ -746,6 +755,7 @@ class ScriptEditor {
746755
this.elements.gmGetResourceURL?.checked || false;
747756
scriptData.gmSetClipboard = this.elements.gmSetClipboard?.checked || false;
748757
scriptData.gmAddStyle = this.elements.gmAddStyle?.checked || false;
758+
scriptData.gmAddElement = this.elements.gmAddElement?.checked || false;
749759
scriptData.gmRegisterMenuCommand = this.elements.gmRegisterMenuCommand?.checked || false;
750760
scriptData.gmXmlhttpRequest = this.elements.gmXmlhttpRequest?.checked || false;
751761

@@ -915,6 +925,16 @@ class ScriptEditor {
915925
}
916926
}
917927

928+
updateApiCount() {
929+
const apiCheckboxes = Object.values(this.gmApiDefinitions).map(api => this.elements[api.el]);
930+
const checkedCount = apiCheckboxes.filter(checkbox => checkbox && checkbox.checked).length;
931+
932+
if (this.elements.apiCountBadge) {
933+
this.elements.apiCountBadge.textContent = checkedCount;
934+
this.elements.apiCountBadge.style.display = checkedCount > 0 ? 'inline' : 'none';
935+
}
936+
}
937+
918938
/**
919939
* Export current script in classic Tampermonkey format (.user.js)
920940
*/

Chrome/manifest.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@
1010
"webNavigation",
1111
"declarativeNetRequest",
1212
"contextMenus",
13-
"clipboardWrite",
14-
"notifications",
15-
"downloads",
16-
"offscreen"
13+
"notifications"
1714
],
1815
"host_permissions": [
1916
"<all_urls>"

Chrome/utils/gm_core.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,63 @@
271271
};
272272
window.GM_addStyle = window.GM.addStyle = fn;
273273
}
274+
if (e.gmAddElement) {
275+
const fn = (arg1, arg2, arg3 = {}) => {
276+
try {
277+
let parent;
278+
let tag;
279+
let attributes;
280+
281+
// Support both signatures:
282+
// 1) GM_addElement(tag, attributes)
283+
// 2) GM_addElement(parent, tag, attributes)
284+
if (typeof arg1 === 'string') {
285+
tag = arg1;
286+
attributes = (arg2 && typeof arg2 === 'object') ? arg2 : {};
287+
// Default parent by tag
288+
const lower = tag.toLowerCase();
289+
parent = (lower === 'style' || lower === 'script' || lower === 'link')
290+
? (document.head || document.documentElement || document.body)
291+
: (document.body || document.documentElement || document.head);
292+
} else {
293+
parent = arg1;
294+
tag = arg2;
295+
attributes = (arg3 && typeof arg3 === 'object') ? arg3 : {};
296+
}
297+
298+
if (!parent || typeof parent.appendChild !== 'function' || typeof tag !== 'string') {
299+
console.warn('GM_addElement: parent must be a valid DOM node and tag must be a string');
300+
return null;
301+
}
302+
303+
const el = document.createElement(tag);
304+
305+
// Apply attributes and direct properties
306+
Object.entries(attributes).forEach(([k, v]) => {
307+
if (v == null) return;
308+
try {
309+
if (k === 'style' && typeof v === 'string') {
310+
el.style.cssText = v;
311+
} else if (k in el) {
312+
el[k] = v;
313+
} else {
314+
el.setAttribute(k, String(v));
315+
}
316+
} catch (_e) {
317+
// Fallback to attribute if direct assignment fails
318+
try { el.setAttribute(k, String(v)); } catch (_e2) {}
319+
}
320+
});
321+
322+
parent.appendChild(el);
323+
return el;
324+
} catch (err) {
325+
console.error('GM_addElement: Failed to create or append element:', err);
326+
return null;
327+
}
328+
};
329+
window.GM_addElement = window.GM.addElement = fn;
330+
}
274331
}
275332

276333
// ---- NETWORK ----

Chrome/utils/inject.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,12 @@ class GMAPIRegistry {
219219
window.GM.addStyle = addStyle;
220220
}
221221

222+
if (enabledApis.gmAddElement) {
223+
const addElement = (parent, tag, attributes = {}) => this.addElementToDocument(parent, tag, attributes);
224+
window.GM_addElement = addElement;
225+
window.GM.addElement = addElement;
226+
}
227+
222228
// Always provide Trusted Types helpers for user scripts
223229
this.setupTrustedTypesHelpers();
224230
}
@@ -430,6 +436,32 @@ class GMAPIRegistry {
430436
return style;
431437
}
432438

439+
addElementToDocument(parent, tag, attributes = {}) {
440+
if (!parent || typeof tag !== "string") {
441+
console.warn("GM_addElement: parent must be a valid DOM node and tag must be a string");
442+
return null;
443+
}
444+
445+
try {
446+
const element = document.createElement(tag);
447+
448+
// Set attributes if provided
449+
if (attributes && typeof attributes === "object") {
450+
Object.entries(attributes).forEach(([key, value]) => {
451+
if (typeof key === "string" && value != null) {
452+
element.setAttribute(key, String(value));
453+
}
454+
});
455+
}
456+
457+
parent.appendChild(element);
458+
return element;
459+
} catch (error) {
460+
console.error("GM_addElement: Failed to create or append element:", error);
461+
return null;
462+
}
463+
}
464+
433465
registerMenuCommand(caption, onClick, accessKey) {
434466
if (typeof caption !== "string" || typeof onClick !== "function") {
435467
console.warn(
@@ -595,6 +627,32 @@ function createMainWorldExecutor(
595627
valueManager.initializeCache(initialValues);
596628
apiRegistry.registerAll(enabledApis);
597629

630+
// Ensure native functions are bound to the correct receiver to prevent Illegal invocation
631+
try {
632+
window.setTimeout = window.setTimeout.bind(window);
633+
window.clearTimeout = window.clearTimeout.bind(window);
634+
window.setInterval = window.setInterval.bind(window);
635+
window.clearInterval = window.clearInterval.bind(window);
636+
if (typeof window.postMessage === 'function') {
637+
window.postMessage = window.postMessage.bind(window);
638+
}
639+
} catch (e) {
640+
console.warn('CodeTweak: Failed to bind timers/postMessage:', e);
641+
}
642+
643+
// Debug: log enabled APIs for this execution
644+
try {
645+
const enabled = Object.entries(enabledApis)
646+
.filter(([, v]) => !!v)
647+
.map(([k]) => k)
648+
.sort();
649+
console.log(`CodeTweak: Enabled GM APIs for script ${scriptId} (MAIN):`, enabled);
650+
// Also dump full object for completeness
651+
console.debug('CodeTweak: enabledApis (MAIN) raw:', enabledApis);
652+
} catch (e) {
653+
console.warn('CodeTweak: Failed to log enabled APIs (MAIN):', e);
654+
}
655+
598656
// Expose GM_info (read-only)
599657
try {
600658
Object.defineProperty(window, "GM_info", {
@@ -681,6 +739,32 @@ function createIsolatedWorldExecutor(
681739
valueManager.initializeCache(initialValues);
682740
apiRegistry.registerAll(enabledApis);
683741

742+
// Ensure native functions are bound to the correct receiver to prevent Illegal invocation
743+
try {
744+
window.setTimeout = window.setTimeout.bind(window);
745+
window.clearTimeout = window.clearTimeout.bind(window);
746+
window.setInterval = window.setInterval.bind(window);
747+
window.clearInterval = window.clearInterval.bind(window);
748+
if (typeof window.postMessage === 'function') {
749+
window.postMessage = window.postMessage.bind(window);
750+
}
751+
} catch (e) {
752+
console.warn('CodeTweak: Failed to bind timers/postMessage (isolated):', e);
753+
}
754+
755+
// Debug: log enabled APIs for this execution (ISOLATED)
756+
try {
757+
const enabled = Object.entries(enabledApis)
758+
.filter(([, v]) => !!v)
759+
.map(([k]) => k)
760+
.sort();
761+
console.log(`CodeTweak: Enabled GM APIs for script ${scriptId} (ISOLATED):`, enabled);
762+
// Also dump full object for completeness
763+
console.debug('CodeTweak: enabledApis (ISOLATED) raw:', enabledApis);
764+
} catch (e) {
765+
console.warn('CodeTweak: Failed to log enabled APIs (ISOLATED):', e);
766+
}
767+
684768
// Expose GM_info (read-only)
685769
try {
686770
Object.defineProperty(window, "GM_info", {
@@ -858,10 +942,23 @@ class ScriptInjector {
858942
gmGetResourceURL: Boolean(script.gmGetResourceURL),
859943
gmSetClipboard: Boolean(script.gmSetClipboard),
860944
gmAddStyle: Boolean(script.gmAddStyle),
945+
gmAddElement: Boolean(script.gmAddElement),
861946
gmRegisterMenuCommand: Boolean(script.gmRegisterMenuCommand),
862947
gmXmlhttpRequest: Boolean(script.gmXmlhttpRequest),
863948
};
864949

950+
// Debug: log enabled APIs at config time (background/service context)
951+
try {
952+
const enabledList = Object.entries(enabledApis)
953+
.filter(([, v]) => !!v)
954+
.map(([k]) => k)
955+
.sort();
956+
console.log('CodeTweak: prepareScriptConfig enabled APIs for', scriptId, enabledList);
957+
console.debug('CodeTweak: prepareScriptConfig enabledApis raw:', enabledApis);
958+
} catch (e) {
959+
console.warn('CodeTweak: Failed to log enabled APIs in prepareScriptConfig:', e);
960+
}
961+
865962
const resourceManager = ResourceManager.fromScript(script);
866963

867964
return {

Chrome/utils/metadataParser.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ function parseUserScriptMetadata(content) {
1717
GM_getResourceURL: "gmGetResourceURL",
1818
GM_setClipboard: "gmSetClipboard",
1919
GM_addStyle: "gmAddStyle",
20+
GM_addElement: "gmAddElement",
2021
GM_registerMenuCommand: "gmRegisterMenuCommand",
2122
GM_xmlhttpRequest: "gmXmlhttpRequest",
2223
unsafeWindow: "unsafeWindow",
@@ -32,6 +33,7 @@ function parseUserScriptMetadata(content) {
3233
"GM.getResourceURL": "gmGetResourceURL",
3334
"GM.setClipboard": "gmSetClipboard",
3435
"GM.addStyle": "gmAddStyle",
36+
"GM.addElement": "gmAddElement",
3537
"GM.registerMenuCommand": "gmRegisterMenuCommand",
3638
"GM.xmlhttpRequest": "gmXmlhttpRequest",
3739
};
@@ -123,6 +125,7 @@ const gmApiFlagToGrant = {
123125
gmGetResourceURL: "GM_getResourceURL",
124126
gmSetClipboard: "GM_setClipboard",
125127
gmAddStyle: "GM_addStyle",
128+
gmAddElement: "GM_addElement",
126129
gmRegisterMenuCommand: "GM_registerMenuCommand",
127130
gmXmlhttpRequest: "GM_xmlhttpRequest",
128131
unsafeWindow: "unsafeWindow",

0 commit comments

Comments
 (0)