浏览代码

Merge pull request #11010 from AvaloniaUI/mozilla-hacks-clipboard

Add "readClipboardText" workaround for Mozilla
Max Katz 2 年之前
父节点
当前提交
40b5c3b178

+ 2 - 0
src/Browser/Avalonia.Browser/AvaloniaView.cs

@@ -90,6 +90,8 @@ namespace Avalonia.Browser
 
             _topLevel.Renderer.Start();
 
+            InputHelper.InitializeBackgroundHandlers();
+
             InputHelper.SubscribeKeyEvents(
                 _containerElement,
                 OnKeyDown,

+ 4 - 1
src/Browser/Avalonia.Browser/Interop/InputHelper.cs

@@ -81,7 +81,10 @@ internal static partial class InputHelper
     [JSImport("InputHelper.setBounds", AvaloniaModule.MainModuleName)]
     public static partial void SetBounds(JSObject htmlElement, int x, int y, int width, int height, int caret);
 
-    [JSImport("globalThis.navigator.clipboard.readText")]
+    [JSImport("InputHelper.initializeBackgroundHandlers", AvaloniaModule.MainModuleName)]
+    public static partial void InitializeBackgroundHandlers();
+
+    [JSImport("InputHelper.readClipboardText", AvaloniaModule.MainModuleName)]
     public static partial Task<string> ReadClipboardTextAsync();
 
     [JSImport("globalThis.navigator.clipboard.writeText")]

+ 58 - 1
src/Browser/Avalonia.Browser/webapp/modules/avalonia/input.ts

@@ -19,14 +19,68 @@ enum RawInputModifiers {
     PenBarrelButton = 2048
 }
 
+/*
+* This is a hack to handle Mozilla clipboard events in a more convinient way for framework users.
+* In the browser, events go in order KeyDown -> Paste -> KeyUp.
+* On KeyDown we trigger Avalonia handlers, which might execute readClipboardText.
+* When readClipboardText was executed, we mark ClipboardState as Pending and setup clipboard promise,
+* which will un-handle KeyDown event, basically allowing browser to pass a Paste event properly.
+* On actual Paste event we execute promise callbacks, resuming async operation, and returning pasted text to the app.
+* Note #1, on every KeyUp event we will reset all the state and reject pending promises if any, as this event it expected to come after Paste.
+* Note #2, whole this code will be executed only on legacy browsers like Mozilla, where clipboard.readText is not available.
+* Note #3, with all of these hacks Clipboard.ReadText will still work only on actual "paste" gesture initiated by user.
+* */
+enum ClipboardState {
+    None,
+    Ready,
+    Pending
+}
+
 export class InputHelper {
+    static clipboardState: ClipboardState = ClipboardState.None;
+    static resolveClipboard?: any;
+    static rejectClipboard?: any;
+
+    public static initializeBackgroundHandlers() {
+        if (this.clipboardState !== ClipboardState.None) {
+            return;
+        }
+
+        globalThis.addEventListener("paste", (args: any) => {
+            if (this.clipboardState === ClipboardState.Pending) {
+                this.resolveClipboard(args.clipboardData.getData("text"));
+            }
+        });
+        this.clipboardState = ClipboardState.Ready;
+    }
+
+    public static async readClipboardText(): Promise<string> {
+        if (globalThis.navigator.clipboard.readText) {
+            return await globalThis.navigator.clipboard.readText();
+        } else {
+            try {
+                return await new Promise<any>((resolve, reject) => {
+                    this.clipboardState = ClipboardState.Pending;
+                    this.resolveClipboard = resolve;
+                    this.rejectClipboard = reject;
+                });
+            } finally {
+                this.clipboardState = ClipboardState.Ready;
+                this.resolveClipboard = null;
+                this.rejectClipboard = null;
+            }
+        }
+    }
+
     public static subscribeKeyEvents(
         element: HTMLInputElement,
         keyDownCallback: (code: string, key: string, modifiers: RawInputModifiers) => boolean,
         keyUpCallback: (code: string, key: string, modifiers: RawInputModifiers) => boolean) {
         const keyDownHandler = (args: KeyboardEvent) => {
             if (keyDownCallback(args.code, args.key, this.getModifiers(args))) {
-                args.preventDefault();
+                if (this.clipboardState !== ClipboardState.Pending) {
+                    args.preventDefault();
+                }
             }
         };
         element.addEventListener("keydown", keyDownHandler);
@@ -35,6 +89,9 @@ export class InputHelper {
             if (keyUpCallback(args.code, args.key, this.getModifiers(args))) {
                 args.preventDefault();
             }
+            if (this.rejectClipboard) {
+                this.rejectClipboard();
+            }
         };
 
         element.addEventListener("keyup", keyUpHandler);