Browse Source

fix(export): always revoke object url on download failure

Made-with: Cursor
main
npmrun 2 weeks ago
parent
commit
c83e922996
  1. 75
      app/utils/markdown-export.test.ts
  2. 17
      app/utils/markdown-export.ts

75
app/utils/markdown-export.test.ts

@ -185,4 +185,79 @@ describe("downloadMarkdownFile", () => {
}
}
});
test("still revokes object URL when anchor click throws", () => {
const windowDescriptor = Object.getOwnPropertyDescriptor(globalThis, "window");
const documentDescriptor = Object.getOwnPropertyDescriptor(globalThis, "document");
const urlDescriptor = Object.getOwnPropertyDescriptor(globalThis, "URL");
let revokedArg = "";
let removed = false;
const blobUrl = "blob:error-case";
const clickError = new Error("click failed");
const anchor = {
href: "",
download: "",
style: { display: "" },
click: () => {
throw clickError;
},
remove: () => {
removed = true;
},
};
Object.defineProperty(globalThis, "window", {
value: {},
configurable: true,
writable: true,
});
Object.defineProperty(globalThis, "document", {
value: {
createElement: (_tag: string) => anchor,
body: {
appendChild: (_node: unknown) => undefined,
},
},
configurable: true,
writable: true,
});
Object.defineProperty(globalThis, "URL", {
value: {
createObjectURL: (_blob: Blob) => blobUrl,
revokeObjectURL: (url: string) => {
revokedArg = url;
},
},
configurable: true,
writable: true,
});
try {
expect(() => downloadMarkdownFile("broken.md", "# broken")).toThrow("click failed");
expect(removed).toBe(true);
expect(revokedArg).toBe(blobUrl);
} finally {
if (windowDescriptor) {
Object.defineProperty(globalThis, "window", windowDescriptor);
} else {
Reflect.deleteProperty(globalThis, "window");
}
if (documentDescriptor) {
Object.defineProperty(globalThis, "document", documentDescriptor);
} else {
Reflect.deleteProperty(globalThis, "document");
}
if (urlDescriptor) {
Object.defineProperty(globalThis, "URL", urlDescriptor);
} else {
Reflect.deleteProperty(globalThis, "URL");
}
}
});
});

17
app/utils/markdown-export.ts

@ -36,11 +36,14 @@ export function downloadMarkdownFile(filename: string, content: string): void {
const blob = new Blob([content], { type: "text/markdown;charset=utf-8" });
const url = URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = filename;
anchor.style.display = "none";
document.body.appendChild(anchor);
anchor.click();
anchor.remove();
URL.revokeObjectURL(url);
try {
anchor.href = url;
anchor.download = filename;
anchor.style.display = "none";
document.body.appendChild(anchor);
anchor.click();
} finally {
anchor.remove();
URL.revokeObjectURL(url);
}
}

Loading…
Cancel
Save