From 79e1473fe245998a3ed8046419c66a7a9ba617d3 Mon Sep 17 00:00:00 2001 From: TW Date: Tue, 14 Jul 2026 13:39:52 +0800 Subject: [PATCH] fix: complete iframe and resize interaction handling - Inject selection handling into inherited-origin frames without initializing the button iframe. - Persist final panel dimensions when focus loss cancels an active user resize. - Add regression coverage for frame filtering and canceled resize persistence. --- .../content/display/panel/panel_runtime.js | 2 +- .../content/select/frame_pointer_bridge.js | 7 ++- .../src/content/select/select.js | 7 ++- .../src/content/select/select_button.js | 1 + .../EdgeTranslate/src/manifest_chrome.json | 3 +- .../content/display/panel_runtime.test.js | 54 +++++++++++++++++++ .../select/frame_pointer_bridge.test.js | 9 ++++ .../unit/content/select/select_button.test.js | 15 ++++++ 8 files changed, 93 insertions(+), 5 deletions(-) diff --git a/packages/EdgeTranslate/src/content/display/panel/panel_runtime.js b/packages/EdgeTranslate/src/content/display/panel/panel_runtime.js index 0a9bf6bb..a59a8e4e 100644 --- a/packages/EdgeTranslate/src/content/display/panel/panel_runtime.js +++ b/packages/EdgeTranslate/src/content/display/panel/panel_runtime.js @@ -129,7 +129,7 @@ export function attachResizeHandlers({ if (dragShield.deactivate(inputEvent, canceled)) setUsePDFMaskLayer(false); startTranslate = translate; target.style.transform = `translate(${translate[0]}px, ${translate[1]}px)`; - if (inputEvent) { + if (inputEvent || canceled) { updateResizedDisplayData(displaySettingRef.current, width, height); updateDisplaySetting(); } diff --git a/packages/EdgeTranslate/src/content/select/frame_pointer_bridge.js b/packages/EdgeTranslate/src/content/select/frame_pointer_bridge.js index 71153819..6aa3c0b8 100644 --- a/packages/EdgeTranslate/src/content/select/frame_pointer_bridge.js +++ b/packages/EdgeTranslate/src/content/select/frame_pointer_bridge.js @@ -1,7 +1,8 @@ import { SUBFRAME_POINTER_DOWN_EVENT } from "common/scripts/frame_events.js"; +const EXTENSION_BUTTON_FRAME_ID = "edge-translate-button"; const EXTENSION_UI_IDS = new Set([ - "edge-translate-button", + EXTENSION_BUTTON_FRAME_ID, "edge-translate-button-host", "edge-translate-root", "edge-translate-screenshot-overlay", @@ -21,6 +22,10 @@ export function registerSubframePointerDownBridge(channel) { ); } +export function isExtensionOwnedFrame(frameElement = window.frameElement) { + return frameElement?.id === EXTENSION_BUTTON_FRAME_ID; +} + export function isExtensionOwnedPointerEvent(event) { const path = event.composedPath?.() || [event.target]; return path.some((node) => EXTENSION_UI_IDS.has(node?.id)); diff --git a/packages/EdgeTranslate/src/content/select/select.js b/packages/EdgeTranslate/src/content/select/select.js index 2585a184..fdf5a0be 100644 --- a/packages/EdgeTranslate/src/content/select/select.js +++ b/packages/EdgeTranslate/src/content/select/select.js @@ -9,7 +9,10 @@ import { import { finishLongPressMouseUp } from "./select_long_press_events.js"; import { createLongPressTools } from "./select_long_press.js"; import { createScreenshotSelector } from "./select_screenshot.js"; -import { registerSubframePointerDownBridge } from "./frame_pointer_bridge.js"; +import { + isExtensionOwnedFrame, + registerSubframePointerDownBridge, +} from "./frame_pointer_bridge.js"; import { getSelection, shouldTranslate, @@ -25,7 +28,7 @@ import { disappearButton, } from "./select_button.js"; -if (!isNativePDFViewer()) { +if (!isExtensionOwnedFrame() && !isNativePDFViewer()) { initSelectTranslate(); } diff --git a/packages/EdgeTranslate/src/content/select/select_button.js b/packages/EdgeTranslate/src/content/select/select_button.js index 07cef328..315adb28 100644 --- a/packages/EdgeTranslate/src/content/select/select_button.js +++ b/packages/EdgeTranslate/src/content/select/select_button.js @@ -21,6 +21,7 @@ const LayerMode = { export function initializeButtonContainer(state, onMouseDown) { const iframeContainer = state.translationButtonContainer; + state.translationButtonContainer.id = BUTTON_ID; document.documentElement.appendChild(state.translationButtonContainer); if (state.translationButtonContainer.contentDocument === null) { state.translationButtonContainer = document.createElement("div"); diff --git a/packages/EdgeTranslate/src/manifest_chrome.json b/packages/EdgeTranslate/src/manifest_chrome.json index 0d14a199..82961f7b 100644 --- a/packages/EdgeTranslate/src/manifest_chrome.json +++ b/packages/EdgeTranslate/src/manifest_chrome.json @@ -111,7 +111,8 @@ "run_at": "document_start", "js": ["./content/select/select.js"], "css": ["./content/select/select.css"], - "all_frames": true + "all_frames": true, + "match_about_blank": true }, { "matches": [""], diff --git a/packages/EdgeTranslate/test/unit/content/display/panel_runtime.test.js b/packages/EdgeTranslate/test/unit/content/display/panel_runtime.test.js index 35f96f1b..22b25af2 100644 --- a/packages/EdgeTranslate/test/unit/content/display/panel_runtime.test.js +++ b/packages/EdgeTranslate/test/unit/content/display/panel_runtime.test.js @@ -274,12 +274,66 @@ describe("panel drag shield", () => { moveablePanel.handlers.resizeStart({ set: jest.fn(), inputEvent: {} }); expect(shield.style.pointerEvents).toBe("auto"); moveablePanel.handlers.resizeEnd({ + width: 300, + height: 400, translate: [0, 0], target: moveablePanel.targetElement, canceled: true, }); expect(shield.style.pointerEvents).toBe("none"); }); + + it("persists the final fixed width when blur cancels a user resize", () => { + const moveablePanel = createMoveablePanelStub(); + const displaySettingRef = { + current: { + type: "fixed", + floatingData: { width: 0.2, height: 0.6 }, + fixedData: { width: 0.2, position: "right" }, + }, + }; + const updateDisplaySetting = jest.fn(); + + attachResizeHandlers({ + moveablePanel, + dragShieldElRef: { current: document.createElement("div") }, + displaySettingRef, + resizePageFlag: { current: true }, + setUsePDFMaskLayer: jest.fn(), + updateDisplaySetting, + }); + + moveablePanel.handlers.resizeStart({ set: jest.fn(), inputEvent: {} }); + moveablePanel.handlers.resize({ + target: moveablePanel.targetElement, + width: 320, + height: 500, + translate: [0, 0], + inputEvent: {}, + }); + moveablePanel.handlers.resizeEnd({ + target: moveablePanel.targetElement, + width: 320, + height: 500, + translate: [0, 0], + canceled: true, + }); + + expect(displaySettingRef.current.fixedData.width).toBe(320 / window.innerWidth); + expect(document.body.style.width).toBe(`${(1 - 320 / window.innerWidth) * 100}%`); + expect(updateDisplaySetting).toHaveBeenCalledTimes(1); + + moveablePanel.handlers.resizeStart({ set: jest.fn() }); + moveablePanel.handlers.resizeEnd({ + target: moveablePanel.targetElement, + width: 400, + height: 500, + translate: [0, 0], + }); + + expect(displaySettingRef.current.fixedData.width).toBe(320 / window.innerWidth); + expect(updateDisplaySetting).toHaveBeenCalledTimes(1); + }); }); function createMoveablePanelStub() { diff --git a/packages/EdgeTranslate/test/unit/content/select/frame_pointer_bridge.test.js b/packages/EdgeTranslate/test/unit/content/select/frame_pointer_bridge.test.js index dd9160be..eac240b1 100644 --- a/packages/EdgeTranslate/test/unit/content/select/frame_pointer_bridge.test.js +++ b/packages/EdgeTranslate/test/unit/content/select/frame_pointer_bridge.test.js @@ -1,4 +1,5 @@ import { + isExtensionOwnedFrame, isExtensionOwnedPointerEvent, registerSubframePointerDownBridge, } from "content/select/frame_pointer_bridge.js"; @@ -13,6 +14,14 @@ describe("subframe pointer bridge", () => { addEventListener.mockRestore(); }); + it("identifies the extension button iframe", () => { + const buttonFrame = document.createElement("iframe"); + buttonFrame.id = "edge-translate-button"; + + expect(isExtensionOwnedFrame(buttonFrame)).toBe(true); + expect(isExtensionOwnedFrame(document.createElement("iframe"))).toBe(false); + }); + it.each([ "edge-translate-button", "edge-translate-button-host", diff --git a/packages/EdgeTranslate/test/unit/content/select/select_button.test.js b/packages/EdgeTranslate/test/unit/content/select/select_button.test.js index ce1c2683..838fdd1b 100644 --- a/packages/EdgeTranslate/test/unit/content/select/select_button.test.js +++ b/packages/EdgeTranslate/test/unit/content/select/select_button.test.js @@ -43,6 +43,21 @@ describe("selection button top layer host", () => { expect(state.translationButtonContainer.style.pointerEvents).toBe("auto"); }); + it("marks the button iframe before attaching its about:blank document", () => { + const state = createButtonState(); + const appendChild = jest.spyOn(document.documentElement, "appendChild"); + let idWhenAttached; + appendChild.mockImplementation((node) => { + if (node === state.translationButtonContainer) idWhenAttached = node.id; + return Node.prototype.appendChild.call(document.documentElement, node); + }); + + initializeButtonContainer(state, jest.fn()); + + expect(idWhenAttached).toBe("edge-translate-button"); + appendChild.mockRestore(); + }); + it("promotes the button host as a popover and keeps a selection snapshot", () => { const { showModal, showPopover } = mockDialogMethods({ clearSelectionOnModal: true }); const state = createButtonState();