diff --git a/src/PlaywrightNative/Chromium/CRPage.cs b/src/PlaywrightNative/Chromium/CRPage.cs
index a3064605..b9f3eccf 100644
--- a/src/PlaywrightNative/Chromium/CRPage.cs
+++ b/src/PlaywrightNative/Chromium/CRPage.cs
@@ -137,7 +137,7 @@ public CRPage(CRSession client, string targetId, CRBrowser browser, ILoggerFacto
// DragManager mirrors upstream crDragDrop: intercept HTML5 drag so custom
// DataTransfer types are preserved without chromium/x-drag-id.
CRDragManager dragManager = new CRDragManager(this);
- _keyboard = new Input.Keyboard(new CRRawKeyboard(_client, dragManager));
+ _keyboard = new Input.Keyboard(new CRRawKeyboard(_client, dragManager, browser.UserAgent.Contains("Macintosh", StringComparison.Ordinal)));
_mouse = new Input.Mouse(new CRRawMouse(_client, dragManager), _keyboard);
_touchscreen = new Input.Touchscreen(new CRRawTouchscreen(_client), _keyboard);
diff --git a/src/PlaywrightNative/Chromium/CRRawKeyboard.cs b/src/PlaywrightNative/Chromium/CRRawKeyboard.cs
index e622d84a..7a343b64 100644
--- a/src/PlaywrightNative/Chromium/CRRawKeyboard.cs
+++ b/src/PlaywrightNative/Chromium/CRRawKeyboard.cs
@@ -14,7 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using PlaywrightNative.Input;
@@ -28,16 +30,19 @@ internal class CRRawKeyboard : IRawKeyboard
{
private readonly CRSession _session;
private readonly CRDragManager _dragManager;
+ private readonly bool _isMac;
///
/// Initializes a new instance of the class.
///
/// The CDP session to send commands on.
/// Chromium drag interceptor.
- public CRRawKeyboard(CRSession session, CRDragManager dragManager)
+ /// Whether the browser runs on macOS and needs editing commands.
+ public CRRawKeyboard(CRSession session, CRDragManager dragManager, bool isMac)
{
_session = session;
_dragManager = dragManager;
+ _isMac = isMac;
}
///
@@ -52,6 +57,7 @@ public async Task KeyDownAsync(IReadOnlyCollection modif
}
string type = string.IsNullOrEmpty(key.Text) ? "rawKeyDown" : "keyDown";
+ string[] commands = CommandsForCode(key.Code, modifiers);
await _session.SendAsync("Input.dispatchKeyEvent", new
{
@@ -59,6 +65,7 @@ public async Task KeyDownAsync(IReadOnlyCollection modif
modifiers = modifiers.ToCdpMask(),
windowsVirtualKeyCode = key.KeyCodeWithoutLocation == 0 ? key.KeyCode : key.KeyCodeWithoutLocation,
code = key.Code,
+ commands,
key = key.Key,
text = key.Text,
unmodifiedText = key.Text,
@@ -92,5 +99,25 @@ public Task InsertTextAsync(string text)
{
return _session.SendAsync("Input.insertText", new { text });
}
+
+ ///
+ /// Returns the macOS editing commands Chromium should run for a key press. Chromium
+ /// skips native key bindings for synthetic events, so shortcuts like Meta+A need them.
+ /// Mirrors upstream crInput.ts _commandsForCode.
+ ///
+ private string[] CommandsForCode(string code, IReadOnlyCollection modifiers)
+ {
+ if (!_isMac)
+ {
+ return Array.Empty();
+ }
+
+ // Commands that insert text are not supported. Drop the trailing ':' to match the
+ // Chromium command names.
+ return MacEditingCommands.Resolve(MacEditingCommands.BuildShortcut(modifiers, code))
+ .Where(command => !command.StartsWith("insert", StringComparison.Ordinal))
+ .Select(command => command[..^1])
+ .ToArray();
+ }
}
}
diff --git a/src/PlaywrightNative/WebKit/MacEditingCommands.cs b/src/PlaywrightNative/Input/MacEditingCommands.cs
similarity index 81%
rename from src/PlaywrightNative/WebKit/MacEditingCommands.cs
rename to src/PlaywrightNative/Input/MacEditingCommands.cs
index 12a787b5..39a1291d 100644
--- a/src/PlaywrightNative/WebKit/MacEditingCommands.cs
+++ b/src/PlaywrightNative/Input/MacEditingCommands.cs
@@ -15,14 +15,16 @@
* limitations under the License.
*/
using System.Collections.Generic;
+using System.Linq;
-namespace PlaywrightNative.WebKit
+namespace PlaywrightNative.Input
{
///
/// Maps a keyboard shortcut (modifiers + physical code, joined with +) to the
- /// macOS editing command(s) WebKit should execute for that key. WebKit applies these named
- /// commands (e.g. insertNewline: for Enter) instead of inserting the raw key text,
- /// so an Enter in a textarea produces a real newline rather than a carriage return.
+ /// macOS editing command(s) the browser should execute for that key. WebKit and Chromium on
+ /// macOS apply these named commands (e.g. insertNewline: for Enter, selectAll:
+ /// for Meta+A) because the native key bindings that would run them are not used for
+ /// synthetic key events.
/// Ported from upstream Playwright's macEditingCommands.ts.
///
internal static class MacEditingCommands
@@ -145,6 +147,44 @@ internal static class MacEditingCommands
["Shift+Meta+KeyZ"] = ["redo:"],
};
+ ///
+ /// Builds the shortcut string for a key press: held modifiers (in
+ /// Shift+Control+Alt+Meta order) followed by the physical key code, joined
+ /// with +. Mirrors upstream wkInput.ts and crInput.ts.
+ ///
+ /// The held modifiers.
+ /// The physical event.code of the key.
+ /// The shortcut string used to look up macOS editing commands.
+ internal static string BuildShortcut(IReadOnlyCollection modifiers, string code)
+ {
+ List parts = new();
+ if (modifiers != null)
+ {
+ if (modifiers.Contains(KeyboardModifier.Shift))
+ {
+ parts.Add("Shift");
+ }
+
+ if (modifiers.Contains(KeyboardModifier.Control))
+ {
+ parts.Add("Control");
+ }
+
+ if (modifiers.Contains(KeyboardModifier.Alt))
+ {
+ parts.Add("Alt");
+ }
+
+ if (modifiers.Contains(KeyboardModifier.Meta))
+ {
+ parts.Add("Meta");
+ }
+ }
+
+ parts.Add(code);
+ return string.Join("+", parts);
+ }
+
///
/// Resolves the macOS editing command(s) for a shortcut, or an empty array when the
/// shortcut has no associated command.
diff --git a/src/PlaywrightNative/WebKit/WKRawKeyboard.cs b/src/PlaywrightNative/WebKit/WKRawKeyboard.cs
index e274e726..973ed08f 100644
--- a/src/PlaywrightNative/WebKit/WKRawKeyboard.cs
+++ b/src/PlaywrightNative/WebKit/WKRawKeyboard.cs
@@ -68,48 +68,10 @@ public static int ToWebKitModifiersMask(IReadOnlyCollection
- /// Builds the macEditingCommands shortcut string for a key press: held modifiers
- /// (in Shift+Control+Alt+Meta order) followed by the physical key code,
- /// joined with +. Mirrors upstream wkInput.ts.
- ///
- /// The held modifiers.
- /// The physical event.code of the key.
- /// The shortcut string used to look up macOS editing commands.
- public static string BuildShortcut(IReadOnlyCollection modifiers, string code)
- {
- List parts = new();
- if (modifiers != null)
- {
- if (modifiers.Contains(Input.KeyboardModifier.Shift))
- {
- parts.Add("Shift");
- }
-
- if (modifiers.Contains(Input.KeyboardModifier.Control))
- {
- parts.Add("Control");
- }
-
- if (modifiers.Contains(Input.KeyboardModifier.Alt))
- {
- parts.Add("Alt");
- }
-
- if (modifiers.Contains(Input.KeyboardModifier.Meta))
- {
- parts.Add("Meta");
- }
- }
-
- parts.Add(code);
- return string.Join("+", parts);
- }
-
///
public Task KeyDownAsync(IReadOnlyCollection modifiers, Input.KeyDefinition key, bool autoRepeat)
{
- string shortcut = BuildShortcut(modifiers, key.Code);
+ string shortcut = MacEditingCommands.BuildShortcut(modifiers, key.Code);
// Linux/Windows WebKit uses native Ctrl+A select-all. The macOS table maps
// Control+KeyA to moveToBeginningOfParagraph, which would steal selectAll.