Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions web/packages/extension/assets/css/common.css
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
}
5 changes: 5 additions & 0 deletions web/packages/extension/assets/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@
<input type="number" id="max_execution_duration" min="1" />
<label for="max_execution_duration">Maximum allowed ActionScript execution duration (in seconds)</label>
</div>
<div class="option textarea">
<label for="custom_config">Custom Ruffle config (JSON)</label>
<textarea id="custom_config" rows="10" placeholder='{"letterbox":"on"}'></textarea>
<button>Set custom config</button>
</div>
<button id="reset-settings">Reset settings</button>
</div>
<script src="dist/options.js"></script>
Expand Down
58 changes: 52 additions & 6 deletions web/packages/extension/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ export interface Options extends Config.BaseLoadOptions {
autostart: boolean;
showReloadButton: boolean;
swfTakeover: boolean;
customConfig?: string;
}

interface OptionElement<T> {
readonly input: Element;
readonly label: HTMLLabelElement;
readonly submitBtn?: HTMLButtonElement;
value: T;
}

Expand Down Expand Up @@ -110,6 +112,26 @@ class SelectOption implements OptionElement<string | null> {
}
}

class TextareaOption implements OptionElement<string> {
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<unknown> {
const label = option.getElementsByTagName("label")[0]!;

Expand All @@ -129,6 +151,12 @@ function getElement(option: Element): OptionElement<unknown> {
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");
}

Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion web/packages/extension/src/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/"),
});
Expand Down
12 changes: 12 additions & 0 deletions web/packages/extension/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const DEFAULT_OPTIONS: Required<Options> = {
autostart: false,
showReloadButton: false,
swfTakeover: true,
customConfig: "",
};

// TODO: Once https://crbug.com/798169 is addressed, just use browser.
Expand Down Expand Up @@ -106,6 +107,17 @@ export async function getExplicitOptions(): Promise<Options> {
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;
}

Expand Down