diff --git a/lib/TweetLib.Browser.CEF/Dialogs/FileDialogType.cs b/lib/TweetLib.Browser.CEF/Dialogs/FileDialogType.cs
new file mode 100644
index 00000000..58297465
--- /dev/null
+++ b/lib/TweetLib.Browser.CEF/Dialogs/FileDialogType.cs
@@ -0,0 +1,7 @@
+namespace TweetLib.Browser.CEF.Dialogs {
+	public enum FileDialogType {
+		Open,
+		OpenMultiple,
+		Other
+	}
+}
diff --git a/lib/TweetLib.Browser.CEF/Dialogs/JsDialogType.cs b/lib/TweetLib.Browser.CEF/Dialogs/JsDialogType.cs
new file mode 100644
index 00000000..f6d83560
--- /dev/null
+++ b/lib/TweetLib.Browser.CEF/Dialogs/JsDialogType.cs
@@ -0,0 +1,8 @@
+namespace TweetLib.Browser.CEF.Dialogs {
+	public enum JsDialogType {
+		Alert,
+		Confirm,
+		Prompt,
+		Unknown
+	}
+}
diff --git a/lib/TweetLib.Browser.CEF/Dialogs/MessageDialogType.cs b/lib/TweetLib.Browser.CEF/Dialogs/MessageDialogType.cs
new file mode 100644
index 00000000..9bf5a00d
--- /dev/null
+++ b/lib/TweetLib.Browser.CEF/Dialogs/MessageDialogType.cs
@@ -0,0 +1,9 @@
+namespace TweetLib.Browser.CEF.Dialogs {
+	public enum MessageDialogType {
+		None,
+		Question,
+		Information,
+		Warning,
+		Error
+	}
+}
diff --git a/lib/TweetLib.Browser.CEF/Interfaces/IFileDialogCallbackAdapter.cs b/lib/TweetLib.Browser.CEF/Interfaces/IFileDialogCallbackAdapter.cs
new file mode 100644
index 00000000..c1759ee7
--- /dev/null
+++ b/lib/TweetLib.Browser.CEF/Interfaces/IFileDialogCallbackAdapter.cs
@@ -0,0 +1,7 @@
+namespace TweetLib.Browser.CEF.Interfaces {
+	public interface IFileDialogCallbackAdapter<T> {
+		void Continue(T callback, int selectedAcceptFilter, string[] filePaths);
+		void Cancel(T callback);
+		void Dispose(T callback);
+	}
+}
diff --git a/lib/TweetLib.Browser.CEF/Interfaces/IFileDialogOpener.cs b/lib/TweetLib.Browser.CEF/Interfaces/IFileDialogOpener.cs
new file mode 100644
index 00000000..fb449091
--- /dev/null
+++ b/lib/TweetLib.Browser.CEF/Interfaces/IFileDialogOpener.cs
@@ -0,0 +1,8 @@
+using System;
+using System.Collections.Generic;
+
+namespace TweetLib.Browser.CEF.Interfaces {
+	public interface IFileDialogOpener {
+		void OpenFile(string title, bool multiple, List<string> supportedExtensions, Action<string[]> onAccepted, Action onCancelled);
+	}
+}
diff --git a/lib/TweetLib.Browser.CEF/Interfaces/IJsDialogCallbackAdapter.cs b/lib/TweetLib.Browser.CEF/Interfaces/IJsDialogCallbackAdapter.cs
new file mode 100644
index 00000000..3fbc0180
--- /dev/null
+++ b/lib/TweetLib.Browser.CEF/Interfaces/IJsDialogCallbackAdapter.cs
@@ -0,0 +1,6 @@
+namespace TweetLib.Browser.CEF.Interfaces {
+	public interface IJsDialogCallbackAdapter<T> {
+		void Continue(T callback, bool success, string? userInput = null);
+		void Dispose(T callback);
+	}
+}
diff --git a/lib/TweetLib.Browser.CEF/Interfaces/IJsDialogOpener.cs b/lib/TweetLib.Browser.CEF/Interfaces/IJsDialogOpener.cs
new file mode 100644
index 00000000..2522cb72
--- /dev/null
+++ b/lib/TweetLib.Browser.CEF/Interfaces/IJsDialogOpener.cs
@@ -0,0 +1,10 @@
+using System;
+using TweetLib.Browser.CEF.Dialogs;
+
+namespace TweetLib.Browser.CEF.Interfaces {
+	public interface IJsDialogOpener {
+		void Alert(MessageDialogType type, string title, string message, Action<bool> callback);
+		void Confirm(MessageDialogType type, string title, string message, Action<bool> callback);
+		void Prompt(MessageDialogType type, string title, string message, Action<bool, string> callback);
+	}
+}
diff --git a/lib/TweetLib.Browser.CEF/Logic/DialogHandlerLogic.cs b/lib/TweetLib.Browser.CEF/Logic/DialogHandlerLogic.cs
new file mode 100644
index 00000000..920bcd3e
--- /dev/null
+++ b/lib/TweetLib.Browser.CEF/Logic/DialogHandlerLogic.cs
@@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using TweetLib.Browser.CEF.Dialogs;
+using TweetLib.Browser.CEF.Interfaces;
+using TweetLib.Utils.Static;
+
+namespace TweetLib.Browser.CEF.Logic {
+	public sealed class DialogHandlerLogic<TCallback> {
+		private readonly IFileDialogOpener fileDialogOpener;
+		private readonly IFileDialogCallbackAdapter<TCallback> callbackAdapter;
+
+		public DialogHandlerLogic(IFileDialogOpener fileDialogOpener, IFileDialogCallbackAdapter<TCallback> callbackAdapter) {
+			this.fileDialogOpener = fileDialogOpener;
+			this.callbackAdapter = callbackAdapter;
+		}
+
+		public bool OnFileDialog(FileDialogType type, IEnumerable<string> acceptFilters, TCallback callback) {
+			if (type is FileDialogType.Open or FileDialogType.OpenMultiple) {
+				var multiple = type == FileDialogType.OpenMultiple;
+				var supportedExtensions = acceptFilters.SelectMany(ParseFileType).Where(static filter => !string.IsNullOrEmpty(filter)).ToList();
+
+				fileDialogOpener.OpenFile("Open Files", multiple, supportedExtensions, files => {
+					string ext = Path.GetExtension(files[0])!.ToLower();
+					callbackAdapter.Continue(callback, supportedExtensions.FindIndex(filter => ParseFileType(filter).Contains(ext)), files);
+					callbackAdapter.Dispose(callback);
+				}, () => {
+					callbackAdapter.Cancel(callback);
+					callbackAdapter.Dispose(callback);
+				});
+
+				return true;
+			}
+			else {
+				callbackAdapter.Dispose(callback);
+				return false;
+			}
+		}
+
+		private static IEnumerable<string> ParseFileType(string type) {
+			if (string.IsNullOrEmpty(type)) {
+				return StringUtils.EmptyArray;
+			}
+
+			if (type[0] == '.') {
+				return new string[] { type };
+			}
+
+			string[] extensions = type switch {
+				"image/jpeg"      => new string[] { ".jpg", ".jpeg" },
+				"image/png"       => new string[] { ".png" },
+				"image/gif"       => new string[] { ".gif" },
+				"image/webp"      => new string[] { ".webp" },
+				"video/mp4"       => new string[] { ".mp4" },
+				"video/quicktime" => new string[] { ".mov", ".qt" },
+				_                 => StringUtils.EmptyArray
+			};
+
+			if (extensions.Length == 0) {
+				Debug.WriteLine("Unknown file type: " + type);
+				Debugger.Break();
+			}
+
+			return extensions;
+		}
+	}
+}
diff --git a/lib/TweetLib.Browser.CEF/Logic/JsDialogHandlerLogic.cs b/lib/TweetLib.Browser.CEF/Logic/JsDialogHandlerLogic.cs
new file mode 100644
index 00000000..3309fffc
--- /dev/null
+++ b/lib/TweetLib.Browser.CEF/Logic/JsDialogHandlerLogic.cs
@@ -0,0 +1,71 @@
+using System;
+using TweetLib.Browser.CEF.Dialogs;
+using TweetLib.Browser.CEF.Interfaces;
+
+namespace TweetLib.Browser.CEF.Logic {
+	public sealed class JsDialogHandlerLogic<TCallback> {
+		private static (MessageDialogType, string) GetMessageDialogProperties(string text) {
+			MessageDialogType type = MessageDialogType.None;
+			
+			int pipe = text.IndexOf('|');
+			if (pipe != -1) {
+				type = text.Substring(0, pipe) switch {
+					"error"    => MessageDialogType.Error,
+					"warning"  => MessageDialogType.Warning,
+					"info"     => MessageDialogType.Information,
+					"question" => MessageDialogType.Question,
+					_          => MessageDialogType.None
+				};
+
+				if (type != MessageDialogType.None) {
+					text = text.Substring(pipe + 1);
+				}
+			}
+
+			return (type, text);
+		}
+		
+		private readonly IJsDialogOpener jsDialogOpener;
+		private readonly IJsDialogCallbackAdapter<TCallback> callbackAdapter;
+
+		public JsDialogHandlerLogic(IJsDialogOpener jsDialogOpener, IJsDialogCallbackAdapter<TCallback> callbackAdapter) {
+			this.jsDialogOpener = jsDialogOpener;
+			this.callbackAdapter = callbackAdapter;
+		}
+
+		public bool OnJSDialog(JsDialogType dialogType, string messageText, TCallback callback, out bool suppressMessage) {
+			suppressMessage = false;
+
+			var (type, text) = GetMessageDialogProperties(messageText);
+			
+			if (dialogType == JsDialogType.Alert) {
+				jsDialogOpener.Alert(type, "Browser Message", text, success => {
+					callbackAdapter.Continue(callback, success);
+					callbackAdapter.Dispose(callback);
+				});
+			}
+			else if (dialogType == JsDialogType.Confirm) {
+				jsDialogOpener.Confirm(type, "Browser Confirmation", text, success => {
+					callbackAdapter.Continue(callback, success);
+					callbackAdapter.Dispose(callback);
+				});
+			}
+			else if (dialogType == JsDialogType.Prompt) {
+				jsDialogOpener.Prompt(type, "Browser Prompt", text, (success, input) => {
+					callbackAdapter.Continue(callback, success, input);
+					callbackAdapter.Dispose(callback);
+				});
+			}
+			else {
+				return false;
+			}
+			
+			return true;
+		}
+
+		public bool OnBeforeUnloadDialog(IDisposable callback) {
+			callback.Dispose();
+			return false;
+		}
+	}
+}
diff --git a/windows/TweetDuck/Browser/Base/CefBrowserComponent.cs b/windows/TweetDuck/Browser/Base/CefBrowserComponent.cs
index 58f0e93b..9a059ddf 100644
--- a/windows/TweetDuck/Browser/Base/CefBrowserComponent.cs
+++ b/windows/TweetDuck/Browser/Base/CefBrowserComponent.cs
@@ -1,7 +1,6 @@
 using CefSharp.WinForms;
 using TweetDuck.Utils;
 using TweetImpl.CefSharp.Component;
-using TweetLib.Browser.Base;
 using TweetLib.Browser.CEF.Utils;
 using TweetLib.Core;
 
@@ -11,13 +10,8 @@ sealed class CefBrowserComponent : BrowserComponentBase {
 
 		public override string CacheFolder => CefUtils.GetCacheFolder(App.StoragePath);
 		
-		public CefBrowserComponent(ChromiumWebBrowser browser, CreateContextMenu createContextMenu = null, bool autoReload = true) : base(browser, createContextMenu ?? DefaultContextMenuFactory, PopupHandler.Instance, autoReload) {
+		public CefBrowserComponent(ChromiumWebBrowser browser, CreateContextMenu createContextMenu = null, bool autoReload = true) : base(browser, createContextMenu ?? DefaultContextMenuFactory, new JsDialogOpener(browser), PopupHandler.Instance, autoReload) {
 			browser.SetupZoomEvents();	
 		}
-
-		public override void Setup(BrowserSetup setup) {
-			base.Setup(setup);
-			browser.JsDialogHandler = new CustomJsDialogHandler();
-		}
 	}
 }
diff --git a/windows/TweetDuck/Browser/Base/CustomJsDialogHandler.cs b/windows/TweetDuck/Browser/Base/CustomJsDialogHandler.cs
deleted file mode 100644
index e6cf67c8..00000000
--- a/windows/TweetDuck/Browser/Base/CustomJsDialogHandler.cs
+++ /dev/null
@@ -1,97 +0,0 @@
-using System.Drawing;
-using System.Windows.Forms;
-using CefSharp;
-using CefSharp.WinForms;
-using TweetDuck.Controls;
-using TweetDuck.Dialogs;
-using TweetDuck.Utils;
-
-namespace TweetDuck.Browser.Base {
-	sealed class CustomJsDialogHandler : IJsDialogHandler {
-		private static FormMessage CreateMessageForm(string caption, string text) {
-			MessageBoxIcon icon = MessageBoxIcon.None;
-			int pipe = text.IndexOf('|');
-
-			if (pipe != -1) {
-				icon = text.Substring(0, pipe) switch {
-					"error"    => MessageBoxIcon.Error,
-					"warning"  => MessageBoxIcon.Warning,
-					"info"     => MessageBoxIcon.Information,
-					"question" => MessageBoxIcon.Question,
-					_          => MessageBoxIcon.None
-				};
-
-				if (icon != MessageBoxIcon.None) {
-					text = text.Substring(pipe + 1);
-				}
-			}
-
-			return new FormMessage(caption, text, icon);
-		}
-
-		bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage) {
-			var control = (ChromiumWebBrowser) browserControl;
-
-			control.InvokeSafe(() => {
-				FormMessage form;
-				TextBox input = null;
-
-				if (dialogType == CefJsDialogType.Alert) {
-					form = CreateMessageForm("Browser Message", messageText);
-					form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
-				}
-				else if (dialogType == CefJsDialogType.Confirm) {
-					form = CreateMessageForm("Browser Confirmation", messageText);
-					form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel);
-					form.AddButton(FormMessage.Yes, ControlType.Focused);
-				}
-				else if (dialogType == CefJsDialogType.Prompt) {
-					form = CreateMessageForm("Browser Prompt", messageText);
-					form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel);
-					form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
-
-					float dpiScale = form.GetDPIScale();
-					int inputPad = form.HasIcon ? 43 : 0;
-
-					input = new TextBox {
-						Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
-						Font = SystemFonts.MessageBoxFont,
-						Location = new Point(BrowserUtils.Scale(22 + inputPad, dpiScale), form.ActionPanelY - BrowserUtils.Scale(46, dpiScale)),
-						Size = new Size(form.ClientSize.Width - BrowserUtils.Scale(44 + inputPad, dpiScale), BrowserUtils.Scale(23, dpiScale))
-					};
-
-					form.Controls.Add(input);
-					form.ActiveControl = input;
-					form.Height += input.Size.Height + input.Margin.Vertical;
-				}
-				else {
-					callback.Continue(false);
-					return;
-				}
-
-				bool success = form.ShowDialog() == DialogResult.OK;
-
-				if (input == null) {
-					callback.Continue(success);
-				}
-				else {
-					callback.Continue(success, input.Text);
-					input.Dispose();
-				}
-
-				callback.Dispose();
-				form.Dispose();
-			});
-
-			return true;
-		}
-
-		bool IJsDialogHandler.OnBeforeUnloadDialog(IWebBrowser browserControl, IBrowser browser, string messageText, bool isReload, IJsDialogCallback callback) {
-			callback.Dispose();
-			return false;
-		}
-
-		void IJsDialogHandler.OnResetDialogState(IWebBrowser browserControl, IBrowser browser) {}
-		void IJsDialogHandler.OnDialogClosed(IWebBrowser browserControl, IBrowser browser) {}
-	}
-}
diff --git a/windows/TweetDuck/Browser/Base/FileDialogHandler.cs b/windows/TweetDuck/Browser/Base/FileDialogHandler.cs
deleted file mode 100644
index e1c1a631..00000000
--- a/windows/TweetDuck/Browser/Base/FileDialogHandler.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Windows.Forms;
-using CefSharp;
-using TweetLib.Core;
-using TweetLib.Utils.Static;
-
-namespace TweetDuck.Browser.Base {
-	sealed class FileDialogHandler : IDialogHandler {
-		public bool OnFileDialog(IWebBrowser browserControl, IBrowser browser, CefFileDialogMode mode, CefFileDialogFlags flags, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback) {
-			if (mode == CefFileDialogMode.Open || mode == CefFileDialogMode.OpenMultiple) {
-				string allFilters = string.Join(";", acceptFilters.SelectMany(ParseFileType).Where(filter => !string.IsNullOrEmpty(filter)).Select(filter => "*" + filter));
-
-				using OpenFileDialog dialog = new OpenFileDialog {
-					AutoUpgradeEnabled = true,
-					DereferenceLinks = true,
-					Multiselect = mode == CefFileDialogMode.OpenMultiple,
-					Title = "Open Files",
-					Filter = $"All Supported Formats ({allFilters})|{allFilters}|All Files (*.*)|*.*"
-				};
-
-				if (dialog.ShowDialog() == DialogResult.OK) {
-					string ext = Path.GetExtension(dialog.FileName)?.ToLower();
-					callback.Continue(acceptFilters.FindIndex(filter => ParseFileType(filter).Contains(ext)), dialog.FileNames.ToList());
-				}
-				else {
-					callback.Cancel();
-				}
-
-				callback.Dispose();
-				return true;
-			}
-			else {
-				callback.Dispose();
-				return false;
-			}
-		}
-
-		private static IEnumerable<string> ParseFileType(string type) {
-			if (string.IsNullOrEmpty(type)) {
-				return StringUtils.EmptyArray;
-			}
-
-			if (type[0] == '.') {
-				return new string[] { type };
-			}
-
-			string[] extensions = type switch {
-				"image/jpeg"      => new string[] { ".jpg", ".jpeg" },
-				"image/png"       => new string[] { ".png" },
-				"image/gif"       => new string[] { ".gif" },
-				"image/webp"      => new string[] { ".webp" },
-				"video/mp4"       => new string[] { ".mp4" },
-				"video/quicktime" => new string[] { ".mov", ".qt" },
-				_                 => StringUtils.EmptyArray
-			};
-
-			if (extensions.Length == 0) {
-				App.Logger.Warn("Unknown file type: " + type);
-				Debugger.Break();
-			}
-
-			return extensions;
-		}
-	}
-}
diff --git a/windows/TweetDuck/Browser/Base/JsDialogOpener.cs b/windows/TweetDuck/Browser/Base/JsDialogOpener.cs
new file mode 100644
index 00000000..26fde46b
--- /dev/null
+++ b/windows/TweetDuck/Browser/Base/JsDialogOpener.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Drawing;
+using System.Windows.Forms;
+using TweetDuck.Controls;
+using TweetDuck.Dialogs;
+using TweetDuck.Utils;
+using TweetLib.Browser.CEF.Dialogs;
+using TweetLib.Browser.CEF.Interfaces;
+
+namespace TweetDuck.Browser.Base {
+	sealed class JsDialogOpener : IJsDialogOpener {
+		private static FormMessage CreateMessageForm(MessageDialogType type, string title, string text) {
+			return new FormMessage(title, text, type switch {
+				MessageDialogType.Error       => MessageBoxIcon.Error,
+				MessageDialogType.Warning     => MessageBoxIcon.Warning,
+				MessageDialogType.Information => MessageBoxIcon.Information,
+				MessageDialogType.Question    => MessageBoxIcon.Question,
+				_                             => MessageBoxIcon.None
+			});
+		}
+
+		private readonly Control control;
+
+		public JsDialogOpener(Control control) {
+			this.control = control;
+		}
+
+		public void Alert(MessageDialogType type, string title, string message, Action<bool> callback) {
+			control.InvokeSafe(() => {
+				using FormMessage form = CreateMessageForm(type, title, message);
+				form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
+
+				bool success = form.ShowDialog() == DialogResult.OK;
+				callback(success);
+			});
+		}
+
+		public void Confirm(MessageDialogType type, string title, string message, Action<bool> callback) {
+			control.InvokeSafe(() => {
+				using FormMessage form = CreateMessageForm(type, title, message);
+				form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel);
+				form.AddButton(FormMessage.Yes, ControlType.Focused);
+
+				bool success = form.ShowDialog() == DialogResult.OK;
+				callback(success);
+			});
+		}
+
+		public void Prompt(MessageDialogType type, string title, string message, Action<bool, string> callback) {
+			control.InvokeSafe(() => {
+				using FormMessage form = CreateMessageForm(type, title, message);
+				form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel);
+				form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
+
+				float dpiScale = form.GetDPIScale();
+				int inputPad = form.HasIcon ? 43 : 0;
+
+				using var input = new TextBox {
+					Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
+					Font = SystemFonts.MessageBoxFont,
+					Location = new Point(BrowserUtils.Scale(22 + inputPad, dpiScale), form.ActionPanelY - BrowserUtils.Scale(46, dpiScale)),
+					Size = new Size(form.ClientSize.Width - BrowserUtils.Scale(44 + inputPad, dpiScale), BrowserUtils.Scale(23, dpiScale))
+				};
+
+				form.Controls.Add(input);
+				form.ActiveControl = input;
+				form.Height += input.Size.Height + input.Margin.Vertical;
+
+				bool success = form.ShowDialog() == DialogResult.OK;
+				callback(success, input.Text);
+			});
+		}
+	}
+}
diff --git a/windows/TweetDuck/Browser/TweetDeckBrowser.cs b/windows/TweetDuck/Browser/TweetDeckBrowser.cs
index 4c0ffb63..b62bf45c 100644
--- a/windows/TweetDuck/Browser/TweetDeckBrowser.cs
+++ b/windows/TweetDuck/Browser/TweetDeckBrowser.cs
@@ -40,7 +40,6 @@ public bool IsTweetDeckWebsite {
 
 		public TweetDeckBrowser(FormBrowser owner, PluginManager pluginManager, ITweetDeckInterface tweetDeckInterface, UpdateChecker updateChecker) {
 			this.browser = new ChromiumWebBrowser(TwitterUrls.TweetDeck) {
-				DialogHandler = new FileDialogHandler(),
 				KeyboardHandler = new CustomKeyboardHandler(owner)
 			};
 
diff --git a/windows/TweetDuck/TweetDuck.csproj b/windows/TweetDuck/TweetDuck.csproj
index b1294302..b8efd644 100644
--- a/windows/TweetDuck/TweetDuck.csproj
+++ b/windows/TweetDuck/TweetDuck.csproj
@@ -105,8 +105,7 @@
     <Compile Include="Browser\Base\ContextMenuBrowser.cs" />
     <Compile Include="Browser\Base\ContextMenuNotification.cs" />
     <Compile Include="Browser\Base\CustomKeyboardHandler.cs" />
-    <Compile Include="Browser\Base\FileDialogHandler.cs" />
-    <Compile Include="Browser\Base\CustomJsDialogHandler.cs" />
+    <Compile Include="Browser\Base\JsDialogOpener.cs" />
     <Compile Include="Browser\Base\PopupHandler.cs" />
     <Compile Include="Browser\Notification\FormNotificationExample.cs">
       <SubType>Form</SubType>
diff --git a/windows/TweetImpl.CefSharp/Adapters/CefFileDialogCallbackAdapter.cs b/windows/TweetImpl.CefSharp/Adapters/CefFileDialogCallbackAdapter.cs
new file mode 100644
index 00000000..49833b41
--- /dev/null
+++ b/windows/TweetImpl.CefSharp/Adapters/CefFileDialogCallbackAdapter.cs
@@ -0,0 +1,23 @@
+using System.Linq;
+using CefSharp;
+using TweetLib.Browser.CEF.Interfaces;
+
+namespace TweetImpl.CefSharp.Adapters {
+	sealed class CefFileDialogCallbackAdapter : IFileDialogCallbackAdapter<IFileDialogCallback> {
+		public static CefFileDialogCallbackAdapter Instance { get; } = new CefFileDialogCallbackAdapter();
+
+		private CefFileDialogCallbackAdapter() {}
+
+		public void Continue(IFileDialogCallback callback, int selectedAcceptFilter, string[] filePaths) {
+			callback.Continue(selectedAcceptFilter, filePaths.ToList());
+		}
+
+		public void Cancel(IFileDialogCallback callback) {
+			callback.Cancel();
+		}
+
+		public void Dispose(IFileDialogCallback callback) {
+			callback.Dispose();
+		}
+	}
+}
diff --git a/windows/TweetImpl.CefSharp/Adapters/CefJsDialogCallbackAdapter.cs b/windows/TweetImpl.CefSharp/Adapters/CefJsDialogCallbackAdapter.cs
new file mode 100644
index 00000000..61e1c04d
--- /dev/null
+++ b/windows/TweetImpl.CefSharp/Adapters/CefJsDialogCallbackAdapter.cs
@@ -0,0 +1,23 @@
+using CefSharp;
+using TweetLib.Browser.CEF.Interfaces;
+
+namespace TweetImpl.CefSharp.Adapters {
+	sealed class CefJsDialogCallbackAdapter : IJsDialogCallbackAdapter<IJsDialogCallback> {
+		public static CefJsDialogCallbackAdapter Instance { get; } = new CefJsDialogCallbackAdapter();
+
+		private CefJsDialogCallbackAdapter() {}
+
+		public void Continue(IJsDialogCallback callback, bool success, string userInput = null) {
+			if (userInput == null) {
+				callback.Continue(success);
+			}
+			else {
+				callback.Continue(success, userInput);
+			}
+		}
+
+		public void Dispose(IJsDialogCallback callback) {
+			callback.Dispose();
+		}
+	}
+}
diff --git a/windows/TweetImpl.CefSharp/Component/BrowserComponentBase.cs b/windows/TweetImpl.CefSharp/Component/BrowserComponentBase.cs
index 372bdb30..fd4cb260 100644
--- a/windows/TweetImpl.CefSharp/Component/BrowserComponentBase.cs
+++ b/windows/TweetImpl.CefSharp/Component/BrowserComponentBase.cs
@@ -14,18 +14,20 @@ public abstract class BrowserComponentBase : BrowserComponent<IFrame, IRequest>
 
 		public ResourceHandlerRegistry<IResourceHandler> ResourceHandlerRegistry { get; } = new ResourceHandlerRegistry<IResourceHandler>(CefResourceHandlerFactory.Instance);
 
-		protected readonly ChromiumWebBrowser browser;
+		private readonly ChromiumWebBrowser browser;
 		private readonly CreateContextMenu createContextMenu;
+		private readonly IJsDialogOpener jsDialogOpener;
 		private readonly IPopupHandler popupHandler;
 		private readonly bool autoReload;
 
-		protected BrowserComponentBase(ChromiumWebBrowser browser, CreateContextMenu createContextMenu, IPopupHandler popupHandler, bool autoReload) : base(new CefBrowserAdapter(browser), CefAdapter.Instance, CefFrameAdapter.Instance, CefRequestAdapter.Instance) {
+		protected BrowserComponentBase(ChromiumWebBrowser browser, CreateContextMenu createContextMenu, IJsDialogOpener jsDialogOpener, IPopupHandler popupHandler, bool autoReload) : base(new CefBrowserAdapter(browser), CefAdapter.Instance, CefFrameAdapter.Instance, CefRequestAdapter.Instance) {
 			this.browser = browser;
 			this.browser.LoadingStateChanged += OnLoadingStateChanged;
 			this.browser.LoadError += OnLoadError;
 			this.browser.FrameLoadStart += OnFrameLoadStart;
 			this.browser.FrameLoadEnd += OnFrameLoadEnd;
 			this.createContextMenu = createContextMenu;
+			this.jsDialogOpener = jsDialogOpener;
 			this.popupHandler = popupHandler;
 			this.autoReload = autoReload;
 		}
@@ -34,8 +36,10 @@ public override void Setup(BrowserSetup setup) {
 			var lifeSpanHandler = new CefLifeSpanHandler(popupHandler);
 			var requestHandler = new CefRequestHandler(lifeSpanHandler, autoReload);
 
+			browser.DialogHandler = new CefFileDialogHandler();
 			browser.DragHandler = new CefDragHandler(requestHandler, this);
 			browser.LifeSpanHandler = lifeSpanHandler;
+			browser.JsDialogHandler = new CefJsDialogHandler(jsDialogOpener);
 			browser.MenuHandler = createContextMenu(setup.ContextMenuHandler);
 			browser.RequestHandler = requestHandler;
 			browser.ResourceRequestHandlerFactory = new CefResourceRequestHandlerFactory(setup.ResourceRequestHandler, ResourceHandlerRegistry);
diff --git a/windows/TweetImpl.CefSharp/Dialogs/FileDialogOpener.cs b/windows/TweetImpl.CefSharp/Dialogs/FileDialogOpener.cs
new file mode 100644
index 00000000..b4a9ccf3
--- /dev/null
+++ b/windows/TweetImpl.CefSharp/Dialogs/FileDialogOpener.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+using TweetLib.Browser.CEF.Interfaces;
+
+namespace TweetImpl.CefSharp.Dialogs {
+	sealed class FileDialogOpener : IFileDialogOpener {
+		public static FileDialogOpener Instance { get; } = new FileDialogOpener();
+
+		private FileDialogOpener() {}
+
+		public void OpenFile(string title, bool multiple, List<string> supportedExtensions, Action<string[]> onAccepted, Action onCancelled) {
+			string supportedFormatsFilter = string.Join(";", supportedExtensions.Select(filter => "*" + filter));
+			
+			using OpenFileDialog dialog = new OpenFileDialog {
+				AutoUpgradeEnabled = true,
+				DereferenceLinks = true,
+				Multiselect = multiple,
+				Title = title,
+				Filter = $"All Supported Formats ({supportedFormatsFilter})|{supportedFormatsFilter}|All Files (*.*)|*.*"
+			};
+
+			if (dialog.ShowDialog() == DialogResult.OK) {
+				onAccepted(dialog.FileNames);
+			}
+			else {
+				onCancelled();
+			}
+		}
+	}
+}
diff --git a/windows/TweetImpl.CefSharp/Handlers/CefFileDialogHandler.cs b/windows/TweetImpl.CefSharp/Handlers/CefFileDialogHandler.cs
new file mode 100644
index 00000000..fe2e5256
--- /dev/null
+++ b/windows/TweetImpl.CefSharp/Handlers/CefFileDialogHandler.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using CefSharp;
+using TweetImpl.CefSharp.Adapters;
+using TweetImpl.CefSharp.Dialogs;
+using TweetLib.Browser.CEF.Dialogs;
+using TweetLib.Browser.CEF.Logic;
+using static TweetLib.Browser.CEF.Dialogs.FileDialogType;
+
+namespace TweetImpl.CefSharp.Handlers {
+	sealed class CefFileDialogHandler : IDialogHandler {
+		private readonly DialogHandlerLogic<IFileDialogCallback> logic;
+
+		public CefFileDialogHandler() {
+			this.logic = new DialogHandlerLogic<IFileDialogCallback>(FileDialogOpener.Instance, CefFileDialogCallbackAdapter.Instance);
+		}
+
+		public bool OnFileDialog(IWebBrowser chromiumWebBrowser, IBrowser browser, CefFileDialogMode mode, CefFileDialogFlags flags, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback) {
+			return logic.OnFileDialog(ConvertDialogType(mode), acceptFilters, callback);
+		}
+
+		private static FileDialogType ConvertDialogType(CefFileDialogMode mode) {
+			return mode switch {
+				CefFileDialogMode.Open         => Open,
+				CefFileDialogMode.OpenMultiple => OpenMultiple,
+				_                              => Other
+			};
+		}
+	}
+}
diff --git a/windows/TweetImpl.CefSharp/Handlers/CefJsDialogHandler.cs b/windows/TweetImpl.CefSharp/Handlers/CefJsDialogHandler.cs
new file mode 100644
index 00000000..a6faa1d1
--- /dev/null
+++ b/windows/TweetImpl.CefSharp/Handlers/CefJsDialogHandler.cs
@@ -0,0 +1,37 @@
+using System.Diagnostics.CodeAnalysis;
+using CefSharp;
+using TweetImpl.CefSharp.Adapters;
+using TweetLib.Browser.CEF.Dialogs;
+using TweetLib.Browser.CEF.Interfaces;
+using TweetLib.Browser.CEF.Logic;
+
+namespace TweetImpl.CefSharp.Handlers {
+	sealed class CefJsDialogHandler : IJsDialogHandler {
+		private readonly JsDialogHandlerLogic<IJsDialogCallback> logic;
+
+		public CefJsDialogHandler(IJsDialogOpener jsDialogOpener) {
+			this.logic = new JsDialogHandlerLogic<IJsDialogCallback>(jsDialogOpener, CefJsDialogCallbackAdapter.Instance);
+		}
+
+		[SuppressMessage("ReSharper", "RedundantAssignment")]
+		public bool OnJSDialog(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage) {
+			return logic.OnJSDialog(ConvertDialogType(dialogType), messageText, callback, out suppressMessage);
+		}
+
+		public bool OnBeforeUnloadDialog(IWebBrowser chromiumWebBrowser, IBrowser browser, string messageText, bool isReload, IJsDialogCallback callback) {
+			return logic.OnBeforeUnloadDialog(callback);
+		}
+
+		public void OnResetDialogState(IWebBrowser chromiumWebBrowser, IBrowser browser) {}
+		public void OnDialogClosed(IWebBrowser chromiumWebBrowser, IBrowser browser) {}
+
+		private static JsDialogType ConvertDialogType(CefJsDialogType type) {
+			return type switch {
+				CefJsDialogType.Alert   => JsDialogType.Alert,
+				CefJsDialogType.Confirm => JsDialogType.Confirm,
+				CefJsDialogType.Prompt  => JsDialogType.Prompt,
+				_                       => JsDialogType.Unknown
+			};
+		}
+	}
+}
diff --git a/windows/TweetImpl.CefSharp/TweetImpl.CefSharp.csproj b/windows/TweetImpl.CefSharp/TweetImpl.CefSharp.csproj
index 27b50baf..18e83a70 100644
--- a/windows/TweetImpl.CefSharp/TweetImpl.CefSharp.csproj
+++ b/windows/TweetImpl.CefSharp/TweetImpl.CefSharp.csproj
@@ -69,15 +69,20 @@
     <Compile Include="Adapters\CefBrowserAdapter.cs" />
     <Compile Include="Adapters\CefDragDataAdapter.cs" />
     <Compile Include="Adapters\CefErrorCodeAdapter.cs" />
+    <Compile Include="Adapters\CefFileDialogCallbackAdapter.cs" />
     <Compile Include="Adapters\CefFrameAdapter.cs" />
+    <Compile Include="Adapters\CefJsDialogCallbackAdapter.cs" />
     <Compile Include="Adapters\CefMenuModelAdapter.cs" />
     <Compile Include="Adapters\CefRequestAdapter.cs" />
     <Compile Include="Adapters\CefResponseAdapter.cs" />
     <Compile Include="Component\BrowserComponentBase.cs" />
+    <Compile Include="Dialogs\FileDialogOpener.cs" />
     <Compile Include="Handlers\CefByteArrayResourceHandler.cs" />
     <Compile Include="Handlers\CefContextMenuHandler.cs" />
     <Compile Include="Handlers\CefDownloadRequestClient.cs" />
     <Compile Include="Handlers\CefDragHandler.cs" />
+    <Compile Include="Handlers\CefFileDialogHandler.cs" />
+    <Compile Include="Handlers\CefJsDialogHandler.cs" />
     <Compile Include="Handlers\CefLifeSpanHandler.cs" />
     <Compile Include="Handlers\CefRequestHandler.cs" />
     <Compile Include="Handlers\CefResourceHandlerFactory.cs" />