diff --git a/web/packages/extension/assets/css/common.css b/web/packages/extension/assets/css/common.css index 8c3beb54e06f..2c370689bbc3 100644 --- a/web/packages/extension/assets/css/common.css +++ b/web/packages/extension/assets/css/common.css @@ -157,3 +157,19 @@ button { height: 20px; margin: auto; } + +/* Textarea input */ +.option.textarea { + flex-direction: column; +} + +.option.textarea label { + color: var(--ruffle-orange); + font-size: 20px; + margin: 8px auto; + padding: 0; +} + +.option.textarea textarea { + width: 100%; +} diff --git a/web/packages/extension/assets/options.html b/web/packages/extension/assets/options.html index 6431a4121f44..51a7cec45611 100644 --- a/web/packages/extension/assets/options.html +++ b/web/packages/extension/assets/options.html @@ -66,6 +66,11 @@ +
+ + + +
diff --git a/web/packages/extension/src/common.ts b/web/packages/extension/src/common.ts index 4dec1094742b..994e925fee9f 100644 --- a/web/packages/extension/src/common.ts +++ b/web/packages/extension/src/common.ts @@ -7,11 +7,13 @@ export interface Options extends Config.BaseLoadOptions { autostart: boolean; showReloadButton: boolean; swfTakeover: boolean; + customConfig?: string; } interface OptionElement { readonly input: Element; readonly label: HTMLLabelElement; + readonly submitBtn?: HTMLButtonElement; value: T; } @@ -110,6 +112,26 @@ class SelectOption implements OptionElement { } } +class TextareaOption implements OptionElement { + constructor( + private readonly textarea: HTMLTextAreaElement, + readonly label: HTMLLabelElement, + readonly submitBtn: HTMLButtonElement, + ) {} + + get input() { + return this.textarea; + } + + get value() { + return this.textarea.value; + } + + set value(value: string) { + this.textarea.value = value ?? ""; + } +} + function getElement(option: Element): OptionElement { const label = option.getElementsByTagName("label")[0]!; @@ -129,6 +151,12 @@ function getElement(option: Element): OptionElement { return new SelectOption(select, label); } + const [textarea] = option.getElementsByTagName("textarea"); + if (textarea) { + const submitBtn = option.getElementsByTagName("button")[0]!; + return new TextareaOption(textarea, label, submitBtn); + } + throw new Error("Unknown option element"); } @@ -168,12 +196,30 @@ export async function bindOptions( element.label.textContent = message; } - // Listen for user input. - element.input.addEventListener("change", () => { - const value = element.value; - options[key] = value as never; - utils.storage.sync.set({ [key]: value }); - }); + if (element.input.nodeName === "TEXTAREA" && element.submitBtn) { + element.submitBtn.addEventListener("click", () => { + const value = element.value as string; + if (value.trim() === "") { + utils.storage.sync.set({ [key]: "" }); + alert("Custom configuration cleared."); + return; + } + try { + JSON.parse(value); + utils.storage.sync.set({ [key]: value }); + alert("Custom configuration saved successfully."); + } catch (_e) { + alert("Invalid configuration."); + } + }); + } else { + // Listen for user input. + element.input.addEventListener("change", () => { + const value = element.value; + options[key] = value as never; + utils.storage.sync.set({ [key]: value }); + }); + } } // Listen for future changes. diff --git a/web/packages/extension/src/content.ts b/web/packages/extension/src/content.ts index a6ab01115ad6..eac7cbb76cf4 100644 --- a/web/packages/extension/src/content.ts +++ b/web/packages/extension/src/content.ts @@ -191,10 +191,10 @@ function isXMLDocument(): boolean { await sendMessageToPage({ type: "load", config: { - ...explicitOptions, autoplay: options.autostart ? "on" : "auto", unmuteOverlay: options.autostart ? "hidden" : "visible", splashScreen: !options.autostart, + ...explicitOptions, }, publicPath: utils.runtime.getURL("/dist/"), }); diff --git a/web/packages/extension/src/utils.ts b/web/packages/extension/src/utils.ts index 35815cf2ff55..58f4ffffaad0 100644 --- a/web/packages/extension/src/utils.ts +++ b/web/packages/extension/src/utils.ts @@ -8,6 +8,7 @@ const DEFAULT_OPTIONS: Required = { autostart: false, showReloadButton: false, swfTakeover: true, + customConfig: "", }; // TODO: Once https://crbug.com/798169 is addressed, just use browser. @@ -106,6 +107,17 @@ export async function getExplicitOptions(): Promise { delete options["responseHeadersUnsupported"]; } + // Handle customConfig JSON + if (options.customConfig) { + try { + const extra = JSON.parse(options.customConfig); + Object.assign(options, extra); + } catch (e) { + console.warn("Invalid custom_config JSON:", e); + } + delete options.customConfig; + } + return options; }