diff --git a/apps/chrome-extension/package.json b/apps/chrome-extension/package.json
index 51e4d378813..ba008400c9d 100644
--- a/apps/chrome-extension/package.json
+++ b/apps/chrome-extension/package.json
@@ -33,6 +33,7 @@
"@types/react-dom": "19.2.3",
"@vitejs/plugin-react": "4.4.1",
"autoprefixer": "^10.4.16",
+ "jsdom": "^26.1.0",
"postcss": "^8.4.31",
"tailwindcss": "^3.4.0",
"typescript": "^5.8.3",
diff --git a/apps/chrome-extension/src/content/blur-overlay.test.tsx b/apps/chrome-extension/src/content/blur-overlay.test.tsx
new file mode 100644
index 00000000000..be2064879c6
--- /dev/null
+++ b/apps/chrome-extension/src/content/blur-overlay.test.tsx
@@ -0,0 +1,125 @@
+// @vitest-environment jsdom
+
+import { act } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { BlurOverlay } from "./blur-overlay";
+
+let root: Root;
+let host: HTMLDivElement;
+let target: HTMLDivElement;
+const onDone = vi.fn();
+
+beforeEach(() => {
+ vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
+ host = document.createElement("div");
+ target = document.createElement("div");
+ document.body.append(host, target);
+ Object.defineProperty(document, "elementFromPoint", {
+ configurable: true,
+ value: vi.fn(() => target),
+ });
+ root = createRoot(host);
+ act(() => root.render());
+});
+
+afterEach(() => {
+ act(() => root.unmount());
+ host.remove();
+ target.remove();
+ Reflect.deleteProperty(document, "elementFromPoint");
+ vi.unstubAllGlobals();
+});
+
+const clickTarget = () => {
+ const event = new MouseEvent("click", { bubbles: true, cancelable: true });
+ act(() => target.dispatchEvent(event));
+ return event;
+};
+
+describe("blur style restoration", () => {
+ it.each([
+ {
+ filter: "contrast(2)",
+ filterPriority: "important",
+ select: "text",
+ selectPriority: "important",
+ },
+ {
+ filter: "grayscale(1)",
+ filterPriority: "",
+ select: "all",
+ selectPriority: "",
+ },
+ {
+ filter: "",
+ filterPriority: "",
+ select: "none",
+ selectPriority: "important",
+ },
+ ])(
+ "restores the original inline values and priorities: $select/$filter",
+ (styles) => {
+ target.style.setProperty("filter", styles.filter, styles.filterPriority);
+ target.style.setProperty(
+ "user-select",
+ styles.select,
+ styles.selectPriority,
+ );
+ target.style.setProperty("color", "red", "important");
+ for (let cycle = 0; cycle < 2; cycle++) {
+ expect(clickTarget().defaultPrevented).toBe(true);
+ expect(target.style.getPropertyValue("filter")).toBe("blur(12px)");
+ expect(target.style.getPropertyPriority("filter")).toBe("important");
+ expect(target.style.getPropertyValue("user-select")).toBe("none");
+ expect(target.style.getPropertyPriority("user-select")).toBe(
+ "important",
+ );
+ clickTarget();
+ expect(target.style.getPropertyValue("filter")).toBe(styles.filter);
+ expect(target.style.getPropertyPriority("filter")).toBe(
+ styles.filterPriority,
+ );
+ expect(target.style.getPropertyValue("user-select")).toBe(
+ styles.select,
+ );
+ expect(target.style.getPropertyPriority("user-select")).toBe(
+ styles.selectPriority,
+ );
+ expect(target.style.getPropertyValue("color")).toBe("red");
+ expect(target.style.getPropertyPriority("color")).toBe("important");
+ expect(Object.keys(target.dataset)).toEqual([]);
+ }
+ },
+ );
+
+ it("removes temporary styles when the target had no inline declarations", () => {
+ clickTarget();
+ clickTarget();
+ expect(target.style.getPropertyValue("filter")).toBe("");
+ expect(target.style.getPropertyValue("user-select")).toBe("");
+ expect(Object.keys(target.dataset)).toEqual([]);
+ });
+
+ it("retains saved styles when the blur tool closes and reopens", () => {
+ target.style.setProperty("user-select", "text", "important");
+ clickTarget();
+ act(() => root.render());
+ expect(clickTarget().defaultPrevented).toBe(false);
+ expect(target.style.getPropertyValue("filter")).toBe("blur(12px)");
+ act(() => root.render());
+ clickTarget();
+ expect(target.style.getPropertyValue("user-select")).toBe("text");
+ expect(target.style.getPropertyPriority("user-select")).toBe("important");
+ expect(Object.keys(target.dataset)).toEqual([]);
+ });
+
+ it("ignores extension controls", () => {
+ target.id = "cap-extension-recorder-overlay";
+ target.style.setProperty("user-select", "text", "important");
+ expect(clickTarget().defaultPrevented).toBe(false);
+ expect(target.style.getPropertyValue("user-select")).toBe("text");
+ expect(target.style.getPropertyPriority("user-select")).toBe("important");
+ expect(Object.keys(target.dataset)).toEqual([]);
+ });
+});
diff --git a/apps/chrome-extension/src/content/blur-overlay.tsx b/apps/chrome-extension/src/content/blur-overlay.tsx
index 3512d93360d..f2f32a8ade5 100644
--- a/apps/chrome-extension/src/content/blur-overlay.tsx
+++ b/apps/chrome-extension/src/content/blur-overlay.tsx
@@ -71,15 +71,37 @@ export function BlurOverlay({ active, onDone }: BlurOverlayProps) {
if (target.dataset.capBlurred === "true") {
const orig = target.dataset.capOrigFilter ?? "";
if (orig) {
- target.style.filter = orig;
+ target.style.setProperty(
+ "filter",
+ orig,
+ target.dataset.capOrigFilterPriority ?? "",
+ );
} else {
target.style.removeProperty("filter");
}
- target.style.removeProperty("user-select");
+ const originalSelection = target.dataset.capOrigUserSelect ?? "";
+ if (originalSelection) {
+ target.style.setProperty(
+ "user-select",
+ originalSelection,
+ target.dataset.capOrigUserSelectPriority ?? "",
+ );
+ } else {
+ target.style.removeProperty("user-select");
+ }
delete target.dataset.capBlurred;
delete target.dataset.capOrigFilter;
+ delete target.dataset.capOrigFilterPriority;
+ delete target.dataset.capOrigUserSelect;
+ delete target.dataset.capOrigUserSelectPriority;
} else {
target.dataset.capOrigFilter = target.style.filter || "";
+ target.dataset.capOrigFilterPriority =
+ target.style.getPropertyPriority("filter");
+ target.dataset.capOrigUserSelect =
+ target.style.getPropertyValue("user-select");
+ target.dataset.capOrigUserSelectPriority =
+ target.style.getPropertyPriority("user-select");
target.style.setProperty("filter", "blur(12px)", "important");
target.style.setProperty("user-select", "none", "important");
target.dataset.capBlurred = "true";
diff --git a/bun.lock b/bun.lock
index 5a6fb2c3053..710b246b228 100644
--- a/bun.lock
+++ b/bun.lock
@@ -43,6 +43,7 @@
"@types/react-dom": "19.2.3",
"@vitejs/plugin-react": "4.4.1",
"autoprefixer": "^10.4.16",
+ "jsdom": "^26.1.0",
"postcss": "^8.4.31",
"tailwindcss": "^3.4.0",
"typescript": "^5.8.3",