Accept raw settings backups during import

This commit is contained in:
2026-04-07 14:31:36 -04:00
parent e6c56bcecb
commit f32d1b3f71
3 changed files with 102 additions and 3 deletions
+40 -1
View File
@@ -8,6 +8,44 @@
root.SpeederShared = root.SpeederShared || {};
root.SpeederShared.importExport = exports;
})(typeof globalThis !== "undefined" ? globalThis : this, function() {
var rawSettingsKeys = new Set([
"audioBoolean",
"controllerButtons",
"controllerLocation",
"controllerMarginBottom",
"controllerMarginLeft",
"controllerMarginRight",
"controllerMarginTop",
"controllerOpacity",
"enableSubtitleNudge",
"enabled",
"forceLastSavedSpeed",
"hideWithControls",
"hideWithControlsTimer",
"hideWithYouTubeControls",
"keyBindings",
"lastSpeed",
"popupControllerButtons",
"popupMatchHoverControls",
"rememberSpeed",
"showPopupControlBar",
"siteRules",
"speed",
"startHidden",
"subtitleNudgeAmount",
"subtitleNudgeInterval"
]);
function isRecognizedRawSettingsObject(backup) {
if (!backup || typeof backup !== "object" || Array.isArray(backup)) {
return false;
}
return Object.keys(backup).some(function(key) {
return rawSettingsKeys.has(key);
});
}
function generateBackupFilename(now) {
var date = now instanceof Date ? now : new Date(now || Date.now());
var year = date.getFullYear();
@@ -53,7 +91,7 @@
} else if (
backup &&
typeof backup === "object" &&
(backup.keyBindings || backup.rememberSpeed !== undefined)
isRecognizedRawSettingsObject(backup)
) {
settingsToImport = backup;
}
@@ -80,6 +118,7 @@
buildBackupPayload: buildBackupPayload,
extractImportSettings: extractImportSettings,
generateBackupFilename: generateBackupFilename,
isRecognizedRawSettingsObject: isRecognizedRawSettingsObject,
parseImportText: parseImportText
};
});
+53 -1
View File
@@ -128,6 +128,58 @@ describe("import/export flows", () => {
expect(globalThis.restore_options).toHaveBeenCalled();
});
it("imports raw settings objects without touching local storage", async () => {
vi.useFakeTimers();
const chrome = await setupImportExport({
local: { customButtonIcons: { faster: { slug: "rocket" } } }
});
const originalCreateElement = document.createElement.bind(document);
let createdInput = null;
vi.spyOn(document, "createElement").mockImplementation((tagName) => {
const el = originalCreateElement(tagName);
if (tagName === "input") {
createdInput = el;
el.click = vi.fn();
}
return el;
});
globalThis.FileReader = class MockFileReader {
readAsText(file) {
this.onload({
target: {
result: file.__text
}
});
}
};
globalThis.importSettings();
createdInput.onchange({
target: {
files: [
{
__text: JSON.stringify({
enabled: false,
siteRules: [{ pattern: "example.com", enabled: false }]
})
}
]
}
});
expect(chrome.storage.local.clear).not.toHaveBeenCalled();
expect(chrome.storage.local.set).not.toHaveBeenCalled();
expect(chrome.storage.sync.set).toHaveBeenCalledWith(
{
enabled: false,
siteRules: [{ pattern: "example.com", enabled: false }]
},
expect.any(Function)
);
});
it("clears stale local data when a wrapped backup has empty local settings", async () => {
vi.useFakeTimers();
const chrome = await setupImportExport({
@@ -210,7 +262,7 @@ describe("import/export flows", () => {
target: {
files: [
{
__text: JSON.stringify({ enabled: true })
__text: JSON.stringify({ wat: true })
}
]
}
+9 -1
View File
@@ -140,6 +140,14 @@ describe("shared helpers", () => {
expect(
importExportUtils.extractImportSettings({ enabled: true })
).toBeNull();
).toEqual({
isWrappedBackup: false,
settings: { enabled: true },
localSettings: null
});
expect(importExportUtils.isRecognizedRawSettingsObject({ wat: true })).toBe(
false
);
});
});