Skip to content

Commit a8c6c17

Browse files
committed
fix: resolve cross-platform Actions failures and update Pro pin
1 parent 7775432 commit a8c6c17

5 files changed

Lines changed: 106 additions & 22 deletions

File tree

‎src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,9 @@ define(function (require, exports, module) {
137137
if (!data || data.type !== "MDVIEWR_EVENT") {
138138
return;
139139
}
140-
// Verify message is from our iframe
141-
if (_$iframe && _$iframe[0] && event.source !== _$iframe[0].contentWindow) {
140+
// Verify both the viewer window and its expected (normally opaque) origin.
141+
if (!_$iframe || !_$iframe[0] || event.source !== _$iframe[0].contentWindow ||
142+
event.origin !== _viewerOrigin(_$iframe[0])) {
142143
return;
143144
}
144145

@@ -1384,6 +1385,16 @@ define(function (require, exports, module) {
13841385
_revealPendingSelection();
13851386
}
13861387

1388+
/**
1389+
* The bundled viewer is served with Phoenix, but its production sandbox gives it an opaque origin.
1390+
* @param {HTMLIFrameElement} iframe Markdown viewer frame.
1391+
* @return {string} Expected message origin; "null" for a sandbox without allow-same-origin.
1392+
*/
1393+
function _viewerOrigin(iframe) {
1394+
return iframe.hasAttribute("sandbox") && !iframe.sandbox.contains("allow-same-origin") ?
1395+
"null" : window.location.origin;
1396+
}
1397+
13871398
/**
13881399
* Retrieve rendered snapshot lines to clarify a previously attached Markdown selection.
13891400
* @param {Object} params Attachment selectionId, rendered line range, and optional per-line character limit.
@@ -1395,6 +1406,7 @@ define(function (require, exports, module) {
13951406
if (!frame) {
13961407
return Promise.resolve({error: "Markdown selection snapshot unavailable. Attach the selection again."});
13971408
}
1409+
const origin = _viewerOrigin(_mdIframeRef);
13981410
return new Promise(resolve => {
13991411
const requestId = crypto.randomUUID();
14001412
const timeout = setTimeout(() => finish({error: "Markdown preview did not respond."}), 3000);
@@ -1405,13 +1417,14 @@ define(function (require, exports, module) {
14051417
}
14061418
function onResult(event) {
14071419
const data = event.data;
1408-
if (event.source === frame && data && data.type === "MDVIEWR_EVENT" &&
1420+
if (event.source === frame && event.origin === origin && data && data.type === "MDVIEWR_EVENT" &&
14091421
data.eventName === "mdviewrRenderedLines" && data.requestId === requestId) {
14101422
finish(data.result);
14111423
}
14121424
}
14131425
window.addEventListener("message", onResult);
1414-
frame.postMessage({type: "MDVIEWR_RENDERED_LINES", requestId, params}, "*");
1426+
// Opaque sandbox origins cannot be named as a postMessage target.
1427+
frame.postMessage({type: "MDVIEWR_RENDERED_LINES", requestId, params}, origin === "null" ? "*" : origin);
14151428
});
14161429
}
14171430

‎src/features/SelectionViewManager.js‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -595,16 +595,24 @@ define(function (require, exports, module) {
595595
}
596596
}
597597

598+
/** Dismiss on editor viewport scrolling, except while workspace layout is being restored. */
599+
function onEditorScroll() {
600+
if (resizingPanels.size || (popoverState && popoverState.layoutPending)) { return; }
601+
hidePreview(false);
602+
}
603+
598604
function onActiveEditorChange(_event, current, previous) {
599605
// Hide preview when editor changes
600606
hidePreview();
601607

602608
if (previous && previous.document) {
603609
previous.document.off("change", hidePreview);
610+
previous.off("scroll", onEditorScroll);
604611
}
605612

606613
if (current && current.document) {
607614
current.document.on("change", hidePreview);
615+
current.on("scroll", onEditorScroll);
608616
}
609617
}
610618

@@ -618,12 +626,10 @@ define(function (require, exports, module) {
618626
enabled = _enabled;
619627
let editorHolder = $("#editor-holder")[0];
620628
if (enabled) {
621-
// Note: listening to "scroll" also catches text edits, which bubble a scroll
622-
// event up from the hidden text area. This means
623-
// we auto-hide on text edit, which is probably actually a good thing.
629+
// Editor scroll events exclude Firefox's hidden input scrolling on selection/focus.
630+
// Text edits already dismiss through the document change listener.
624631
editorHolder.addEventListener("mouseup", handleMouseUp, true);
625632
editorHolder.addEventListener("mousemove", _processMouseMove, true);
626-
editorHolder.addEventListener("scroll", hidePreview, true);
627633

628634
// Setup doc "change" listener
629635
onActiveEditorChange(null, EditorManager.getActiveEditor(), null);
@@ -632,7 +638,6 @@ define(function (require, exports, module) {
632638
} else {
633639
editorHolder.removeEventListener("mouseup", handleMouseUp, true);
634640
editorHolder.removeEventListener("mousemove", _processMouseMove, true);
635-
editorHolder.removeEventListener("scroll", hidePreview, true);
636641

637642
// Cleanup doc "change" listener
638643
onActiveEditorChange(null, null, EditorManager.getActiveEditor());

‎test/spec/SelectionViewManager-test.js‎

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -361,12 +361,13 @@ define(function (require, exports, module) {
361361
const scroll = editor.getScrollPos();
362362
SelectionViewManager.registerSelectionViewProvider(provider, ["all"]);
363363
try {
364-
editor.setSize(null, 90);
365-
editor.setSelection({line: 2, ch: 0}, {line: 2, ch: 6});
364+
// Keep both vertical gaps too small for the popup, including with smaller editor fonts.
365+
editor.setSize(null, 70);
366+
editor.setSelection({line: 0, ch: 0}, {line: 0, ch: 6});
366367
editor.setScrollPos(0, 0);
367-
await showPopoverAtPos(2, 6);
368+
await showPopoverAtPos(0, 6);
368369
const popup = getBounds(testWindow.$("#selection-view-container"));
369-
expect(popup.left).toBeGreaterThan(editor.charCoords({line: 2, ch: 6}).left);
370+
expect(popup.left).toBeGreaterThan(editor.charCoords({line: 0, ch: 6}).left);
370371
expect(boundsInsideWindow(testWindow.$("#selection-view-container"))).toBe(true);
371372
} finally {
372373
SelectionViewManager.hidePreview();
@@ -407,6 +408,9 @@ define(function (require, exports, module) {
407408
SelectionViewManager.registerSelectionViewProvider(provider, ["all"]);
408409
try {
409410
editor.setSelection({line: 0, ch: 0}, {line: 10, ch: 0});
411+
editor.setScrollPos(0, 0);
412+
// Finish the programmatic selection's scroll before simulating the user's mouseup.
413+
await new Promise(resolve => testWindow.requestAnimationFrame(resolve));
410414
const point = editor.charCoords({line: 4, ch: 3});
411415
const root = editor.getRootElement();
412416
root.dispatchEvent(new testWindow.MouseEvent("mouseup", {bubbles: true, buttons: 0,
@@ -422,6 +426,35 @@ define(function (require, exports, module) {
422426
}
423427
});
424428

429+
it("ignores scroll events from the hidden selection input", async function () {
430+
SelectionViewManager.registerSelectionViewProvider(provider, ["all"]);
431+
try {
432+
editor.setSelection({line: 0, ch: 0}, {line: 10, ch: 0});
433+
await showPopoverAtPos(4, 0);
434+
editor.getRootElement().querySelector("textarea").dispatchEvent(new testWindow.Event("scroll"));
435+
expect(SelectionViewManager.isSelectionViewShown()).toBe(true);
436+
} finally {
437+
SelectionViewManager.hidePreview();
438+
SelectionViewManager.removeSelectionViewProvider(provider, ["all"]);
439+
}
440+
});
441+
442+
it("dismisses when the editor viewport actually scrolls", async function () {
443+
const scroll = editor.getScrollPos();
444+
SelectionViewManager.registerSelectionViewProvider(provider, ["all"]);
445+
try {
446+
editor.setSelection({line: 0, ch: 0}, {line: 10, ch: 0});
447+
editor.setScrollPos(0, 0);
448+
await showPopoverAtPos(4, 0);
449+
editor.setScrollPos(0, 100);
450+
await awaitsFor(() => !SelectionViewManager.isSelectionViewShown(), "popup dismissed on scroll");
451+
} finally {
452+
SelectionViewManager.hidePreview();
453+
SelectionViewManager.removeSelectionViewProvider(provider, ["all"]);
454+
editor.setScrollPos(scroll.x, scroll.y);
455+
}
456+
});
457+
425458
it("keeps its anchor when the mouse moves and provider content updates", async function () {
426459
SelectionViewManager.registerSelectionViewProvider(provider, ["all"]);
427460
try {
@@ -459,9 +492,13 @@ define(function (require, exports, module) {
459492
SelectionViewManager.registerSelectionViewProvider(provider, ["all"]);
460493
SelectionViewManager.hidePreview();
461494
try {
462-
if (action !== "opens") { panel.show(); }
495+
if (action !== "opens") {
496+
panel.show();
497+
workspace.setPluginPanelWidth(250);
498+
}
463499
editor.setSelection({line: 7, ch: 0}, {line: 10, ch: 0});
464500
editor.setScrollPos(0, 0);
501+
await new Promise(resolve => testWindow.requestAnimationFrame(resolve));
465502
await showPopoverAtPos(7, 0);
466503
const popup = testWindow.$("#selection-view-container");
467504
const content = popup.find("#blinker-fluid")[0];
@@ -471,13 +508,12 @@ define(function (require, exports, module) {
471508
if (action === "resizes") { workspace.setPluginPanelWidth(400); }
472509
if (action === "closes") { panel.hide(); }
473510
expect(SelectionViewManager.isSelectionViewShown()).toBe(false);
474-
await awaitsFor(() => {
475-
const bounds = getBounds(popup);
476-
const root = editor.getRootElement().getBoundingClientRect();
477-
return SelectionViewManager.isSelectionViewShown() &&
478-
Math.abs(bounds.right - before) > 30 &&
479-
Math.abs((bounds.right - before) - (root.right - beforeEditorRight)) < 2;
480-
}, "popup follows the resized editor", 3000);
511+
await awaitsFor(() => SelectionViewManager.isSelectionViewShown(),
512+
"popup restored after editor layout", 3000);
513+
const bounds = getBounds(popup);
514+
const root = editor.getRootElement().getBoundingClientRect();
515+
expect(Math.abs(bounds.right - before)).toBeGreaterThan(30);
516+
expect(Math.abs((bounds.right - before) - (root.right - beforeEditorRight))).toBeLessThan(2);
481517
expect(boundsInsideWindow(popup)).toBe(true);
482518
expect(popup.find("#blinker-fluid")[0]).toBe(content);
483519
} finally {

‎test/spec/md-ask-ai-integ-test.js‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ define(function (require, exports, module) {
8181
const previousCount = received.length;
8282
const doc = frame.contentDocument;
8383
const element = doc.querySelectorAll("#viewer-content " + selector)[occurrence];
84+
// File switches and large re-renders queue scroll restoration. A real user selects
85+
// after that layout; otherwise its late scroll dismisses the freshly opened toolbar.
86+
element.scrollIntoView({block: "nearest"});
87+
await new Promise(resolve => frame.contentWindow.requestAnimationFrame(resolve));
8488
const walker = doc.createTreeWalker(element, win.NodeFilter.SHOW_TEXT);
8589
let node;
8690
while ((node = walker.nextNode())) { if (node.data.includes(text)) { break; } }
@@ -247,6 +251,32 @@ define(function (require, exports, module) {
247251
const result = await sync.getRenderedMdSelectionFollowUp({selectionId: "missing", lineStart: 1, lineEnd: 3});
248252
expect(result.error).toContain("expired");
249253
});
254+
["origin", "source"].forEach(function (field) {
255+
it("ignores a snapshot reply from an unrelated " + field, async function () {
256+
const selection = await attach("strong", "bold words", 2, 8);
257+
const viewer = frame.contentWindow;
258+
const onRequest = function (event) {
259+
const message = event.data;
260+
if (message.type === "MDVIEWR_RENDERED_LINES") {
261+
win.dispatchEvent(new win.MessageEvent("message", {
262+
source: field === "source" ? win : viewer,
263+
origin: field === "origin" ? "https://unrelated.invalid" : win.location.origin,
264+
data: {type: "MDVIEWR_EVENT", eventName: "mdviewrRenderedLines",
265+
requestId: message.requestId, result: {error: "unrelated reply"}}
266+
}));
267+
}
268+
};
269+
viewer.addEventListener("message", onRequest);
270+
try {
271+
const result = await sync.getRenderedMdSelectionFollowUp({selectionId: selection.selectionId,
272+
lineStart: selection.rendered.startLine, lineEnd: selection.rendered.endLine});
273+
expect(result.error).toBeUndefined();
274+
expect(result.lines[0].text).toContain("⟦ld wor⟧");
275+
} finally {
276+
viewer.removeEventListener("message", onRequest);
277+
}
278+
});
279+
});
250280
/** Replace only this spec's unsaved buffer and wait for the real preview render. */
251281
async function setSource(source) {
252282
editor.document.setText(source);

‎tracking-repos.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"phoenixPro": {
3-
"commitID": "4187429a957a91180496b206eb7f076ebbdcc24a"
3+
"commitID": "f9f0b5afa4ed7292e1c77afc17b5ac25bc601b28"
44
}
55
}

0 commit comments

Comments
 (0)