From ae5bd3b516a5bf10bf796cc51c61f1aaa28be3d9 Mon Sep 17 00:00:00 2001 From: jsakamoto Date: Sat, 13 Apr 2024 22:17:02 +0900 Subject: [PATCH] Fix: Disposing immediately after "CreateContext" will not work. --- HotKeys2/HotKeysContext.cs | 74 +++++++++++++++------------------- HotKeys2/script.js | 2 + HotKeys2/script.ts | 1 + HotKeys2/wwwroot/script.min.js | 2 +- 4 files changed, 36 insertions(+), 43 deletions(-) diff --git a/HotKeys2/HotKeysContext.cs b/HotKeys2/HotKeysContext.cs index d0b9cb0..2d2ae33 100644 --- a/HotKeys2/HotKeysContext.cs +++ b/HotKeys2/HotKeysContext.cs @@ -18,6 +18,8 @@ public partial class HotKeysContext : IDisposable private readonly ILogger _Logger; + private bool _IsDisposed = false; + /// /// Initialize a new instance of the HotKeysContext class. /// @@ -306,7 +308,9 @@ public HotKeysContext Add(ModKey modifiers, Key key, FuncThis context. private HotKeysContext AddInternal(ModKey modifiers, Key key, Func action, IHandleEvent? ownerOfAction, HotKeyOptions options) { - lock (this.Keys) this.Keys.Add(this.Register(new HotKeyEntryByKey(this._Logger, modifiers, key, action, ownerOfAction, options))); + var hotkeyEntry = new HotKeyEntryByKey(this._Logger, modifiers, key, action, ownerOfAction, options); + lock (this.Keys) this.Keys.Add(hotkeyEntry); + var _ = this.RegisterAsync(hotkeyEntry); return this; } @@ -589,59 +593,44 @@ public HotKeysContext Add(ModCode modifiers, Code code, FuncThis context. private HotKeysContext AddInternal(ModCode modifiers, Code code, Func action, IHandleEvent? ownerOfAction, HotKeyOptions options) { - lock (this.Keys) this.Keys.Add(this.Register(new HotKeyEntryByCode(this._Logger, modifiers, code, action, ownerOfAction, options))); + var hotkeyEntry = new HotKeyEntryByCode(this._Logger, modifiers, code, action, ownerOfAction, options); + lock (this.Keys) this.Keys.Add(hotkeyEntry); + var _ = this.RegisterAsync(hotkeyEntry); return this; } // =============================================================================================== - private HotKeyEntry Register(HotKeyEntry hotKeyEntry) + private async ValueTask RegisterAsync(HotKeyEntry hotKeyEntry) { - this._AttachTask.ContinueWith(t => - { - if (t.IsCompleted && !t.IsFaulted) - { - return t.Result.InvokeAsync( - "Toolbelt.Blazor.HotKeys2.register", - hotKeyEntry._ObjectRef, hotKeyEntry.Mode, hotKeyEntry._Modifiers, hotKeyEntry._KeyEntry, hotKeyEntry.Exclude, hotKeyEntry.ExcludeSelector).AsTask(); - } - else - { - var tcs = new TaskCompletionSource(); - tcs.TrySetException(t.Exception?.InnerExceptions ?? new[] { new Exception() }.AsEnumerable()); - return tcs.Task; - } - }) - .Unwrap() - .ContinueWith(t => + await this.InvokeJsSafeAsync(async () => { - if (!t.IsCanceled && !t.IsFaulted) { hotKeyEntry.Id = t.Result; } + var module = await this._AttachTask; + if (this._IsDisposed) return; + + hotKeyEntry.Id = await module.InvokeAsync( + "Toolbelt.Blazor.HotKeys2.register", + hotKeyEntry._ObjectRef, hotKeyEntry.Mode, hotKeyEntry._Modifiers, hotKeyEntry._KeyEntry, hotKeyEntry.Exclude, hotKeyEntry.ExcludeSelector); }); - return hotKeyEntry; } - private void Unregister(HotKeyEntry hotKeyEntry) + private async ValueTask UnregisterAsync(HotKeyEntry hotKeyEntry) { - if (hotKeyEntry.Id == -1) return; - - this._AttachTask.ContinueWith(t => - { - if (t.IsCompleted && !t.IsFaulted) - { - return t.Result.InvokeVoidAsync("Toolbelt.Blazor.HotKeys2.unregister", hotKeyEntry.Id).AsTask(); - } - else - { - var tcs = new TaskCompletionSource(); - tcs.TrySetException(t.Exception?.InnerExceptions ?? new[] { new Exception() }.AsEnumerable()); - return tcs.Task as Task; - } - }) - .ContinueWith(t => + await this.InvokeJsSafeAsync(async () => { - hotKeyEntry.Dispose(); + var module = await this._AttachTask; + await module.InvokeVoidAsync("Toolbelt.Blazor.HotKeys2.unregister", hotKeyEntry.Id); }); + + await this.InvokeJsSafeAsync(() => { hotKeyEntry.Dispose(); return ValueTask.CompletedTask; }); + } + + private async ValueTask InvokeJsSafeAsync(Func action) + { + try { await action(); } + catch (JSDisconnectedException) { } // Ignore this exception because it is thrown when the user navigates to another page. + catch (Exception ex) { this._Logger.LogError(ex, ex.Message); } } private const string _AMBIGUOUS_PARAMETER_EXCEPTION_MESSAGE = "Specified parameters are ambiguous to identify the single hotkey entry that should be removed."; @@ -744,7 +733,7 @@ public HotKeysContext Remove(Func, IEnumerable, IEnumerable public void Dispose() { + this._IsDisposed = true; foreach (var entry in this.Keys) { - this.Unregister(entry); + var _ = this.UnregisterAsync(entry); } this.Keys.Clear(); } diff --git a/HotKeys2/script.js b/HotKeys2/script.js index 9d0a524..3cfd398 100644 --- a/HotKeys2/script.js +++ b/HotKeys2/script.js @@ -26,6 +26,8 @@ export var Toolbelt; return id; }; HotKeys2.unregister = (id) => { + if (id === -1) + return; hotKeyEntries.delete(id); }; const convertToKeyNameMap = { diff --git a/HotKeys2/script.ts b/HotKeys2/script.ts index ddee1db..e6371ec 100644 --- a/HotKeys2/script.ts +++ b/HotKeys2/script.ts @@ -48,6 +48,7 @@ } export const unregister = (id: number): void => { + if (id === -1) return; hotKeyEntries.delete(id); } diff --git a/HotKeys2/wwwroot/script.min.js b/HotKeys2/wwwroot/script.min.js index b80f3a4..199f480 100644 --- a/HotKeys2/wwwroot/script.min.js +++ b/HotKeys2/wwwroot/script.min.js @@ -1 +1 @@ -export var Toolbelt;(function(n){var t;(function(n){var t;(function(n){class f{constructor(n,t,i,r,u,f){this.dotNetObj=n;this.mode=t;this.modifiers=i;this.keyEntry=r;this.exclude=u;this.excludeSelector=f}action(){this.dotNetObj.invokeMethodAsync("InvokeAction")}}let e=0;const t=new Map;n.register=(n,i,r,u,o,s)=>{const h=e++,c=new f(n,i,r,u,o,s);return t.set(h,c),h};n.unregister=n=>{t.delete(n)};const o={OS:"Meta",Decimal:"Period"},s=n=>o[n.key]||n.key,i="OnKeyDown";n.attach=(n,t)=>{document.addEventListener("keydown",r=>{if(typeof r.altKey!="undefined"){const u=(r.shiftKey?1:0)+(r.ctrlKey?2:0)+(r.altKey?4:0)+(r.metaKey?8:0),f=s(r),e=r.code,o=r.target,c=o.tagName,l=o.getAttribute("type"),a=h(u,f,e,o,c,l),v=t===!0?n.invokeMethod(i,u,c,l,f,e):!1;(a||v)&&r.preventDefault();t===!1&&n.invokeMethodAsync(i,u,c,l,f,e)}})};const h=(n,i,r,u,f,e)=>{let o=!1;return t.forEach(t=>{const l=t.mode===1,a=l?r:i,s=t.keyEntry;if(s===a){const v=l?n:n&65534;let h=l?t.modifiers:t.modifiers&65534;(s.startsWith("Shift")&&l&&(h|=1),s.startsWith("Control")&&(h|=2),s.startsWith("Alt")&&(h|=4),s.startsWith("Meta")&&(h|=8),v===h)&&(c(t,u,f,e)||(o=!0,t.action()))}}),o},r=["button","checkbox","color","file","image","radio","range","reset","submit",],u="INPUT",c=(n,t,i,f)=>(n.exclude&1)!=0&&i===u&&r.every(n=>n!==f)?!0:(n.exclude&2)!=0&&i===u&&r.some(n=>n===f)?!0:(n.exclude&4)!=0&&i==="TEXTAREA"?!0:(n.exclude&8)!=0&&t.isContentEditable?!0:n.excludeSelector!==""&&t.matches(n.excludeSelector)?!0:!1})(t=n.HotKeys2||(n.HotKeys2={}))})(t=n.Blazor||(n.Blazor={}))})(Toolbelt||(Toolbelt={})); \ No newline at end of file +export var Toolbelt;(function(n){var t;(function(n){var t;(function(n){class f{constructor(n,t,i,r,u,f){this.dotNetObj=n;this.mode=t;this.modifiers=i;this.keyEntry=r;this.exclude=u;this.excludeSelector=f}action(){this.dotNetObj.invokeMethodAsync("InvokeAction")}}let e=0;const t=new Map;n.register=(n,i,r,u,o,s)=>{const h=e++,c=new f(n,i,r,u,o,s);return t.set(h,c),h};n.unregister=n=>{n!==-1&&t.delete(n)};const o={OS:"Meta",Decimal:"Period"},s=n=>o[n.key]||n.key,i="OnKeyDown";n.attach=(n,t)=>{document.addEventListener("keydown",r=>{if(typeof r.altKey!="undefined"){const u=(r.shiftKey?1:0)+(r.ctrlKey?2:0)+(r.altKey?4:0)+(r.metaKey?8:0),f=s(r),e=r.code,o=r.target,c=o.tagName,l=o.getAttribute("type"),a=h(u,f,e,o,c,l),v=t===!0?n.invokeMethod(i,u,c,l,f,e):!1;(a||v)&&r.preventDefault();t===!1&&n.invokeMethodAsync(i,u,c,l,f,e)}})};const h=(n,i,r,u,f,e)=>{let o=!1;return t.forEach(t=>{const l=t.mode===1,a=l?r:i,s=t.keyEntry;if(s===a){const v=l?n:n&65534;let h=l?t.modifiers:t.modifiers&65534;(s.startsWith("Shift")&&l&&(h|=1),s.startsWith("Control")&&(h|=2),s.startsWith("Alt")&&(h|=4),s.startsWith("Meta")&&(h|=8),v===h)&&(c(t,u,f,e)||(o=!0,t.action()))}}),o},r=["button","checkbox","color","file","image","radio","range","reset","submit",],u="INPUT",c=(n,t,i,f)=>(n.exclude&1)!=0&&i===u&&r.every(n=>n!==f)?!0:(n.exclude&2)!=0&&i===u&&r.some(n=>n===f)?!0:(n.exclude&4)!=0&&i==="TEXTAREA"?!0:(n.exclude&8)!=0&&t.isContentEditable?!0:n.excludeSelector!==""&&t.matches(n.excludeSelector)?!0:!1})(t=n.HotKeys2||(n.HotKeys2={}))})(t=n.Blazor||(n.Blazor={}))})(Toolbelt||(Toolbelt={})); \ No newline at end of file