@@ -2077,6 +2077,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
20772077 private var bonsplitTabDragUITestRecorder: DispatchSourceTimer?
20782078 private var gotoSplitUITestArrowRecorder: DispatchSourceTimer?
20792079 private var gotoSplitUITestInputSetupGeneration = 0
2080+ private var gotoSplitUITestContentEditableSetupGeneration = 0
20802081 private var gotoSplitUITestObservers: [NSObjectProtocol] = []
20812082 private var didSetupMultiWindowNotificationsUITest = false
20822083 private var didSetupDisplayResolutionUITestDiagnostics = false
@@ -7883,6 +7884,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
78837884 if ProcessInfo.processInfo.environment["CMUX_UI_TEST_GOTO_SPLIT_ARROW_SETUP"] == "1" {
78847885 self.startGotoSplitUITestArrowRecorder(panelId: panel.id)
78857886 }
7887+ if ProcessInfo.processInfo.environment["CMUX_UI_TEST_GOTO_SPLIT_CONTENTEDITABLE_SETUP"] == "1" {
7888+ self.setupContentEditableForGotoSplitUITest(panel: panel)
7889+ }
78867890 return
78877891 }
78887892
@@ -7933,6 +7937,214 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
79337937 attempt()
79347938 }
79357939
7940+ private func setupContentEditableForGotoSplitUITest(panel: BrowserPanel) {
7941+ guard ProcessInfo.processInfo.environment["CMUX_UI_TEST_GOTO_SPLIT_CONTENTEDITABLE_SETUP"] == "1" else {
7942+ return
7943+ }
7944+
7945+ gotoSplitUITestContentEditableSetupGeneration += 1
7946+ let generation = gotoSplitUITestContentEditableSetupGeneration
7947+ let deadline = Date().addingTimeInterval(8.0)
7948+ let script = """
7949+ (() => {
7950+ const snapshot = () => {
7951+ const active = document.activeElement;
7952+ return {
7953+ seeded: false,
7954+ editorId: "",
7955+ activeId: active && typeof active.id === "string" ? active.id : "",
7956+ readyState: String(document.readyState || ""),
7957+ secondaryCenterX: -1,
7958+ secondaryCenterY: -1,
7959+ editorCenterX: -1,
7960+ editorCenterY: -1
7961+ };
7962+ };
7963+ const seed = () => {
7964+ const parent = document.getElementById("cmux-ui-test-focus-container");
7965+ const secondary = document.getElementById("cmux-ui-test-focus-input-secondary");
7966+ if (!document.body || !parent || !secondary) {
7967+ return snapshot();
7968+ }
7969+
7970+ let editor = document.getElementById("cmux-ui-test-contenteditable");
7971+ if (!editor || !editor.tagName || editor.tagName.toLowerCase() !== "div") {
7972+ editor = document.createElement("div");
7973+ editor.id = "cmux-ui-test-contenteditable";
7974+ }
7975+
7976+ editor.setAttribute("contenteditable", "true");
7977+ editor.setAttribute("role", "textbox");
7978+ editor.setAttribute("aria-label", "cmux-ui-test-contenteditable");
7979+ editor.spellcheck = false;
7980+ editor.tabIndex = 0;
7981+ editor.innerHTML = "alpha<br>beta<br>gamma";
7982+ editor.style.display = "block";
7983+ editor.style.minHeight = "84px";
7984+ editor.style.padding = "8px 10px";
7985+ editor.style.border = "1px solid #5f6368";
7986+ editor.style.borderRadius = "6px";
7987+ editor.style.boxSizing = "border-box";
7988+ editor.style.fontSize = "14px";
7989+ editor.style.fontFamily = "system-ui, -apple-system, sans-serif";
7990+ editor.style.background = "white";
7991+ editor.style.color = "black";
7992+ editor.style.whiteSpace = "pre-wrap";
7993+ editor.style.outline = "none";
7994+
7995+ if (editor.parentElement !== parent) {
7996+ parent.appendChild(editor);
7997+ }
7998+
7999+ if (!window.__cmuxContentEditableArrowReport || typeof window.__cmuxContentEditableArrowReport !== "object") {
8000+ window.__cmuxContentEditableArrowReport = {
8001+ down: 0,
8002+ up: 0,
8003+ commandShiftDown: 0,
8004+ commandShiftUp: 0
8005+ };
8006+ }
8007+
8008+ if (!editor.__cmuxContentEditableArrowReportInstalled) {
8009+ editor.__cmuxContentEditableArrowReportInstalled = true;
8010+ editor.addEventListener("keydown", (event) => {
8011+ if (event.key === "ArrowDown") window.__cmuxContentEditableArrowReport.down += 1;
8012+ if (event.key === "ArrowUp") window.__cmuxContentEditableArrowReport.up += 1;
8013+ if (event.key === "ArrowDown" && event.metaKey && event.shiftKey) {
8014+ window.__cmuxContentEditableArrowReport.commandShiftDown += 1;
8015+ }
8016+ if (event.key === "ArrowUp" && event.metaKey && event.shiftKey) {
8017+ window.__cmuxContentEditableArrowReport.commandShiftUp += 1;
8018+ }
8019+ }, true);
8020+ }
8021+
8022+ editor.focus({ preventScroll: true });
8023+ const selection = window.getSelection();
8024+ if (selection) {
8025+ const range = document.createRange();
8026+ range.selectNodeContents(editor);
8027+ range.collapse(false);
8028+ selection.removeAllRanges();
8029+ selection.addRange(range);
8030+ }
8031+
8032+ const secondaryRect = secondary.getBoundingClientRect();
8033+ const editorRect = editor.getBoundingClientRect();
8034+ const active = document.activeElement;
8035+ return {
8036+ seeded: active === editor,
8037+ editorId: editor.id || "",
8038+ activeId: active && typeof active.id === "string" ? active.id : "",
8039+ readyState: String(document.readyState || ""),
8040+ secondaryCenterX: secondaryRect.left + (secondaryRect.width / 2),
8041+ secondaryCenterY: secondaryRect.top + (secondaryRect.height / 2),
8042+ editorCenterX: editorRect.left + (editorRect.width / 2),
8043+ editorCenterY: editorRect.top + (editorRect.height / 2)
8044+ };
8045+ };
8046+ const ready =
8047+ String(document.readyState || "") === "complete" &&
8048+ !!document.body &&
8049+ !!document.getElementById("cmux-ui-test-focus-container") &&
8050+ !!document.getElementById("cmux-ui-test-focus-input-secondary");
8051+
8052+ if (!ready) {
8053+ return snapshot();
8054+ }
8055+
8056+ try {
8057+ return seed();
8058+ } catch (_) {
8059+ return snapshot();
8060+ }
8061+ })();
8062+ """
8063+
8064+ func attempt() {
8065+ guard gotoSplitUITestContentEditableSetupGeneration == generation else { return }
8066+
8067+ panel.webView.evaluateJavaScript(script) { [weak self, weak panel] result, _ in
8068+ guard let self,
8069+ self.gotoSplitUITestContentEditableSetupGeneration == generation,
8070+ let panel else { return }
8071+
8072+ let payload = result as? [String: Any]
8073+ let seeded = (payload?["seeded"] as? Bool) ?? false
8074+ let editorId = (payload?["editorId"] as? String) ?? ""
8075+ let activeId = (payload?["activeId"] as? String) ?? ""
8076+ let readyState = (payload?["readyState"] as? String) ?? ""
8077+ let secondaryCenterX = (payload?["secondaryCenterX"] as? NSNumber)?.doubleValue ?? -1
8078+ let secondaryCenterY = (payload?["secondaryCenterY"] as? NSNumber)?.doubleValue ?? -1
8079+ let editorCenterX = (payload?["editorCenterX"] as? NSNumber)?.doubleValue ?? -1
8080+ let editorCenterY = (payload?["editorCenterY"] as? NSNumber)?.doubleValue ?? -1
8081+ var editorClickOffsetX = -1.0
8082+ var editorClickOffsetY = -1.0
8083+
8084+ if let window = panel.webView.window {
8085+ let webFrame = panel.webView.convert(panel.webView.bounds, to: nil)
8086+ let contentHeight = Double(window.contentView?.bounds.height ?? 0)
8087+ if webFrame.width > 1,
8088+ webFrame.height > 1,
8089+ contentHeight > 1,
8090+ secondaryCenterX >= 0,
8091+ secondaryCenterY >= 0,
8092+ editorCenterX >= 0,
8093+ editorCenterY >= 0 {
8094+ let editorXInContent = Double(webFrame.minX) + editorCenterX
8095+ let editorYInContent = Double(webFrame.maxY) - editorCenterY
8096+ let titlebarHeight = max(0, Double(window.frame.height) - contentHeight)
8097+ editorClickOffsetX = editorXInContent
8098+ editorClickOffsetY = titlebarHeight + (contentHeight - editorYInContent)
8099+ }
8100+ }
8101+
8102+ if seeded,
8103+ !editorId.isEmpty,
8104+ activeId == editorId,
8105+ editorClickOffsetX > 0,
8106+ editorClickOffsetY > 0 {
8107+ self.writeGotoSplitTestData([
8108+ "webContentEditableSeeded": "true",
8109+ "webContentEditableElementId": editorId,
8110+ "webContentEditableActiveElementId": activeId,
8111+ "webContentEditableReadyState": readyState,
8112+ "webContentEditableClickOffsetX": "\(editorClickOffsetX)",
8113+ "webContentEditableClickOffsetY": "\(editorClickOffsetY)"
8114+ ])
8115+ return
8116+ }
8117+
8118+ if Date() < deadline {
8119+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { [weak self] in
8120+ guard let self,
8121+ self.gotoSplitUITestContentEditableSetupGeneration == generation else { return }
8122+ attempt()
8123+ }
8124+ return
8125+ }
8126+
8127+ self.writeGotoSplitTestData([
8128+ "webContentEditableSeeded": "false",
8129+ "webContentEditableElementId": editorId,
8130+ "webContentEditableActiveElementId": activeId,
8131+ "webContentEditableReadyState": readyState,
8132+ "webContentEditableClickOffsetX": "\(editorClickOffsetX)",
8133+ "webContentEditableClickOffsetY": "\(editorClickOffsetY)",
8134+ "setupError":
8135+ "Timed out focusing contenteditable for omnibar restore test " +
8136+ "seeded=\(seeded) editorId=\(editorId) activeId=\(activeId) " +
8137+ "readyState=\(readyState) secondaryCenterX=\(secondaryCenterX) " +
8138+ "secondaryCenterY=\(secondaryCenterY) editorCenterX=\(editorCenterX) " +
8139+ "editorCenterY=\(editorCenterY) editorClickOffsetX=\(editorClickOffsetX) " +
8140+ "editorClickOffsetY=\(editorClickOffsetY)"
8141+ ])
8142+ }
8143+ }
8144+
8145+ attempt()
8146+ }
8147+
79368148 private func startGotoSplitUITestArrowRecorder(panelId: UUID) {
79378149 guard ProcessInfo.processInfo.environment["CMUX_UI_TEST_GOTO_SPLIT_ARROW_SETUP"] == "1" else { return }
79388150
@@ -7964,13 +8176,24 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
79648176 commandShiftDown: 0,
79658177 commandShiftUp: 0
79668178 };
8179+ const contentEditableReport = window.__cmuxContentEditableArrowReport || {
8180+ down: 0,
8181+ up: 0,
8182+ commandShiftDown: 0,
8183+ commandShiftUp: 0
8184+ };
79678185 const active = document.activeElement;
79688186 return {
79698187 installed: !!window.__cmuxArrowKeyReport,
79708188 down: Number(report.down || 0),
79718189 up: Number(report.up || 0),
79728190 commandShiftDown: Number(report.commandShiftDown || 0),
79738191 commandShiftUp: Number(report.commandShiftUp || 0),
8192+ contentEditableInstalled: !!window.__cmuxContentEditableArrowReport,
8193+ contentEditableDown: Number(contentEditableReport.down || 0),
8194+ contentEditableUp: Number(contentEditableReport.up || 0),
8195+ contentEditableCommandShiftDown: Number(contentEditableReport.commandShiftDown || 0),
8196+ contentEditableCommandShiftUp: Number(contentEditableReport.commandShiftUp || 0),
79748197 activeId: active && typeof active.id === "string" ? active.id : "",
79758198 selectionStart: active && typeof active.selectionStart === "number" ? active.selectionStart : null,
79768199 selectionEnd: active && typeof active.selectionEnd === "number" ? active.selectionEnd : null,
@@ -7994,6 +8217,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
79948217 let up = (payload["up"] as? NSNumber)?.intValue ?? 0
79958218 let commandShiftDown = (payload["commandShiftDown"] as? NSNumber)?.intValue ?? 0
79968219 let commandShiftUp = (payload["commandShiftUp"] as? NSNumber)?.intValue ?? 0
8220+ let contentEditableInstalled = (payload["contentEditableInstalled"] as? Bool) ?? false
8221+ let contentEditableDown = (payload["contentEditableDown"] as? NSNumber)?.intValue ?? 0
8222+ let contentEditableUp = (payload["contentEditableUp"] as? NSNumber)?.intValue ?? 0
8223+ let contentEditableCommandShiftDown = (payload["contentEditableCommandShiftDown"] as? NSNumber)?.intValue ?? 0
8224+ let contentEditableCommandShiftUp = (payload["contentEditableCommandShiftUp"] as? NSNumber)?.intValue ?? 0
79978225 let activeId = (payload["activeId"] as? String) ?? ""
79988226 let selectionStart = (payload["selectionStart"] as? NSNumber)?.intValue
79998227 let selectionEnd = (payload["selectionEnd"] as? NSNumber)?.intValue
@@ -8011,6 +8239,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
80118239 "browserArrowSelectionStart": selectionStart.map(String.init) ?? "",
80128240 "browserArrowSelectionEnd": selectionEnd.map(String.init) ?? "",
80138241 "browserArrowReadyState": readyState,
8242+ "browserContentEditableInstalled": contentEditableInstalled ? "true" : "false",
8243+ "browserContentEditableDownCount": "\(contentEditableDown)",
8244+ "browserContentEditableUpCount": "\(contentEditableUp)",
8245+ "browserContentEditableCommandShiftDownCount": "\(contentEditableCommandShiftDown)",
8246+ "browserContentEditableCommandShiftUpCount": "\(contentEditableCommandShiftUp)",
8247+ "browserContentEditableActiveElementId": activeId,
8248+ "browserContentEditableReadyState": readyState,
80148249 "browserArrowFirstResponderType": firstResponderType,
80158250 "browserArrowFirstResponderIsFieldEditor": firstResponderIsFieldEditor ? "true" : "false",
80168251 "browserArrowFirstResponderIsCmuxWebView": firstResponderIsCmuxWebView ? "true" : "false",
0 commit comments