mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-05-03 05:34:07 +02:00
Work on abstracting CEF conventions and logic into a separate library
This commit is contained in:
parent
51d2ec92ca
commit
c9fd4634ab
Browser
Adapters
CefBrowserComponent.csCefContextMenuActionRegistry.csCefResourceHandlerFactory.csCefResourceHandlerRegistry.csCefResourceRequestHandler.csCefSchemeResourceVisitor.cs
Base
CefBrowserAdapter.csCefBrowserComponent.csCefByteArrayResourceHandler.csCefContextMenuModel.csCefDownloadRequestClient.csCefDragDataAdapter.csCefDragHandler.csCefErrorCodeAdapter.csCefFrameAdapter.csCefLifeSpanHandler.csCefRequestAdapter.csCefRequestHandler.csCefResourceHandlerFactory.csCefResourceRequestHandler.csCefResourceRequestHandlerFactory.csCefResponseAdapter.csCefResponseFilter.csCefSchemeHandlerFactory.cs
FormBrowser.csHandling
ContextMenuBase.csContextMenuBrowser.csContextMenuGuide.csContextMenuNotification.csCustomJsDialogHandler.csCustomLifeSpanHandler.csDownloadRequestClient.csDragHandlerBrowser.csPopupHandler.csRequestHandlerBase.csRequestHandlerBrowser.csResourceHandlerNotification.cs
Notification
TweetDeckBrowser.csDialogs
Management
Program.csTweetDuck.csprojTweetDuck.slnlib
TweetLib.Browser.CEF
Component
Data
Interfaces
IBrowserWrapper.csIDragDataAdapter.csIErrorCodeAdapter.csIFrameAdapter.csIPopupHandler.csIRequestAdapter.csIResourceHandlerFactory.csIResponseAdapter.cs
Lib.csLogic
ByteArrayResourceHandlerLogic.csDownloadRequestClientLogic.csDragHandlerLogic.csLifeSpanHandlerLogic.csRequestHandlerLogic.csResourceRequestHandlerFactoryLogic.csResourceRequestHandlerLogic.csResponseFilterLogic.csSchemeHandlerFactoryLogic.csSchemeResourceVisitor.cs
TweetLib.Browser.CEF.csprojUtils
TweetTest.Browser.CEF
TweetTest.Core
@ -1,125 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using CefSharp;
|
|
||||||
using CefSharp.WinForms;
|
|
||||||
using TweetDuck.Browser.Handling;
|
|
||||||
using TweetDuck.Management;
|
|
||||||
using TweetDuck.Utils;
|
|
||||||
using TweetLib.Browser.Base;
|
|
||||||
using TweetLib.Browser.Events;
|
|
||||||
using TweetLib.Browser.Interfaces;
|
|
||||||
using TweetLib.Utils.Static;
|
|
||||||
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
|
||||||
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Adapters {
|
|
||||||
abstract class CefBrowserComponent : IBrowserComponent {
|
|
||||||
public bool Ready { get; private set; }
|
|
||||||
|
|
||||||
public string Url => browser.Address;
|
|
||||||
public string CacheFolder => BrowserCache.CacheFolder;
|
|
||||||
|
|
||||||
public event EventHandler<BrowserLoadedEventArgs> BrowserLoaded;
|
|
||||||
public event EventHandler<PageLoadEventArgs> PageLoadStart;
|
|
||||||
public event EventHandler<PageLoadEventArgs> PageLoadEnd;
|
|
||||||
|
|
||||||
private readonly ChromiumWebBrowser browser;
|
|
||||||
|
|
||||||
protected CefBrowserComponent(ChromiumWebBrowser browser) {
|
|
||||||
this.browser = browser;
|
|
||||||
this.browser.JsDialogHandler = new JavaScriptDialogHandler();
|
|
||||||
this.browser.LifeSpanHandler = new CustomLifeSpanHandler();
|
|
||||||
this.browser.LoadingStateChanged += OnLoadingStateChanged;
|
|
||||||
this.browser.LoadError += OnLoadError;
|
|
||||||
this.browser.FrameLoadStart += OnFrameLoadStart;
|
|
||||||
this.browser.FrameLoadEnd += OnFrameLoadEnd;
|
|
||||||
this.browser.SetupZoomEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IBrowserComponent.Setup(BrowserSetup setup) {
|
|
||||||
browser.MenuHandler = SetupContextMenu(setup.ContextMenuHandler);
|
|
||||||
browser.ResourceRequestHandlerFactory = SetupResourceHandlerFactory(setup.ResourceRequestHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract ContextMenuBase SetupContextMenu(IContextMenuHandler handler);
|
|
||||||
|
|
||||||
protected abstract CefResourceHandlerFactory SetupResourceHandlerFactory(IResourceRequestHandler handler);
|
|
||||||
|
|
||||||
private void OnLoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
|
|
||||||
if (!e.IsLoading) {
|
|
||||||
Ready = true;
|
|
||||||
browser.LoadingStateChanged -= OnLoadingStateChanged;
|
|
||||||
BrowserLoaded?.Invoke(this, new BrowserLoadedEventArgsImpl(browser));
|
|
||||||
BrowserLoaded = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class BrowserLoadedEventArgsImpl : BrowserLoadedEventArgs {
|
|
||||||
private readonly IWebBrowser browser;
|
|
||||||
|
|
||||||
public BrowserLoadedEventArgsImpl(IWebBrowser browser) {
|
|
||||||
this.browser = browser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void AddDictionaryWords(params string[] words) {
|
|
||||||
foreach (string word in words) {
|
|
||||||
browser.AddWordToDictionary(word);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLoadError(object sender, LoadErrorEventArgs e) {
|
|
||||||
if (e.ErrorCode == CefErrorCode.Aborted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!e.FailedUrl.StartsWithOrdinal("td://resources/error/")) {
|
|
||||||
string errorName = Enum.GetName(typeof(CefErrorCode), e.ErrorCode);
|
|
||||||
string errorTitle = StringUtils.ConvertPascalCaseToScreamingSnakeCase(errorName ?? string.Empty);
|
|
||||||
browser.Load("td://resources/error/error.html#" + Uri.EscapeDataString(errorTitle));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnFrameLoadStart(object sender, FrameLoadStartEventArgs e) {
|
|
||||||
if (e.Frame.IsMain) {
|
|
||||||
PageLoadStart?.Invoke(this, new PageLoadEventArgs(e.Url));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnFrameLoadEnd(object sender, FrameLoadEndEventArgs e) {
|
|
||||||
if (e.Frame.IsMain) {
|
|
||||||
PageLoadEnd?.Invoke(this, new PageLoadEventArgs(e.Url));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AttachBridgeObject(string name, object bridge) {
|
|
||||||
browser.JavascriptObjectRepository.Settings.LegacyBindingEnabled = true;
|
|
||||||
browser.JavascriptObjectRepository.Register(name, bridge, isAsync: true, BindingOptions.DefaultBinder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RunScript(string identifier, string script) {
|
|
||||||
using IFrame frame = browser.GetMainFrame();
|
|
||||||
frame.ExecuteJavaScriptAsync(script, identifier, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DownloadFile(string url, string path, Action onSuccess, Action<Exception> onError) {
|
|
||||||
Cef.UIThreadTaskFactory.StartNew(() => {
|
|
||||||
try {
|
|
||||||
using IFrame frame = browser.GetMainFrame();
|
|
||||||
var request = frame.CreateRequest(false);
|
|
||||||
|
|
||||||
request.Method = "GET";
|
|
||||||
request.Url = url;
|
|
||||||
request.Flags = UrlRequestFlags.AllowStoredCredentials;
|
|
||||||
request.SetReferrer(Url, ReferrerPolicy.Default);
|
|
||||||
|
|
||||||
var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
|
|
||||||
var client = new DownloadRequestClient(fileStream, onSuccess, onError);
|
|
||||||
frame.CreateUrlRequest(request, client);
|
|
||||||
} catch (Exception e) {
|
|
||||||
onError?.Invoke(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using CefSharp;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Adapters {
|
|
||||||
sealed class CefContextMenuActionRegistry {
|
|
||||||
private readonly Dictionary<CefMenuCommand, Action> actions = new Dictionary<CefMenuCommand, Action>();
|
|
||||||
|
|
||||||
public CefMenuCommand AddAction(Action action) {
|
|
||||||
CefMenuCommand id = CefMenuCommand.UserFirst + 500 + actions.Count;
|
|
||||||
actions[id] = action;
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Execute(CefMenuCommand id) {
|
|
||||||
if (actions.TryGetValue(id, out var action)) {
|
|
||||||
action();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear() {
|
|
||||||
actions.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using CefSharp;
|
|
||||||
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Adapters {
|
|
||||||
sealed class CefResourceHandlerFactory : IResourceRequestHandlerFactory {
|
|
||||||
bool IResourceRequestHandlerFactory.HasHandlers => registry != null;
|
|
||||||
|
|
||||||
private readonly CefResourceRequestHandler resourceRequestHandler;
|
|
||||||
private readonly CefResourceHandlerRegistry registry;
|
|
||||||
|
|
||||||
public CefResourceHandlerFactory(IResourceRequestHandler resourceRequestHandler, CefResourceHandlerRegistry registry) {
|
|
||||||
this.resourceRequestHandler = new CefResourceRequestHandler(registry, resourceRequestHandler);
|
|
||||||
this.registry = registry;
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "RedundantAssignment")]
|
|
||||||
CefSharp.IResourceRequestHandler IResourceRequestHandlerFactory.GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling) {
|
|
||||||
disableDefaultHandling = registry != null && registry.HasHandler(request.Url);
|
|
||||||
return resourceRequestHandler;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text;
|
|
||||||
using CefSharp;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Adapters {
|
|
||||||
sealed class CefResourceHandlerRegistry {
|
|
||||||
private readonly ConcurrentDictionary<string, Func<IResourceHandler>> resourceHandlers = new ConcurrentDictionary<string, Func<IResourceHandler>>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
public bool HasHandler(string url) {
|
|
||||||
return resourceHandlers.ContainsKey(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IResourceHandler GetHandler(string url) {
|
|
||||||
return resourceHandlers.TryGetValue(url, out var handler) ? handler() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Register(string url, Func<IResourceHandler> factory) {
|
|
||||||
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) {
|
|
||||||
throw new ArgumentException("Resource handler URL must be absolute!");
|
|
||||||
}
|
|
||||||
|
|
||||||
resourceHandlers.AddOrUpdate(uri.AbsoluteUri, factory, (key, prev) => factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterStatic(string url, byte[] staticData, string mimeType = ResourceHandler.DefaultMimeType) {
|
|
||||||
Register(url, () => ResourceHandler.FromByteArray(staticData, mimeType));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterStatic(string url, string staticData, string mimeType = ResourceHandler.DefaultMimeType) {
|
|
||||||
Register(url, () => ResourceHandler.FromString(staticData, Encoding.UTF8, mimeType: mimeType));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterDynamic(string url, IResourceHandler handler) {
|
|
||||||
Register(url, () => handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Unregister(string url) {
|
|
||||||
resourceHandlers.TryRemove(url, out _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using CefSharp;
|
|
||||||
using CefSharp.Handler;
|
|
||||||
using TweetDuck.Browser.Handling;
|
|
||||||
using TweetLib.Browser.Interfaces;
|
|
||||||
using TweetLib.Browser.Request;
|
|
||||||
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
|
|
||||||
using ResourceType = TweetLib.Browser.Request.ResourceType;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Adapters {
|
|
||||||
sealed class CefResourceRequestHandler : ResourceRequestHandler {
|
|
||||||
private readonly CefResourceHandlerRegistry resourceHandlerRegistry;
|
|
||||||
private readonly IResourceRequestHandler resourceRequestHandler;
|
|
||||||
private readonly Dictionary<ulong, IResponseProcessor> responseProcessors = new Dictionary<ulong, IResponseProcessor>();
|
|
||||||
|
|
||||||
public CefResourceRequestHandler(CefResourceHandlerRegistry resourceHandlerRegistry, IResourceRequestHandler resourceRequestHandler) {
|
|
||||||
this.resourceHandlerRegistry = resourceHandlerRegistry;
|
|
||||||
this.resourceRequestHandler = resourceRequestHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback) {
|
|
||||||
if (request.ResourceType == CefSharp.ResourceType.CspReport) {
|
|
||||||
callback.Dispose();
|
|
||||||
return CefReturnValue.Cancel;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resourceRequestHandler != null) {
|
|
||||||
var result = resourceRequestHandler.Handle(request.Url, TranslateResourceType(request.ResourceType));
|
|
||||||
|
|
||||||
switch (result) {
|
|
||||||
case RequestHandleResult.Redirect redirect:
|
|
||||||
request.Url = redirect.Url;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RequestHandleResult.Process process:
|
|
||||||
request.SetHeaderByName("Accept-Encoding", "identity", overwrite: true);
|
|
||||||
responseProcessors[request.Identifier] = process.Processor;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RequestHandleResult.Cancel _:
|
|
||||||
callback.Dispose();
|
|
||||||
return CefReturnValue.Cancel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.OnBeforeResourceLoad(chromiumWebBrowser, browser, frame, request, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IResourceHandler GetResourceHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request) {
|
|
||||||
return resourceHandlerRegistry?.GetHandler(request.Url);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response) {
|
|
||||||
if (responseProcessors.TryGetValue(request.Identifier, out var processor) && int.TryParse(response.Headers["Content-Length"], out int totalBytes)) {
|
|
||||||
return new ResponseFilter(processor, totalBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.GetResourceResponseFilter(browserControl, browser, frame, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnResourceLoadComplete(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength) {
|
|
||||||
responseProcessors.Remove(request.Identifier);
|
|
||||||
base.OnResourceLoadComplete(chromiumWebBrowser, browser, frame, request, response, status, receivedContentLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ResourceType TranslateResourceType(CefSharp.ResourceType resourceType) {
|
|
||||||
return resourceType switch {
|
|
||||||
CefSharp.ResourceType.MainFrame => ResourceType.MainFrame,
|
|
||||||
CefSharp.ResourceType.Script => ResourceType.Script,
|
|
||||||
CefSharp.ResourceType.Stylesheet => ResourceType.Stylesheet,
|
|
||||||
CefSharp.ResourceType.Xhr => ResourceType.Xhr,
|
|
||||||
CefSharp.ResourceType.Image => ResourceType.Image,
|
|
||||||
_ => ResourceType.Unknown
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetLib.Browser.Interfaces;
|
|
||||||
using TweetLib.Browser.Request;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Adapters {
|
|
||||||
sealed class CefSchemeResourceVisitor : ISchemeResourceVisitor<IResourceHandler> {
|
|
||||||
public static CefSchemeResourceVisitor Instance { get; } = new CefSchemeResourceVisitor();
|
|
||||||
|
|
||||||
private static readonly SchemeResource.Status FileIsEmpty = new SchemeResource.Status(HttpStatusCode.NoContent, "File is empty.");
|
|
||||||
|
|
||||||
private CefSchemeResourceVisitor() {}
|
|
||||||
|
|
||||||
public IResourceHandler Status(SchemeResource.Status status) {
|
|
||||||
var handler = CreateHandler(Array.Empty<byte>());
|
|
||||||
handler.StatusCode = (int) status.Code;
|
|
||||||
handler.StatusText = status.Message;
|
|
||||||
return handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IResourceHandler File(SchemeResource.File file) {
|
|
||||||
byte[] contents = file.Contents;
|
|
||||||
if (contents.Length == 0) {
|
|
||||||
return Status(FileIsEmpty); // FromByteArray crashes CEF internals with no contents
|
|
||||||
}
|
|
||||||
|
|
||||||
var handler = CreateHandler(contents);
|
|
||||||
handler.MimeType = Cef.GetMimeType(file.Extension);
|
|
||||||
return handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ResourceHandler CreateHandler(byte[] bytes) {
|
|
||||||
return ResourceHandler.FromStream(new MemoryStream(bytes), autoDisposeStream: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
20
Browser/Base/CefBrowserAdapter.cs
Normal file
20
Browser/Base/CefBrowserAdapter.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using CefSharp.WinForms;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefBrowserAdapter : IBrowserWrapper<IFrame> {
|
||||||
|
public string Url => browser.Address;
|
||||||
|
public IFrame MainFrame => browser.GetMainFrame();
|
||||||
|
|
||||||
|
private readonly ChromiumWebBrowser browser;
|
||||||
|
|
||||||
|
public CefBrowserAdapter(ChromiumWebBrowser browser) {
|
||||||
|
this.browser = browser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddWordToDictionary(string word) {
|
||||||
|
browser.AddWordToDictionary(word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
94
Browser/Base/CefBrowserComponent.cs
Normal file
94
Browser/Base/CefBrowserComponent.cs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using CefSharp;
|
||||||
|
using CefSharp.WinForms;
|
||||||
|
using TweetDuck.Browser.Handling;
|
||||||
|
using TweetDuck.Management;
|
||||||
|
using TweetDuck.Utils;
|
||||||
|
using TweetLib.Browser.Base;
|
||||||
|
using TweetLib.Browser.CEF.Component;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
|
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefBrowserComponent : BrowserComponent<IFrame> {
|
||||||
|
public delegate ContextMenuBase CreateContextMenu(IContextMenuHandler handler);
|
||||||
|
|
||||||
|
private static readonly CreateContextMenu DefaultContextMenuFactory = handler => new ContextMenuBase(handler);
|
||||||
|
|
||||||
|
public override string CacheFolder => BrowserCache.CacheFolder;
|
||||||
|
|
||||||
|
public ResourceHandlerRegistry<IResourceHandler> ResourceHandlerRegistry { get; } = new ResourceHandlerRegistry<IResourceHandler>(CefResourceHandlerFactory.Instance);
|
||||||
|
|
||||||
|
private readonly ChromiumWebBrowser browser;
|
||||||
|
private readonly bool autoReload;
|
||||||
|
|
||||||
|
private CreateContextMenu createContextMenu;
|
||||||
|
|
||||||
|
public CefBrowserComponent(ChromiumWebBrowser browser, CreateContextMenu createContextMenu = null, bool autoReload = true) : base(new CefBrowserAdapter(browser), CefFrameAdapter.Instance) {
|
||||||
|
this.browser = browser;
|
||||||
|
this.browser.LoadingStateChanged += OnLoadingStateChanged;
|
||||||
|
this.browser.LoadError += OnLoadError;
|
||||||
|
this.browser.FrameLoadStart += OnFrameLoadStart;
|
||||||
|
this.browser.FrameLoadEnd += OnFrameLoadEnd;
|
||||||
|
this.browser.SetupZoomEvents();
|
||||||
|
this.createContextMenu = createContextMenu ?? DefaultContextMenuFactory;
|
||||||
|
this.autoReload = autoReload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Setup(BrowserSetup setup) {
|
||||||
|
var lifeSpanHandler = new CefLifeSpanHandler(PopupHandler.Instance);
|
||||||
|
var requestHandler = new CefRequestHandler(lifeSpanHandler, autoReload);
|
||||||
|
|
||||||
|
browser.DragHandler = new CefDragHandler(requestHandler, this);
|
||||||
|
browser.JsDialogHandler = new CustomJsDialogHandler();
|
||||||
|
browser.LifeSpanHandler = lifeSpanHandler;
|
||||||
|
browser.MenuHandler = createContextMenu(setup.ContextMenuHandler);
|
||||||
|
browser.RequestHandler = requestHandler;
|
||||||
|
browser.ResourceRequestHandlerFactory = new CefResourceRequestHandlerFactory(setup.ResourceRequestHandler, ResourceHandlerRegistry);
|
||||||
|
|
||||||
|
createContextMenu = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AttachBridgeObject(string name, object bridge) {
|
||||||
|
browser.JavascriptObjectRepository.Settings.LegacyBindingEnabled = true;
|
||||||
|
browser.JavascriptObjectRepository.Register(name, bridge, isAsync: true, BindingOptions.DefaultBinder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
|
||||||
|
base.OnLoadingStateChanged(e.IsLoading);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoadError(object sender, LoadErrorEventArgs e) {
|
||||||
|
base.OnLoadError(e.FailedUrl, e.ErrorCode, CefErrorCodeAdapter.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFrameLoadStart(object sender, FrameLoadStartEventArgs e) {
|
||||||
|
base.OnFrameLoadStart(e.Url, e.Frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFrameLoadEnd(object sender, FrameLoadEndEventArgs e) {
|
||||||
|
base.OnFrameLoadEnd(e.Url, e.Frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DownloadFile(string url, string path, Action onSuccess, Action<Exception> onError) {
|
||||||
|
Cef.UIThreadTaskFactory.StartNew(() => {
|
||||||
|
try {
|
||||||
|
using IFrame frame = browser.GetMainFrame();
|
||||||
|
var request = frame.CreateRequest(false);
|
||||||
|
|
||||||
|
request.Method = "GET";
|
||||||
|
request.Url = url;
|
||||||
|
request.Flags = UrlRequestFlags.AllowStoredCredentials;
|
||||||
|
request.SetReferrer(Url, ReferrerPolicy.Default);
|
||||||
|
|
||||||
|
var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||||
|
var client = new CefDownloadRequestClient(fileStream, onSuccess, onError);
|
||||||
|
frame.CreateUrlRequest(request, client);
|
||||||
|
} catch (Exception e) {
|
||||||
|
onError?.Invoke(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
Browser/Base/CefByteArrayResourceHandler.cs
Normal file
55
Browser/Base/CefByteArrayResourceHandler.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using CefSharp;
|
||||||
|
using CefSharp.Callback;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
|
using TweetLib.Browser.CEF.Logic;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefByteArrayResourceHandler : IResourceHandler {
|
||||||
|
private static readonly ByteArrayResourceHandlerLogic<IResponse>.WriteToOut<Stream> WriteToOut = delegate (Stream dataOut, byte[] dataIn, int position, int length) {
|
||||||
|
dataOut.Write(dataIn, position, length);
|
||||||
|
};
|
||||||
|
|
||||||
|
private ByteArrayResourceHandlerLogic<IResponse> logic;
|
||||||
|
|
||||||
|
public CefByteArrayResourceHandler() {
|
||||||
|
SetResource(new ByteArrayResource(Array.Empty<byte>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CefByteArrayResourceHandler(ByteArrayResource resource) {
|
||||||
|
SetResource(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetResource(ByteArrayResource resource) {
|
||||||
|
this.logic = new ByteArrayResourceHandlerLogic<IResponse>(resource, CefResponseAdapter.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IResourceHandler.Open(IRequest request, out bool handleRequest, ICallback callback) {
|
||||||
|
return logic.Open(out handleRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IResourceHandler.GetResponseHeaders(IResponse response, out long responseLength, out string redirectUrl) {
|
||||||
|
logic.GetResponseHeaders(response, out responseLength, out redirectUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IResourceHandler.Skip(long bytesToSkip, out long bytesSkipped, IResourceSkipCallback callback) {
|
||||||
|
return logic.Skip(bytesToSkip, out bytesSkipped, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IResourceHandler.Read(Stream dataOut, out int bytesRead, IResourceReadCallback callback) {
|
||||||
|
return logic.Read(WriteToOut, dataOut, out bytesRead, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IResourceHandler.ProcessRequest(IRequest request, ICallback callback) {
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IResourceHandler.ReadResponse(Stream dataOut, out int bytesRead, ICallback callback) {
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IResourceHandler.Cancel() {}
|
||||||
|
void IDisposable.Dispose() {}
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,17 @@
|
|||||||
using System;
|
using System;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
using TweetLib.Browser.Contexts;
|
using TweetLib.Browser.Contexts;
|
||||||
using TweetLib.Browser.Interfaces;
|
using TweetLib.Browser.Interfaces;
|
||||||
using TweetLib.Core.Features.TweetDeck;
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
using TweetLib.Core.Features.Twitter;
|
using TweetLib.Core.Features.Twitter;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Adapters {
|
namespace TweetDuck.Browser.Base {
|
||||||
sealed class CefContextMenuModel : IContextMenuBuilder {
|
sealed class CefContextMenuModel : IContextMenuBuilder {
|
||||||
private readonly IMenuModel model;
|
private readonly IMenuModel model;
|
||||||
private readonly CefContextMenuActionRegistry actionRegistry;
|
private readonly ContextMenuActionRegistry<CefMenuCommand> actionRegistry;
|
||||||
|
|
||||||
public CefContextMenuModel(IMenuModel model, CefContextMenuActionRegistry actionRegistry) {
|
public CefContextMenuModel(IMenuModel model, ContextMenuActionRegistry<CefMenuCommand> actionRegistry) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.actionRegistry = actionRegistry;
|
this.actionRegistry = actionRegistry;
|
||||||
}
|
}
|
31
Browser/Base/CefDownloadRequestClient.cs
Normal file
31
Browser/Base/CefDownloadRequestClient.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.CEF.Logic;
|
||||||
|
using static TweetLib.Browser.CEF.Logic.DownloadRequestClientLogic.RequestStatus;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefDownloadRequestClient : UrlRequestClient {
|
||||||
|
private readonly DownloadRequestClientLogic logic;
|
||||||
|
|
||||||
|
public CefDownloadRequestClient(FileStream fileStream, Action onSuccess, Action<Exception> onError) {
|
||||||
|
this.logic = new DownloadRequestClientLogic(fileStream, onSuccess, onError);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool GetAuthCredentials(bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback) {
|
||||||
|
return logic.GetAuthCredentials(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDownloadData(IUrlRequest request, Stream data) {
|
||||||
|
logic.OnDownloadData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnRequestComplete(IUrlRequest request) {
|
||||||
|
logic.OnRequestComplete(request.RequestStatus switch {
|
||||||
|
UrlRequestStatus.Success => Success,
|
||||||
|
UrlRequestStatus.Failed => Failed,
|
||||||
|
_ => Unknown
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
Browser/Base/CefDragDataAdapter.cs
Normal file
26
Browser/Base/CefDragDataAdapter.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefDragDataAdapter : IDragDataAdapter<IDragData> {
|
||||||
|
public static CefDragDataAdapter Instance { get; } = new CefDragDataAdapter();
|
||||||
|
|
||||||
|
private CefDragDataAdapter() {}
|
||||||
|
|
||||||
|
public bool IsLink(IDragData data) {
|
||||||
|
return data.IsLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetLink(IDragData data) {
|
||||||
|
return data.LinkUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsFragment(IDragData data) {
|
||||||
|
return data.IsFragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetFragmentAsText(IDragData data) {
|
||||||
|
return data.FragmentText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
Browser/Base/CefDragHandler.cs
Normal file
21
Browser/Base/CefDragHandler.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using CefSharp;
|
||||||
|
using CefSharp.Enums;
|
||||||
|
using TweetLib.Browser.CEF.Logic;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefDragHandler : IDragHandler {
|
||||||
|
private readonly DragHandlerLogic<IDragData, IRequest> logic;
|
||||||
|
|
||||||
|
public CefDragHandler(CefRequestHandler requestHandler, IScriptExecutor executor) {
|
||||||
|
this.logic = new DragHandlerLogic<IDragData, IRequest>(executor, requestHandler.Logic, CefDragDataAdapter.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask) {
|
||||||
|
return logic.OnDragEnter(dragData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnDraggableRegionsChanged(IWebBrowser browserControl, IBrowser browser, IFrame frame, IList<DraggableRegion> regions) {}
|
||||||
|
}
|
||||||
|
}
|
19
Browser/Base/CefErrorCodeAdapter.cs
Normal file
19
Browser/Base/CefErrorCodeAdapter.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefErrorCodeAdapter : IErrorCodeAdapter<CefErrorCode> {
|
||||||
|
public static CefErrorCodeAdapter Instance { get; } = new CefErrorCodeAdapter();
|
||||||
|
|
||||||
|
private CefErrorCodeAdapter() {}
|
||||||
|
|
||||||
|
public bool IsAborted(CefErrorCode errorCode) {
|
||||||
|
return errorCode == CefErrorCode.Aborted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetName(CefErrorCode errorCode) {
|
||||||
|
return Enum.GetName(typeof(CefErrorCode), errorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
Browser/Base/CefFrameAdapter.cs
Normal file
26
Browser/Base/CefFrameAdapter.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefFrameAdapter : IFrameAdapter<IFrame> {
|
||||||
|
public static CefFrameAdapter Instance { get; } = new CefFrameAdapter();
|
||||||
|
|
||||||
|
private CefFrameAdapter() {}
|
||||||
|
|
||||||
|
public bool IsValid(IFrame frame) {
|
||||||
|
return frame.IsValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsMain(IFrame frame) {
|
||||||
|
return frame.IsMain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadUrl(IFrame frame, string url) {
|
||||||
|
frame.LoadUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExecuteJavaScriptAsync(IFrame frame, string script, string identifier, int startLine = 1) {
|
||||||
|
frame.ExecuteJavaScriptAsync(script, identifier, startLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
Browser/Base/CefLifeSpanHandler.cs
Normal file
34
Browser/Base/CefLifeSpanHandler.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using CefSharp.Handler;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
using TweetLib.Browser.CEF.Logic;
|
||||||
|
using static TweetLib.Browser.CEF.Logic.LifeSpanHandlerLogic.TargetDisposition;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefLifeSpanHandler : LifeSpanHandler {
|
||||||
|
public LifeSpanHandlerLogic Logic { get; }
|
||||||
|
|
||||||
|
public CefLifeSpanHandler(IPopupHandler popupHandler) {
|
||||||
|
this.Logic = new LifeSpanHandlerLogic(popupHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnBeforePopup(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser) {
|
||||||
|
newBrowser = null;
|
||||||
|
return Logic.OnBeforePopup(targetUrl, ConvertTargetDisposition(targetDisposition));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool DoClose(IWebBrowser chromiumWebBrowser, IBrowser browser) {
|
||||||
|
return Logic.DoClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LifeSpanHandlerLogic.TargetDisposition ConvertTargetDisposition(WindowOpenDisposition targetDisposition) {
|
||||||
|
return targetDisposition switch {
|
||||||
|
WindowOpenDisposition.NewBackgroundTab => NewBackgroundTab,
|
||||||
|
WindowOpenDisposition.NewForegroundTab => NewForegroundTab,
|
||||||
|
WindowOpenDisposition.NewPopup => NewPopup,
|
||||||
|
WindowOpenDisposition.NewWindow => NewWindow,
|
||||||
|
_ => Other
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
Browser/Base/CefRequestAdapter.cs
Normal file
46
Browser/Base/CefRequestAdapter.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
using ResourceType = TweetLib.Browser.Request.ResourceType;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefRequestAdapter : IRequestAdapter<IRequest> {
|
||||||
|
public static CefRequestAdapter Instance { get; } = new CefRequestAdapter();
|
||||||
|
|
||||||
|
private CefRequestAdapter() {}
|
||||||
|
|
||||||
|
public ulong GetIdentifier(IRequest request) {
|
||||||
|
return request.Identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetUrl(IRequest request) {
|
||||||
|
return request.Url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetUrl(IRequest request, string url) {
|
||||||
|
request.Url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsTransitionForwardBack(IRequest request) {
|
||||||
|
return request.TransitionType.HasFlag(TransitionType.ForwardBack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsCspReport(IRequest request) {
|
||||||
|
return request.ResourceType == CefSharp.ResourceType.CspReport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceType GetResourceType(IRequest request) {
|
||||||
|
return request.ResourceType switch {
|
||||||
|
CefSharp.ResourceType.MainFrame => ResourceType.MainFrame,
|
||||||
|
CefSharp.ResourceType.Script => ResourceType.Script,
|
||||||
|
CefSharp.ResourceType.Stylesheet => ResourceType.Stylesheet,
|
||||||
|
CefSharp.ResourceType.Xhr => ResourceType.Xhr,
|
||||||
|
CefSharp.ResourceType.Image => ResourceType.Image,
|
||||||
|
_ => ResourceType.Unknown
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetHeader(IRequest request, string header, string value) {
|
||||||
|
request.SetHeaderByName(header, value, overwrite: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
Browser/Base/CefRequestHandler.cs
Normal file
30
Browser/Base/CefRequestHandler.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using CefSharp.Handler;
|
||||||
|
using TweetLib.Browser.CEF.Logic;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefRequestHandler : RequestHandler {
|
||||||
|
public RequestHandlerLogic<IRequest> Logic { get; }
|
||||||
|
|
||||||
|
private readonly bool autoReload;
|
||||||
|
|
||||||
|
public CefRequestHandler(CefLifeSpanHandler lifeSpanHandler, bool autoReload) {
|
||||||
|
this.Logic = new RequestHandlerLogic<IRequest>(CefRequestAdapter.Instance, lifeSpanHandler.Logic);
|
||||||
|
this.autoReload = autoReload;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnBeforeBrowse(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect) {
|
||||||
|
return Logic.OnBeforeBrowse(request, userGesture);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture) {
|
||||||
|
return Logic.OnOpenUrlFromTab(targetUrl, userGesture, CefLifeSpanHandler.ConvertTargetDisposition(targetDisposition));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status) {
|
||||||
|
if (autoReload) {
|
||||||
|
browser.Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
Browser/Base/CefResourceHandlerFactory.cs
Normal file
19
Browser/Base/CefResourceHandlerFactory.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefResourceHandlerFactory : IResourceHandlerFactory<IResourceHandler> {
|
||||||
|
public static CefResourceHandlerFactory Instance { get; } = new CefResourceHandlerFactory();
|
||||||
|
|
||||||
|
private CefResourceHandlerFactory() {}
|
||||||
|
|
||||||
|
public IResourceHandler CreateResourceHandler(ByteArrayResource resource) {
|
||||||
|
return new CefByteArrayResourceHandler(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetMimeTypeFromExtension(string extension) {
|
||||||
|
return Cef.GetMimeType(extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
Browser/Base/CefResourceRequestHandler.cs
Normal file
32
Browser/Base/CefResourceRequestHandler.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using CefSharp.Handler;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
|
using TweetLib.Browser.CEF.Logic;
|
||||||
|
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefResourceRequestHandler : ResourceRequestHandler {
|
||||||
|
private readonly ResourceRequestHandlerLogic<IRequest, IResponse, IResourceHandler> logic;
|
||||||
|
|
||||||
|
public CefResourceRequestHandler(ResourceHandlerRegistry<IResourceHandler> resourceHandlerRegistry, IResourceRequestHandler resourceRequestHandler) {
|
||||||
|
this.logic = new ResourceRequestHandlerLogic<IRequest, IResponse, IResourceHandler>(CefRequestAdapter.Instance, CefResponseAdapter.Instance, resourceHandlerRegistry, resourceRequestHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback) {
|
||||||
|
return logic.OnBeforeResourceLoad(request, callback) ? CefReturnValue.Continue : CefReturnValue.Cancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IResourceHandler GetResourceHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request) {
|
||||||
|
return logic.GetResourceHandler(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response) {
|
||||||
|
var filter = logic.GetResourceResponseFilter(request, response);
|
||||||
|
return filter == null ? null : new CefResponseFilter(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnResourceLoadComplete(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength) {
|
||||||
|
logic.OnResourceLoadComplete(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
Browser/Base/CefResourceRequestHandlerFactory.cs
Normal file
22
Browser/Base/CefResourceRequestHandlerFactory.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
|
using TweetLib.Browser.CEF.Logic;
|
||||||
|
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefResourceRequestHandlerFactory : IResourceRequestHandlerFactory {
|
||||||
|
bool IResourceRequestHandlerFactory.HasHandlers => true;
|
||||||
|
|
||||||
|
private readonly ResourceRequestHandlerFactoryLogic<CefResourceRequestHandler, IResourceHandler, IRequest> logic;
|
||||||
|
|
||||||
|
public CefResourceRequestHandlerFactory(IResourceRequestHandler resourceRequestHandler, ResourceHandlerRegistry<IResourceHandler> registry) {
|
||||||
|
this.logic = new ResourceRequestHandlerFactoryLogic<CefResourceRequestHandler, IResourceHandler, IRequest>(CefRequestAdapter.Instance, new CefResourceRequestHandler(registry, resourceRequestHandler), registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "RedundantAssignment")]
|
||||||
|
CefSharp.IResourceRequestHandler IResourceRequestHandlerFactory.GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling) {
|
||||||
|
return logic.GetResourceRequestHandler(request, ref disableDefaultHandling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
Browser/Base/CefResponseAdapter.cs
Normal file
31
Browser/Base/CefResponseAdapter.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefResponseAdapter : IResponseAdapter<IResponse> {
|
||||||
|
public static CefResponseAdapter Instance { get; } = new CefResponseAdapter();
|
||||||
|
|
||||||
|
private CefResponseAdapter() {}
|
||||||
|
|
||||||
|
public void SetCharset(IResponse response, string charset) {
|
||||||
|
response.Charset = charset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMimeType(IResponse response, string mimeType) {
|
||||||
|
response.MimeType = mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetStatus(IResponse response, int statusCode, string statusText) {
|
||||||
|
response.StatusCode = statusCode;
|
||||||
|
response.StatusText = statusText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetHeader(IResponse response, string header, string value) {
|
||||||
|
response.SetHeaderByName(header, value, overwrite: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetHeader(IResponse response, string header) {
|
||||||
|
return response.Headers[header];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
Browser/Base/CefResponseFilter.cs
Normal file
28
Browser/Base/CefResponseFilter.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.CEF.Logic;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefResponseFilter : IResponseFilter {
|
||||||
|
private readonly ResponseFilterLogic logic;
|
||||||
|
|
||||||
|
public CefResponseFilter(ResponseFilterLogic logic) {
|
||||||
|
this.logic = logic;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IResponseFilter.InitFilter() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten) {
|
||||||
|
return logic.Filter(dataIn, out dataInRead, dataOut, dataOut.Length, out dataOutWritten) switch {
|
||||||
|
ResponseFilterLogic.FilterStatus.NeedMoreData => FilterStatus.NeedMoreData,
|
||||||
|
ResponseFilterLogic.FilterStatus.Done => FilterStatus.Done,
|
||||||
|
_ => FilterStatus.Error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void IDisposable.Dispose() {}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
using System;
|
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
|
using TweetLib.Browser.CEF.Logic;
|
||||||
using TweetLib.Browser.Interfaces;
|
using TweetLib.Browser.Interfaces;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Adapters {
|
namespace TweetDuck.Browser.Base {
|
||||||
sealed class CefSchemeHandlerFactory : ISchemeHandlerFactory {
|
sealed class CefSchemeHandlerFactory : ISchemeHandlerFactory {
|
||||||
public static void Register(CefSettings settings, ICustomSchemeHandler handler) {
|
public static void Register(CefSettings settings, ICustomSchemeHandler handler) {
|
||||||
settings.RegisterScheme(new CefCustomScheme {
|
settings.RegisterScheme(new CefCustomScheme {
|
||||||
@ -16,14 +16,14 @@ public static void Register(CefSettings settings, ICustomSchemeHandler handler)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ICustomSchemeHandler handler;
|
private readonly SchemeHandlerFactoryLogic<IRequest, IResourceHandler> logic;
|
||||||
|
|
||||||
private CefSchemeHandlerFactory(ICustomSchemeHandler handler) {
|
private CefSchemeHandlerFactory(ICustomSchemeHandler handler) {
|
||||||
this.handler = handler;
|
this.logic = new SchemeHandlerFactoryLogic<IRequest, IResourceHandler>(handler, CefRequestAdapter.Instance, CefResourceHandlerFactory.Instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request) {
|
IResourceHandler ISchemeHandlerFactory.Create(IBrowser browser, IFrame frame, string schemeName, IRequest request) {
|
||||||
return Uri.TryCreate(request.Url, UriKind.Absolute, out var uri) ? handler.Resolve(uri)?.Visit(CefSchemeResourceVisitor.Instance) : null;
|
return logic.Create(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -47,13 +47,10 @@ public bool IsWaiting {
|
|||||||
|
|
||||||
public UpdateInstaller UpdateInstaller { get; private set; }
|
public UpdateInstaller UpdateInstaller { get; private set; }
|
||||||
|
|
||||||
#pragma warning disable IDE0069 // Disposable fields should be disposed
|
|
||||||
private readonly TweetDeckBrowser browser;
|
|
||||||
private readonly FormNotificationTweet notification;
|
|
||||||
#pragma warning restore IDE0069 // Disposable fields should be disposed
|
|
||||||
|
|
||||||
private readonly ResourceCache resourceCache;
|
private readonly ResourceCache resourceCache;
|
||||||
|
private readonly TweetDeckBrowser browser;
|
||||||
private readonly ITweetDeckInterface tweetDeckInterface;
|
private readonly ITweetDeckInterface tweetDeckInterface;
|
||||||
|
private readonly FormNotificationTweet notification;
|
||||||
private readonly PluginManager plugins;
|
private readonly PluginManager plugins;
|
||||||
private readonly UpdateChecker updates;
|
private readonly UpdateChecker updates;
|
||||||
private readonly ContextMenu contextMenu;
|
private readonly ContextMenu contextMenu;
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Browser.Adapters;
|
using TweetDuck.Browser.Base;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
using TweetLib.Browser.Contexts;
|
using TweetLib.Browser.Contexts;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
namespace TweetDuck.Browser.Handling {
|
||||||
abstract class ContextMenuBase : IContextMenuHandler {
|
class ContextMenuBase : IContextMenuHandler {
|
||||||
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand) 26500;
|
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand) 26500;
|
||||||
|
|
||||||
private static readonly HashSet<CefMenuCommand> AllowedCefCommands = new HashSet<CefMenuCommand> {
|
private static readonly HashSet<CefMenuCommand> AllowedCefCommands = new HashSet<CefMenuCommand> {
|
||||||
@ -31,11 +32,17 @@ abstract class ContextMenuBase : IContextMenuHandler {
|
|||||||
protected static UserConfig Config => Program.Config.User;
|
protected static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
private readonly TweetLib.Browser.Interfaces.IContextMenuHandler handler;
|
private readonly TweetLib.Browser.Interfaces.IContextMenuHandler handler;
|
||||||
private readonly CefContextMenuActionRegistry actionRegistry;
|
private readonly ContextMenuActionRegistry actionRegistry;
|
||||||
|
|
||||||
protected ContextMenuBase(TweetLib.Browser.Interfaces.IContextMenuHandler handler) {
|
public ContextMenuBase(TweetLib.Browser.Interfaces.IContextMenuHandler handler) {
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.actionRegistry = new CefContextMenuActionRegistry();
|
this.actionRegistry = new ContextMenuActionRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ContextMenuActionRegistry : ContextMenuActionRegistry<CefMenuCommand> {
|
||||||
|
protected override CefMenuCommand NextId(int n) {
|
||||||
|
return CefMenuCommand.UserFirst + 500 + n;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Context CreateContext(IContextMenuParams parameters) {
|
protected virtual Context CreateContext(IContextMenuParams parameters) {
|
||||||
@ -62,8 +69,13 @@ public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser bro
|
|||||||
}
|
}
|
||||||
|
|
||||||
AddSeparator(model);
|
AddSeparator(model);
|
||||||
handler.Show(new CefContextMenuModel(model, actionRegistry), CreateContext(parameters));
|
handler?.Show(new CefContextMenuModel(model, actionRegistry), CreateContext(parameters));
|
||||||
RemoveSeparatorIfLast(model);
|
RemoveSeparatorIfLast(model);
|
||||||
|
AddLastContextMenuItems(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void AddLastContextMenuItems(IMenuModel model) {
|
||||||
|
AddDebugMenuItems(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
|
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
|
||||||
@ -94,16 +106,16 @@ protected static void AddDebugMenuItems(IMenuModel model) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void RemoveSeparatorIfLast(IMenuModel model) {
|
|
||||||
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) == MenuItemType.Separator) {
|
|
||||||
model.RemoveAt(model.Count - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void AddSeparator(IMenuModel model) {
|
protected static void AddSeparator(IMenuModel model) {
|
||||||
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator) { // do not add separators if there is nothing to separate
|
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator) { // do not add separators if there is nothing to separate
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void RemoveSeparatorIfLast(IMenuModel model) {
|
||||||
|
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) == MenuItemType.Separator) {
|
||||||
|
model.RemoveAt(model.Count - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Browser.Adapters;
|
using TweetDuck.Browser.Base;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetLib.Browser.Contexts;
|
using TweetLib.Browser.Contexts;
|
||||||
using TweetLib.Core.Features.TweetDeck;
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
@ -62,6 +62,8 @@ public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser br
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void AddLastContextMenuItems(IMenuModel model) {}
|
||||||
|
|
||||||
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
|
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
|
||||||
if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)) {
|
if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
sealed class ContextMenuGuide : ContextMenuBase {
|
|
||||||
public ContextMenuGuide(IContextMenuHandler handler) : base(handler) {}
|
|
||||||
|
|
||||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
|
||||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
|
||||||
AddDebugMenuItems(model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,7 +20,6 @@ protected override Context CreateContext(IContextMenuParams parameters) {
|
|||||||
|
|
||||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
||||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
AddDebugMenuItems(model);
|
|
||||||
form.InvokeAsyncSafe(() => form.ContextMenuOpen = true);
|
form.InvokeAsyncSafe(() => form.ContextMenuOpen = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
namespace TweetDuck.Browser.Handling {
|
||||||
sealed class JavaScriptDialogHandler : IJsDialogHandler {
|
sealed class CustomJsDialogHandler : IJsDialogHandler {
|
||||||
private static FormMessage CreateMessageForm(string caption, string text) {
|
private static FormMessage CreateMessageForm(string caption, string text) {
|
||||||
MessageBoxIcon icon = MessageBoxIcon.None;
|
MessageBoxIcon icon = MessageBoxIcon.None;
|
||||||
int pipe = text.IndexOf('|');
|
int pipe = text.IndexOf('|');
|
@ -1,31 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using CefSharp.Handler;
|
|
||||||
using TweetLib.Core;
|
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
sealed class CustomLifeSpanHandler : LifeSpanHandler {
|
|
||||||
public static bool HandleLinkClick(WindowOpenDisposition targetDisposition, string targetUrl) {
|
|
||||||
switch (targetDisposition) {
|
|
||||||
case WindowOpenDisposition.NewBackgroundTab:
|
|
||||||
case WindowOpenDisposition.NewForegroundTab:
|
|
||||||
case WindowOpenDisposition.NewPopup when !TwitterUrls.IsAllowedPopupUrl(targetUrl):
|
|
||||||
case WindowOpenDisposition.NewWindow:
|
|
||||||
App.SystemHandler.OpenBrowser(targetUrl);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser) {
|
|
||||||
newBrowser = null;
|
|
||||||
return HandleLinkClick(targetDisposition, targetUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool DoClose(IWebBrowser browserControl, IBrowser browser) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using CefSharp;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
sealed class DownloadRequestClient : UrlRequestClient {
|
|
||||||
private readonly FileStream fileStream;
|
|
||||||
private readonly Action onSuccess;
|
|
||||||
private readonly Action<Exception> onError;
|
|
||||||
|
|
||||||
private bool hasFailed;
|
|
||||||
|
|
||||||
public DownloadRequestClient(FileStream fileStream, Action onSuccess, Action<Exception> onError) {
|
|
||||||
this.fileStream = fileStream;
|
|
||||||
this.onSuccess = onSuccess;
|
|
||||||
this.onError = onError;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool GetAuthCredentials(bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback) {
|
|
||||||
onError?.Invoke(new Exception("This URL requires authentication."));
|
|
||||||
fileStream.Dispose();
|
|
||||||
hasFailed = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnDownloadData(IUrlRequest request, Stream data) {
|
|
||||||
if (hasFailed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
data.CopyTo(fileStream);
|
|
||||||
} catch (Exception e) {
|
|
||||||
fileStream.Dispose();
|
|
||||||
onError?.Invoke(e);
|
|
||||||
hasFailed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnRequestComplete(IUrlRequest request) {
|
|
||||||
if (hasFailed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isEmpty = fileStream.Position == 0;
|
|
||||||
fileStream.Dispose();
|
|
||||||
|
|
||||||
var status = request.RequestStatus;
|
|
||||||
if (status == UrlRequestStatus.Failed) {
|
|
||||||
onError?.Invoke(new Exception("Unknown error."));
|
|
||||||
}
|
|
||||||
else if (status == UrlRequestStatus.Success) {
|
|
||||||
if (isEmpty) {
|
|
||||||
onError?.Invoke(new Exception("File is empty."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
onSuccess?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using CefSharp;
|
|
||||||
using CefSharp.Enums;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
sealed class DragHandlerBrowser : IDragHandler {
|
|
||||||
private readonly RequestHandlerBrowser requestHandler;
|
|
||||||
|
|
||||||
public DragHandlerBrowser(RequestHandlerBrowser requestHandler) {
|
|
||||||
this.requestHandler = requestHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask) {
|
|
||||||
void TriggerDragStart(string type, string data = null) {
|
|
||||||
browserControl.BrowserCore.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", type, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestHandler.BlockNextUserNavUrl = dragData.LinkUrl; // empty if not a link
|
|
||||||
|
|
||||||
if (dragData.IsLink) {
|
|
||||||
TriggerDragStart("link", dragData.LinkUrl);
|
|
||||||
}
|
|
||||||
else if (dragData.IsFragment) {
|
|
||||||
TriggerDragStart("text", dragData.FragmentText.Trim());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
TriggerDragStart("unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnDraggableRegionsChanged(IWebBrowser browserControl, IBrowser browser, IFrame frame, IList<DraggableRegion> regions) {}
|
|
||||||
}
|
|
||||||
}
|
|
19
Browser/Handling/PopupHandler.cs
Normal file
19
Browser/Handling/PopupHandler.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
using TweetLib.Core;
|
||||||
|
using TweetLib.Core.Features.Twitter;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Handling {
|
||||||
|
sealed class PopupHandler : IPopupHandler {
|
||||||
|
public static PopupHandler Instance { get; } = new PopupHandler();
|
||||||
|
|
||||||
|
private PopupHandler() {}
|
||||||
|
|
||||||
|
public bool IsPopupAllowed(string url) {
|
||||||
|
return TwitterUrls.IsAllowedPopupUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenExternalBrowser(string url) {
|
||||||
|
App.SystemHandler.OpenBrowser(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using CefSharp.Handler;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
class RequestHandlerBase : RequestHandler {
|
|
||||||
private readonly bool autoReload;
|
|
||||||
|
|
||||||
public RequestHandlerBase(bool autoReload) {
|
|
||||||
this.autoReload = autoReload;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture) {
|
|
||||||
return CustomLifeSpanHandler.HandleLinkClick(targetDisposition, targetUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status) {
|
|
||||||
if (autoReload) {
|
|
||||||
browser.Reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
sealed class RequestHandlerBrowser : RequestHandlerBase {
|
|
||||||
public string BlockNextUserNavUrl { get; set; }
|
|
||||||
|
|
||||||
public RequestHandlerBrowser() : base(true) {}
|
|
||||||
|
|
||||||
protected override bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect) {
|
|
||||||
if (userGesture && request.TransitionType == TransitionType.LinkClicked) {
|
|
||||||
bool block = request.Url == BlockNextUserNavUrl;
|
|
||||||
BlockNextUserNavUrl = string.Empty;
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
else if (request.TransitionType.HasFlag(TransitionType.ForwardBack) && TwitterUrls.IsTweetDeck(frame.Url)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.OnBeforeBrowse(browserControl, browser, frame, request, userGesture, isRedirect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using CefSharp;
|
|
||||||
using CefSharp.Callback;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
sealed class ResourceHandlerNotification : IResourceHandler {
|
|
||||||
private readonly NameValueCollection headers = new NameValueCollection(0);
|
|
||||||
private MemoryStream dataIn;
|
|
||||||
|
|
||||||
public void SetHTML(string html) {
|
|
||||||
dataIn?.Dispose();
|
|
||||||
dataIn = ResourceHandler.GetMemoryStream(html, Encoding.UTF8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
if (dataIn != null) {
|
|
||||||
dataIn.Dispose();
|
|
||||||
dataIn = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IResourceHandler.Open(IRequest request, out bool handleRequest, ICallback callback) {
|
|
||||||
callback.Dispose();
|
|
||||||
handleRequest = true;
|
|
||||||
|
|
||||||
if (dataIn != null) {
|
|
||||||
dataIn.Position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IResourceHandler.GetResponseHeaders(IResponse response, out long responseLength, out string redirectUrl) {
|
|
||||||
redirectUrl = null;
|
|
||||||
|
|
||||||
response.MimeType = "text/html";
|
|
||||||
response.StatusCode = 200;
|
|
||||||
response.StatusText = "OK";
|
|
||||||
response.Headers = headers;
|
|
||||||
responseLength = dataIn?.Length ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IResourceHandler.Read(Stream dataOut, out int bytesRead, IResourceReadCallback callback) {
|
|
||||||
callback?.Dispose(); // TODO unnecessary null check once ReadResponse is removed
|
|
||||||
|
|
||||||
try {
|
|
||||||
byte[] buffer = new byte[Math.Min(dataIn.Length - dataIn.Position, dataOut.Length)];
|
|
||||||
int length = buffer.Length;
|
|
||||||
|
|
||||||
dataIn.Read(buffer, 0, length);
|
|
||||||
dataOut.Write(buffer, 0, length);
|
|
||||||
bytesRead = length;
|
|
||||||
} catch { // catch IOException, possibly NullReferenceException if dataIn is null
|
|
||||||
bytesRead = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytesRead > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IResourceHandler.Skip(long bytesToSkip, out long bytesSkipped, IResourceSkipCallback callback) {
|
|
||||||
bytesSkipped = -2; // ERR_FAILED
|
|
||||||
callback.Dispose();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IResourceHandler.ProcessRequest(IRequest request, ICallback callback) {
|
|
||||||
return ((IResourceHandler) this).Open(request, out bool _, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IResourceHandler.ReadResponse(Stream dataOut, out int bytesRead, ICallback callback) {
|
|
||||||
return ((IResourceHandler) this).Read(dataOut, out bytesRead, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IResourceHandler.Cancel() {}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,13 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.Text;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using TweetDuck.Browser.Adapters;
|
using TweetDuck.Browser.Base;
|
||||||
using TweetDuck.Browser.Handling;
|
using TweetDuck.Browser.Handling;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
using TweetLib.Browser.Interfaces;
|
using TweetLib.Browser.Interfaces;
|
||||||
using TweetLib.Core.Features.Notifications;
|
using TweetLib.Core.Features.Notifications;
|
||||||
using TweetLib.Core.Features.Twitter;
|
using TweetLib.Core.Features.Twitter;
|
||||||
@ -88,14 +90,11 @@ protected virtual FormBorderStyle NotificationBorderStyle {
|
|||||||
|
|
||||||
private readonly FormBrowser owner;
|
private readonly FormBrowser owner;
|
||||||
|
|
||||||
protected readonly IBrowserComponent browserComponent;
|
protected readonly ChromiumWebBrowser browser;
|
||||||
|
protected readonly CefBrowserComponent browserComponent;
|
||||||
private readonly NotificationBrowser browserImpl;
|
private readonly NotificationBrowser browserImpl;
|
||||||
|
|
||||||
#pragma warning disable IDE0069 // Disposable fields should be disposed
|
private readonly CefByteArrayResourceHandler resourceHandler = new CefByteArrayResourceHandler();
|
||||||
protected readonly ChromiumWebBrowser browser;
|
|
||||||
#pragma warning restore IDE0069 // Disposable fields should be disposed
|
|
||||||
|
|
||||||
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
|
|
||||||
|
|
||||||
private DesktopNotification currentNotification;
|
private DesktopNotification currentNotification;
|
||||||
private int pauseCounter;
|
private int pauseCounter;
|
||||||
@ -115,11 +114,10 @@ protected FormNotificationBase(FormBrowser owner, CreateBrowserImplFunc createBr
|
|||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.owner.FormClosed += owner_FormClosed;
|
this.owner.FormClosed += owner_FormClosed;
|
||||||
|
|
||||||
this.browser = new ChromiumWebBrowser(NotificationBrowser.BlankURL) {
|
this.browser = new ChromiumWebBrowser(NotificationBrowser.BlankURL);
|
||||||
RequestHandler = new RequestHandlerBase(false)
|
this.browserComponent = new CefBrowserComponent(browser, handler => new ContextMenuNotification(this, handler), autoReload: false);
|
||||||
};
|
this.browserComponent.ResourceHandlerRegistry.RegisterStatic(NotificationBrowser.BlankURL, string.Empty);
|
||||||
|
this.browserComponent.ResourceHandlerRegistry.RegisterDynamic(TwitterUrls.TweetDeck, resourceHandler);
|
||||||
this.browserComponent = new ComponentImpl(browser, this);
|
|
||||||
this.browserImpl = createBrowserImpl(this, browserComponent);
|
this.browserImpl = createBrowserImpl(this, browserComponent);
|
||||||
|
|
||||||
this.browser.Dock = DockStyle.None;
|
this.browser.Dock = DockStyle.None;
|
||||||
@ -139,29 +137,9 @@ protected FormNotificationBase(FormBrowser owner, CreateBrowserImplFunc createBr
|
|||||||
UpdateTitle();
|
UpdateTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected sealed class ComponentImpl : CefBrowserComponent {
|
|
||||||
private readonly FormNotificationBase owner;
|
|
||||||
|
|
||||||
public ComponentImpl(ChromiumWebBrowser browser, FormNotificationBase owner) : base(browser) {
|
|
||||||
this.owner = owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ContextMenuBase SetupContextMenu(IContextMenuHandler handler) {
|
|
||||||
return new ContextMenuNotification(owner, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override CefResourceHandlerFactory SetupResourceHandlerFactory(IResourceRequestHandler handler) {
|
|
||||||
var registry = new CefResourceHandlerRegistry();
|
|
||||||
registry.RegisterStatic(NotificationBrowser.BlankURL, string.Empty);
|
|
||||||
registry.RegisterDynamic(TwitterUrls.TweetDeck, owner.resourceHandler);
|
|
||||||
return new CefResourceHandlerFactory(handler, registry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing) {
|
protected override void Dispose(bool disposing) {
|
||||||
if (disposing) {
|
if (disposing) {
|
||||||
components?.Dispose();
|
components?.Dispose();
|
||||||
resourceHandler.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
@ -207,7 +185,7 @@ public virtual void ResumeNotification() {
|
|||||||
|
|
||||||
protected virtual void LoadTweet(DesktopNotification tweet) {
|
protected virtual void LoadTweet(DesktopNotification tweet) {
|
||||||
currentNotification = tweet;
|
currentNotification = tweet;
|
||||||
resourceHandler.SetHTML(browserImpl.GetTweetHTML(tweet));
|
resourceHandler.SetResource(new ByteArrayResource(browserImpl.GetTweetHTML(tweet), Encoding.UTF8));
|
||||||
|
|
||||||
browser.Load(TwitterUrls.TweetDeck);
|
browser.Load(TwitterUrls.TweetDeck);
|
||||||
DisplayTooltip(null);
|
DisplayTooltip(null);
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Browser.Adapters;
|
using CefSharp;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Dialogs;
|
using TweetDuck.Dialogs;
|
||||||
using TweetDuck.Dialogs.Settings;
|
using TweetDuck.Dialogs.Settings;
|
||||||
using TweetDuck.Management;
|
using TweetDuck.Management;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
using TweetLib.Core.Features.TweetDeck;
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Notification {
|
namespace TweetDuck.Browser.Notification {
|
||||||
sealed class SoundNotification : ISoundNotificationHandler {
|
sealed class SoundNotification : ISoundNotificationHandler {
|
||||||
public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
|
public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
|
||||||
|
|
||||||
private readonly CefResourceHandlerRegistry registry;
|
private readonly ResourceHandlerRegistry<IResourceHandler> registry;
|
||||||
|
|
||||||
public SoundNotification(CefResourceHandlerRegistry registry) {
|
public SoundNotification(ResourceHandlerRegistry<IResourceHandler> registry) {
|
||||||
this.registry = registry;
|
this.registry = registry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using TweetDuck.Browser.Adapters;
|
using TweetDuck.Browser.Base;
|
||||||
using TweetDuck.Browser.Handling;
|
using TweetDuck.Browser.Handling;
|
||||||
using TweetDuck.Browser.Notification;
|
using TweetDuck.Browser.Notification;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
@ -11,8 +11,6 @@
|
|||||||
using TweetLib.Core.Features.TweetDeck;
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
using TweetLib.Core.Features.Twitter;
|
using TweetLib.Core.Features.Twitter;
|
||||||
using TweetLib.Core.Systems.Updates;
|
using TweetLib.Core.Systems.Updates;
|
||||||
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
|
||||||
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
|
|
||||||
using TweetDeckBrowserImpl = TweetLib.Core.Features.TweetDeck.TweetDeckBrowser;
|
using TweetDeckBrowserImpl = TweetLib.Core.Features.TweetDeck.TweetDeckBrowser;
|
||||||
|
|
||||||
namespace TweetDuck.Browser {
|
namespace TweetDuck.Browser {
|
||||||
@ -42,24 +40,18 @@ public bool IsTweetDeckWebsite {
|
|||||||
private readonly ChromiumWebBrowser browser;
|
private readonly ChromiumWebBrowser browser;
|
||||||
|
|
||||||
public TweetDeckBrowser(FormBrowser owner, PluginManager pluginManager, ITweetDeckInterface tweetDeckInterface, UpdateChecker updateChecker) {
|
public TweetDeckBrowser(FormBrowser owner, PluginManager pluginManager, ITweetDeckInterface tweetDeckInterface, UpdateChecker updateChecker) {
|
||||||
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
|
|
||||||
|
|
||||||
this.browser = new ChromiumWebBrowser(TwitterUrls.TweetDeck) {
|
this.browser = new ChromiumWebBrowser(TwitterUrls.TweetDeck) {
|
||||||
DialogHandler = new FileDialogHandler(),
|
DialogHandler = new FileDialogHandler(),
|
||||||
DragHandler = new DragHandlerBrowser(requestHandler),
|
KeyboardHandler = new CustomKeyboardHandler(owner)
|
||||||
KeyboardHandler = new CustomKeyboardHandler(owner),
|
|
||||||
RequestHandler = requestHandler
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ReSharper disable once PossiblyImpureMethodCallOnReadonlyVariable
|
// ReSharper disable once PossiblyImpureMethodCallOnReadonlyVariable
|
||||||
this.browser.BrowserSettings.BackgroundColor = (uint) TweetDeckBrowserImpl.BackgroundColor.ToArgb();
|
this.browser.BrowserSettings.BackgroundColor = (uint) TweetDeckBrowserImpl.BackgroundColor.ToArgb();
|
||||||
|
|
||||||
var extraContext = new TweetDeckExtraContext();
|
var extraContext = new TweetDeckExtraContext();
|
||||||
var resourceHandlerRegistry = new CefResourceHandlerRegistry();
|
|
||||||
var soundNotificationHandler = new SoundNotification(resourceHandlerRegistry);
|
|
||||||
|
|
||||||
this.browserComponent = new ComponentImpl(browser, owner, extraContext, resourceHandlerRegistry);
|
this.browserComponent = new CefBrowserComponent(browser, handler => new ContextMenuBrowser(owner, handler, extraContext));
|
||||||
this.browserImpl = new TweetDeckBrowserImpl(browserComponent, tweetDeckInterface, extraContext, soundNotificationHandler, pluginManager, updateChecker);
|
this.browserImpl = new TweetDeckBrowserImpl(browserComponent, tweetDeckInterface, extraContext, new SoundNotification(browserComponent.ResourceHandlerRegistry), pluginManager, updateChecker);
|
||||||
|
|
||||||
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)) {
|
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)) {
|
||||||
browserComponent.PageLoadEnd += (sender, args) => {
|
browserComponent.PageLoadEnd += (sender, args) => {
|
||||||
@ -72,26 +64,6 @@ public TweetDeckBrowser(FormBrowser owner, PluginManager pluginManager, ITweetDe
|
|||||||
owner.Controls.Add(browser);
|
owner.Controls.Add(browser);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class ComponentImpl : CefBrowserComponent {
|
|
||||||
private readonly FormBrowser owner;
|
|
||||||
private readonly TweetDeckExtraContext extraContext;
|
|
||||||
private readonly CefResourceHandlerRegistry registry;
|
|
||||||
|
|
||||||
public ComponentImpl(ChromiumWebBrowser browser, FormBrowser owner, TweetDeckExtraContext extraContext, CefResourceHandlerRegistry registry) : base(browser) {
|
|
||||||
this.owner = owner;
|
|
||||||
this.extraContext = extraContext;
|
|
||||||
this.registry = registry;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ContextMenuBase SetupContextMenu(IContextMenuHandler handler) {
|
|
||||||
return new ContextMenuBrowser(owner, handler, extraContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override CefResourceHandlerFactory SetupResourceHandlerFactory(IResourceRequestHandler handler) {
|
|
||||||
return new CefResourceHandlerFactory(handler, registry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PrepareSize(Size size) {
|
public void PrepareSize(Size size) {
|
||||||
if (!Ready) {
|
if (!Ready) {
|
||||||
browser.Size = size;
|
browser.Size = size;
|
||||||
|
@ -2,12 +2,11 @@
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using TweetDuck.Browser;
|
using TweetDuck.Browser;
|
||||||
using TweetDuck.Browser.Adapters;
|
using TweetDuck.Browser.Base;
|
||||||
using TweetDuck.Browser.Handling;
|
using TweetDuck.Browser.Handling;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Management;
|
using TweetDuck.Management;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
using TweetLib.Browser.Interfaces;
|
|
||||||
using TweetLib.Core.Features;
|
using TweetLib.Core.Features;
|
||||||
|
|
||||||
namespace TweetDuck.Dialogs {
|
namespace TweetDuck.Dialogs {
|
||||||
@ -31,9 +30,7 @@ public static void Show(string hash = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable IDE0069 // Disposable fields should be disposed
|
|
||||||
private readonly ChromiumWebBrowser browser;
|
private readonly ChromiumWebBrowser browser;
|
||||||
#pragma warning restore IDE0069 // Disposable fields should be disposed
|
|
||||||
|
|
||||||
private FormGuide(string url, Form owner) {
|
private FormGuide(string url, Form owner) {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@ -43,13 +40,12 @@ private FormGuide(string url, Form owner) {
|
|||||||
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
|
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
|
||||||
|
|
||||||
browser = new ChromiumWebBrowser(url) {
|
browser = new ChromiumWebBrowser(url) {
|
||||||
KeyboardHandler = new CustomKeyboardHandler(null),
|
KeyboardHandler = new CustomKeyboardHandler(null)
|
||||||
RequestHandler = new RequestHandlerBase(true)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
browser.BrowserSettings.BackgroundColor = (uint) BackColor.ToArgb();
|
browser.BrowserSettings.BackgroundColor = (uint) BackColor.ToArgb();
|
||||||
|
|
||||||
var browserComponent = new ComponentImpl(browser);
|
var browserComponent = new CefBrowserComponent(browser);
|
||||||
var browserImpl = new BaseBrowser(browserComponent);
|
var browserImpl = new BaseBrowser(browserComponent);
|
||||||
|
|
||||||
BrowserUtils.SetupDockOnLoad(browserComponent, browser);
|
BrowserUtils.SetupDockOnLoad(browserComponent, browser);
|
||||||
@ -62,18 +58,6 @@ private FormGuide(string url, Form owner) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class ComponentImpl : CefBrowserComponent {
|
|
||||||
public ComponentImpl(ChromiumWebBrowser browser) : base(browser) {}
|
|
||||||
|
|
||||||
protected override ContextMenuBase SetupContextMenu(IContextMenuHandler handler) {
|
|
||||||
return new ContextMenuGuide(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override CefResourceHandlerFactory SetupResourceHandlerFactory(IResourceRequestHandler handler) {
|
|
||||||
return new CefResourceHandlerFactory(handler, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing) {
|
protected override void Dispose(bool disposing) {
|
||||||
if (disposing) {
|
if (disposing) {
|
||||||
components?.Dispose();
|
components?.Dispose();
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
|
using TweetLib.Browser.CEF.Utils;
|
||||||
using TweetLib.Core;
|
using TweetLib.Core;
|
||||||
using TweetLib.Core.Features.Chromium;
|
|
||||||
|
|
||||||
namespace TweetDuck.Dialogs.Settings {
|
namespace TweetDuck.Dialogs.Settings {
|
||||||
sealed partial class DialogSettingsCefArgs : Form {
|
sealed partial class DialogSettingsCefArgs : Form {
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using TweetLib.Browser.CEF.Utils;
|
||||||
using TweetLib.Core;
|
using TweetLib.Core;
|
||||||
|
|
||||||
namespace TweetDuck.Management {
|
namespace TweetDuck.Management {
|
||||||
static class BrowserCache {
|
static class BrowserCache {
|
||||||
public static string CacheFolder => Path.Combine(App.StoragePath, "Cache");
|
public static string CacheFolder => CefUtils.GetCacheFolder(App.StoragePath);
|
||||||
|
|
||||||
private static bool clearOnExit;
|
private static bool clearOnExit;
|
||||||
private static Timer autoClearTimer;
|
private static Timer autoClearTimer;
|
||||||
|
@ -5,16 +5,16 @@
|
|||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using TweetDuck.Application;
|
using TweetDuck.Application;
|
||||||
using TweetDuck.Browser;
|
using TweetDuck.Browser;
|
||||||
using TweetDuck.Browser.Adapters;
|
using TweetDuck.Browser.Base;
|
||||||
using TweetDuck.Browser.Handling;
|
using TweetDuck.Browser.Handling;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Dialogs;
|
using TweetDuck.Dialogs;
|
||||||
using TweetDuck.Management;
|
using TweetDuck.Management;
|
||||||
using TweetDuck.Updates;
|
using TweetDuck.Updates;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
|
using TweetLib.Browser.CEF.Utils;
|
||||||
using TweetLib.Core;
|
using TweetLib.Core;
|
||||||
using TweetLib.Core.Application;
|
using TweetLib.Core.Application;
|
||||||
using TweetLib.Core.Features.Chromium;
|
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetLib.Core.Features.Plugins;
|
||||||
using TweetLib.Core.Features.Plugins.Config;
|
using TweetLib.Core.Features.Plugins.Config;
|
||||||
using TweetLib.Core.Features.TweetDeck;
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
|
@ -66,6 +66,10 @@
|
|||||||
<Project>{eefb1f37-7cad-46bd-8042-66e7b502ab02}</Project>
|
<Project>{eefb1f37-7cad-46bd-8042-66e7b502ab02}</Project>
|
||||||
<Name>TweetLib.Browser</Name>
|
<Name>TweetLib.Browser</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="lib\TweetLib.Browser.CEF\TweetLib.Browser.CEF.csproj">
|
||||||
|
<Project>{1b7793c6-9002-483e-9bd7-897fe6cd18fb}</Project>
|
||||||
|
<Name>TweetLib.Browser.CEF</Name>
|
||||||
|
</ProjectReference>
|
||||||
<ProjectReference Include="lib\TweetLib.Core\TweetLib.Core.csproj">
|
<ProjectReference Include="lib\TweetLib.Core\TweetLib.Core.csproj">
|
||||||
<Project>{93ba3cb4-a812-4949-b07d-8d393fb38937}</Project>
|
<Project>{93ba3cb4-a812-4949-b07d-8d393fb38937}</Project>
|
||||||
<Name>TweetLib.Core</Name>
|
<Name>TweetLib.Core</Name>
|
||||||
@ -91,29 +95,32 @@
|
|||||||
<Compile Include="Application\FileDialogs.cs" />
|
<Compile Include="Application\FileDialogs.cs" />
|
||||||
<Compile Include="Application\MessageDialogs.cs" />
|
<Compile Include="Application\MessageDialogs.cs" />
|
||||||
<Compile Include="Application\SystemHandler.cs" />
|
<Compile Include="Application\SystemHandler.cs" />
|
||||||
<Compile Include="Browser\Adapters\CefBrowserComponent.cs" />
|
<Compile Include="Browser\Base\CefBrowserAdapter.cs" />
|
||||||
<Compile Include="Browser\Adapters\CefContextMenuActionRegistry.cs" />
|
<Compile Include="Browser\Base\CefBrowserComponent.cs" />
|
||||||
<Compile Include="Browser\Adapters\CefContextMenuModel.cs" />
|
<Compile Include="Browser\Base\CefContextMenuModel.cs" />
|
||||||
<Compile Include="Browser\Adapters\CefResourceHandlerFactory.cs" />
|
<Compile Include="Browser\Base\CefDownloadRequestClient.cs" />
|
||||||
<Compile Include="Browser\Adapters\CefResourceHandlerRegistry.cs" />
|
<Compile Include="Browser\Base\CefDragDataAdapter.cs" />
|
||||||
<Compile Include="Browser\Adapters\CefResourceRequestHandler.cs" />
|
<Compile Include="Browser\Base\CefDragHandler.cs" />
|
||||||
<Compile Include="Browser\Adapters\CefSchemeHandlerFactory.cs" />
|
<Compile Include="Browser\Base\CefErrorCodeAdapter.cs" />
|
||||||
<Compile Include="Browser\Adapters\CefSchemeResourceVisitor.cs" />
|
<Compile Include="Browser\Base\CefFrameAdapter.cs" />
|
||||||
|
<Compile Include="Browser\Base\CefLifeSpanHandler.cs" />
|
||||||
|
<Compile Include="Browser\Base\CefRequestAdapter.cs" />
|
||||||
|
<Compile Include="Browser\Base\CefRequestHandler.cs" />
|
||||||
|
<Compile Include="Browser\Base\CefResourceHandlerFactory.cs" />
|
||||||
|
<Compile Include="Browser\Base\CefByteArrayResourceHandler.cs" />
|
||||||
|
<Compile Include="Browser\Base\CefResourceRequestHandlerFactory.cs" />
|
||||||
|
<Compile Include="Browser\Base\CefResourceRequestHandler.cs" />
|
||||||
|
<Compile Include="Browser\Base\CefResponseAdapter.cs" />
|
||||||
|
<Compile Include="Browser\Base\CefResponseFilter.cs" />
|
||||||
|
<Compile Include="Browser\Base\CefSchemeHandlerFactory.cs" />
|
||||||
<Compile Include="Browser\Handling\BrowserProcessHandler.cs" />
|
<Compile Include="Browser\Handling\BrowserProcessHandler.cs" />
|
||||||
<Compile Include="Browser\Handling\ContextMenuBase.cs" />
|
<Compile Include="Browser\Handling\ContextMenuBase.cs" />
|
||||||
<Compile Include="Browser\Handling\ContextMenuBrowser.cs" />
|
<Compile Include="Browser\Handling\ContextMenuBrowser.cs" />
|
||||||
<Compile Include="Browser\Handling\ContextMenuGuide.cs" />
|
|
||||||
<Compile Include="Browser\Handling\ContextMenuNotification.cs" />
|
<Compile Include="Browser\Handling\ContextMenuNotification.cs" />
|
||||||
<Compile Include="Browser\Handling\CustomKeyboardHandler.cs" />
|
<Compile Include="Browser\Handling\CustomKeyboardHandler.cs" />
|
||||||
<Compile Include="Browser\Handling\CustomLifeSpanHandler.cs" />
|
|
||||||
<Compile Include="Browser\Handling\DownloadRequestClient.cs" />
|
|
||||||
<Compile Include="Browser\Handling\DragHandlerBrowser.cs" />
|
|
||||||
<Compile Include="Browser\Handling\FileDialogHandler.cs" />
|
<Compile Include="Browser\Handling\FileDialogHandler.cs" />
|
||||||
<Compile Include="Browser\Handling\JavaScriptDialogHandler.cs" />
|
<Compile Include="Browser\Handling\CustomJsDialogHandler.cs" />
|
||||||
<Compile Include="Browser\Handling\RequestHandlerBase.cs" />
|
<Compile Include="Browser\Handling\PopupHandler.cs" />
|
||||||
<Compile Include="Browser\Handling\RequestHandlerBrowser.cs" />
|
|
||||||
<Compile Include="Browser\Handling\ResourceHandlerNotification.cs" />
|
|
||||||
<Compile Include="Browser\Handling\ResponseFilter.cs" />
|
|
||||||
<Compile Include="Browser\Notification\FormNotificationExample.cs">
|
<Compile Include="Browser\Notification\FormNotificationExample.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
@ -14,10 +14,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Core", "lib\TweetL
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Browser", "lib\TweetLib.Browser\TweetLib.Browser.csproj", "{EEFB1F37-7CAD-46BD-8042-66E7B502AB02}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Browser", "lib\TweetLib.Browser\TweetLib.Browser.csproj", "{EEFB1F37-7CAD-46BD-8042-66E7B502AB02}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Browser.CEF", "lib\TweetLib.Browser.CEF\TweetLib.Browser.CEF.csproj", "{1B7793C6-9002-483E-9BD7-897FE6CD18FB}"
|
||||||
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Utils", "lib\TweetLib.Utils\TweetLib.Utils.csproj", "{476B1007-B12C-447F-B855-9886048201D6}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Utils", "lib\TweetLib.Utils\TweetLib.Utils.csproj", "{476B1007-B12C-447F-B855-9886048201D6}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TweetTest.Core", "lib\TweetTest.Core\TweetTest.Core.fsproj", "{2D7DFBA6-057D-4111-B61B-E4CB1E2BF369}"
|
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TweetTest.Core", "lib\TweetTest.Core\TweetTest.Core.fsproj", "{2D7DFBA6-057D-4111-B61B-E4CB1E2BF369}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TweetTest.Browser.CEF", "lib\TweetTest.Browser.CEF\TweetTest.Browser.CEF.fsproj", "{651B77C2-3745-4DAA-982C-398C2856E038}"
|
||||||
|
EndProject
|
||||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TweetTest.Utils", "lib\TweetTest.Utils\TweetTest.Utils.fsproj", "{07F6D350-B16F-44E2-804D-C1142E1E345F}"
|
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TweetTest.Utils", "lib\TweetTest.Utils\TweetTest.Utils.fsproj", "{07F6D350-B16F-44E2-804D-C1142E1E345F}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
@ -50,6 +54,10 @@ Global
|
|||||||
{EEFB1F37-7CAD-46BD-8042-66E7B502AB02}.Debug|x86.Build.0 = Debug|x86
|
{EEFB1F37-7CAD-46BD-8042-66E7B502AB02}.Debug|x86.Build.0 = Debug|x86
|
||||||
{EEFB1F37-7CAD-46BD-8042-66E7B502AB02}.Release|x86.ActiveCfg = Release|x86
|
{EEFB1F37-7CAD-46BD-8042-66E7B502AB02}.Release|x86.ActiveCfg = Release|x86
|
||||||
{EEFB1F37-7CAD-46BD-8042-66E7B502AB02}.Release|x86.Build.0 = Release|x86
|
{EEFB1F37-7CAD-46BD-8042-66E7B502AB02}.Release|x86.Build.0 = Release|x86
|
||||||
|
{1B7793C6-9002-483E-9BD7-897FE6CD18FB}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{1B7793C6-9002-483E-9BD7-897FE6CD18FB}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{1B7793C6-9002-483E-9BD7-897FE6CD18FB}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{1B7793C6-9002-483E-9BD7-897FE6CD18FB}.Release|x86.Build.0 = Release|x86
|
||||||
{476B1007-B12C-447F-B855-9886048201D6}.Debug|x86.ActiveCfg = Debug|x86
|
{476B1007-B12C-447F-B855-9886048201D6}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
{476B1007-B12C-447F-B855-9886048201D6}.Debug|x86.Build.0 = Debug|x86
|
{476B1007-B12C-447F-B855-9886048201D6}.Debug|x86.Build.0 = Debug|x86
|
||||||
{476B1007-B12C-447F-B855-9886048201D6}.Release|x86.ActiveCfg = Release|x86
|
{476B1007-B12C-447F-B855-9886048201D6}.Release|x86.ActiveCfg = Release|x86
|
||||||
@ -58,6 +66,10 @@ Global
|
|||||||
{2D7DFBA6-057D-4111-B61B-E4CB1E2BF369}.Debug|x86.Build.0 = Debug|x86
|
{2D7DFBA6-057D-4111-B61B-E4CB1E2BF369}.Debug|x86.Build.0 = Debug|x86
|
||||||
{2D7DFBA6-057D-4111-B61B-E4CB1E2BF369}.Release|x86.ActiveCfg = Release|x86
|
{2D7DFBA6-057D-4111-B61B-E4CB1E2BF369}.Release|x86.ActiveCfg = Release|x86
|
||||||
{2D7DFBA6-057D-4111-B61B-E4CB1E2BF369}.Release|x86.Build.0 = Release|x86
|
{2D7DFBA6-057D-4111-B61B-E4CB1E2BF369}.Release|x86.Build.0 = Release|x86
|
||||||
|
{651B77C2-3745-4DAA-982C-398C2856E038}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{651B77C2-3745-4DAA-982C-398C2856E038}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{651B77C2-3745-4DAA-982C-398C2856E038}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{651B77C2-3745-4DAA-982C-398C2856E038}.Release|x86.Build.0 = Release|x86
|
||||||
{07F6D350-B16F-44E2-804D-C1142E1E345F}.Debug|x86.ActiveCfg = Debug|x86
|
{07F6D350-B16F-44E2-804D-C1142E1E345F}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
{07F6D350-B16F-44E2-804D-C1142E1E345F}.Debug|x86.Build.0 = Debug|x86
|
{07F6D350-B16F-44E2-804D-C1142E1E345F}.Debug|x86.Build.0 = Debug|x86
|
||||||
{07F6D350-B16F-44E2-804D-C1142E1E345F}.Release|x86.ActiveCfg = Release|x86
|
{07F6D350-B16F-44E2-804D-C1142E1E345F}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
86
lib/TweetLib.Browser.CEF/Component/BrowserComponent.cs
Normal file
86
lib/TweetLib.Browser.CEF/Component/BrowserComponent.cs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
using System;
|
||||||
|
using TweetLib.Browser.Base;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
using TweetLib.Browser.Events;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
using TweetLib.Utils.Static;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Component {
|
||||||
|
public abstract class BrowserComponent<TFrame> : IBrowserComponent where TFrame : IDisposable {
|
||||||
|
public bool Ready { get; private set; }
|
||||||
|
|
||||||
|
public string Url => browser.Url;
|
||||||
|
public abstract string CacheFolder { get; }
|
||||||
|
|
||||||
|
public event EventHandler<BrowserLoadedEventArgs>? BrowserLoaded;
|
||||||
|
public event EventHandler<PageLoadEventArgs>? PageLoadStart;
|
||||||
|
public event EventHandler<PageLoadEventArgs>? PageLoadEnd;
|
||||||
|
|
||||||
|
private readonly IBrowserWrapper<TFrame> browser;
|
||||||
|
private readonly IFrameAdapter<TFrame> frameAdapter;
|
||||||
|
|
||||||
|
protected BrowserComponent(IBrowserWrapper<TFrame> browser, IFrameAdapter<TFrame> frameAdapter) {
|
||||||
|
this.browser = browser;
|
||||||
|
this.frameAdapter = frameAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void Setup(BrowserSetup setup);
|
||||||
|
public abstract void AttachBridgeObject(string name, object bridge);
|
||||||
|
public abstract void DownloadFile(string url, string path, Action? onSuccess, Action<Exception>? onError);
|
||||||
|
|
||||||
|
private sealed class BrowserLoadedEventArgsImpl : BrowserLoadedEventArgs {
|
||||||
|
private readonly IBrowserWrapper<TFrame> browser;
|
||||||
|
|
||||||
|
public BrowserLoadedEventArgsImpl(IBrowserWrapper<TFrame> browser) {
|
||||||
|
this.browser = browser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AddDictionaryWords(params string[] words) {
|
||||||
|
foreach (string word in words) {
|
||||||
|
browser.AddWordToDictionary(word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnLoadingStateChanged(bool isLoading) {
|
||||||
|
if (!isLoading && !Ready) {
|
||||||
|
Ready = true;
|
||||||
|
BrowserLoaded?.Invoke(this, new BrowserLoadedEventArgsImpl(browser));
|
||||||
|
BrowserLoaded = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnLoadError<T>(string failedUrl, T errorCode, IErrorCodeAdapter<T> errorCodeAdapter) {
|
||||||
|
if (errorCodeAdapter.IsAborted(errorCode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!failedUrl.StartsWithOrdinal("td://resources/error/")) {
|
||||||
|
using TFrame frame = browser.MainFrame;
|
||||||
|
|
||||||
|
if (frameAdapter.IsValid(frame)) {
|
||||||
|
string? errorName = errorCodeAdapter.GetName(errorCode);
|
||||||
|
string errorTitle = StringUtils.ConvertPascalCaseToScreamingSnakeCase(errorName ?? string.Empty);
|
||||||
|
frameAdapter.LoadUrl(frame, "td://resources/error/error.html#" + Uri.EscapeDataString(errorTitle));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnFrameLoadStart(string url, TFrame frame) {
|
||||||
|
if (frameAdapter.IsMain(frame)) {
|
||||||
|
PageLoadStart?.Invoke(this, new PageLoadEventArgs(url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnFrameLoadEnd(string url, TFrame frame) {
|
||||||
|
if (frameAdapter.IsMain(frame)) {
|
||||||
|
PageLoadEnd?.Invoke(this, new PageLoadEventArgs(url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RunScript(string identifier, string script) {
|
||||||
|
using TFrame frame = browser.MainFrame;
|
||||||
|
frameAdapter.ExecuteJavaScriptAsync(frame, script, identifier, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
lib/TweetLib.Browser.CEF/Data/ByteArrayResource.cs
Normal file
26
lib/TweetLib.Browser.CEF/Data/ByteArrayResource.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Data {
|
||||||
|
public sealed class ByteArrayResource {
|
||||||
|
private const string DefaultMimeType = "text/html";
|
||||||
|
private const HttpStatusCode DefaultStatusCode = HttpStatusCode.OK;
|
||||||
|
private const string DefaultStatusText = "OK";
|
||||||
|
|
||||||
|
internal byte[] Contents { get; }
|
||||||
|
internal int Length { get; }
|
||||||
|
internal string MimeType { get; }
|
||||||
|
internal HttpStatusCode StatusCode { get; }
|
||||||
|
internal string StatusText { get; }
|
||||||
|
|
||||||
|
public ByteArrayResource(byte[] contents, string mimeType = DefaultMimeType, HttpStatusCode statusCode = DefaultStatusCode, string statusText = DefaultStatusText) {
|
||||||
|
this.Contents = contents;
|
||||||
|
this.Length = contents.Length;
|
||||||
|
this.MimeType = mimeType;
|
||||||
|
this.StatusCode = statusCode;
|
||||||
|
this.StatusText = statusText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteArrayResource(string contents, Encoding encoding, string mimeType = DefaultMimeType, HttpStatusCode statusCode = DefaultStatusCode, string statusText = DefaultStatusText) : this(encoding.GetBytes(contents), mimeType, statusCode, statusText) {}
|
||||||
|
}
|
||||||
|
}
|
29
lib/TweetLib.Browser.CEF/Data/ContextMenuActionRegistry.cs
Normal file
29
lib/TweetLib.Browser.CEF/Data/ContextMenuActionRegistry.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Data {
|
||||||
|
public abstract class ContextMenuActionRegistry<T> {
|
||||||
|
private readonly Dictionary<T, Action> actions = new ();
|
||||||
|
|
||||||
|
protected abstract T NextId(int n);
|
||||||
|
|
||||||
|
public T AddAction(Action action) {
|
||||||
|
T id = NextId(actions.Count);
|
||||||
|
actions[id] = action;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Execute(T id) {
|
||||||
|
if (actions.TryGetValue(id, out var action)) {
|
||||||
|
action();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear() {
|
||||||
|
actions.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
lib/TweetLib.Browser.CEF/Data/ResourceHandlerRegistry.cs
Normal file
47
lib/TweetLib.Browser.CEF/Data/ResourceHandlerRegistry.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Text;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Data {
|
||||||
|
public sealed class ResourceHandlerRegistry<TResourceHandler> where TResourceHandler : class {
|
||||||
|
private readonly IResourceHandlerFactory<TResourceHandler> factory;
|
||||||
|
private readonly ConcurrentDictionary<string, Func<TResourceHandler>> resourceHandlers = new (StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
public ResourceHandlerRegistry(IResourceHandlerFactory<TResourceHandler> factory) {
|
||||||
|
this.factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool HasHandler(string url) {
|
||||||
|
return resourceHandlers.ContainsKey(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal TResourceHandler? GetHandler(string url) {
|
||||||
|
return resourceHandlers.TryGetValue(url, out var handler) ? handler() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Register(string url, Func<TResourceHandler> factory) {
|
||||||
|
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) {
|
||||||
|
throw new ArgumentException("Resource handler URL must be absolute!");
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceHandlers.AddOrUpdate(uri.AbsoluteUri, factory, (_, _) => factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterStatic(string url, byte[] staticData, string mimeType = "text/html") {
|
||||||
|
Register(url, () => factory.CreateResourceHandler(new ByteArrayResource(staticData, mimeType)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterStatic(string url, string staticData, string mimeType = "text/html") {
|
||||||
|
Register(url, () => factory.CreateResourceHandler(new ByteArrayResource(staticData, Encoding.UTF8, mimeType)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterDynamic(string url, TResourceHandler handler) {
|
||||||
|
Register(url, () => handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unregister(string url) {
|
||||||
|
resourceHandlers.TryRemove(url, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
lib/TweetLib.Browser.CEF/Interfaces/IBrowserWrapper.cs
Normal file
10
lib/TweetLib.Browser.CEF/Interfaces/IBrowserWrapper.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Interfaces {
|
||||||
|
public interface IBrowserWrapper<TFrame> where TFrame : IDisposable {
|
||||||
|
string Url { get; }
|
||||||
|
TFrame MainFrame { get; }
|
||||||
|
|
||||||
|
void AddWordToDictionary(string word);
|
||||||
|
}
|
||||||
|
}
|
9
lib/TweetLib.Browser.CEF/Interfaces/IDragDataAdapter.cs
Normal file
9
lib/TweetLib.Browser.CEF/Interfaces/IDragDataAdapter.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace TweetLib.Browser.CEF.Interfaces {
|
||||||
|
public interface IDragDataAdapter<T> {
|
||||||
|
bool IsLink(T data);
|
||||||
|
string GetLink(T data);
|
||||||
|
|
||||||
|
bool IsFragment(T data);
|
||||||
|
string GetFragmentAsText(T data);
|
||||||
|
}
|
||||||
|
}
|
6
lib/TweetLib.Browser.CEF/Interfaces/IErrorCodeAdapter.cs
Normal file
6
lib/TweetLib.Browser.CEF/Interfaces/IErrorCodeAdapter.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace TweetLib.Browser.CEF.Interfaces {
|
||||||
|
public interface IErrorCodeAdapter<T> {
|
||||||
|
bool IsAborted(T errorCode);
|
||||||
|
string? GetName(T errorCode);
|
||||||
|
}
|
||||||
|
}
|
8
lib/TweetLib.Browser.CEF/Interfaces/IFrameAdapter.cs
Normal file
8
lib/TweetLib.Browser.CEF/Interfaces/IFrameAdapter.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace TweetLib.Browser.CEF.Interfaces {
|
||||||
|
public interface IFrameAdapter<T> {
|
||||||
|
bool IsValid(T frame);
|
||||||
|
bool IsMain(T frame);
|
||||||
|
void LoadUrl(T frame, string url);
|
||||||
|
void ExecuteJavaScriptAsync(T frame, string script, string identifier, int startLine = 1);
|
||||||
|
}
|
||||||
|
}
|
6
lib/TweetLib.Browser.CEF/Interfaces/IPopupHandler.cs
Normal file
6
lib/TweetLib.Browser.CEF/Interfaces/IPopupHandler.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace TweetLib.Browser.CEF.Interfaces {
|
||||||
|
public interface IPopupHandler {
|
||||||
|
bool IsPopupAllowed(string url);
|
||||||
|
void OpenExternalBrowser(string url);
|
||||||
|
}
|
||||||
|
}
|
19
lib/TweetLib.Browser.CEF/Interfaces/IRequestAdapter.cs
Normal file
19
lib/TweetLib.Browser.CEF/Interfaces/IRequestAdapter.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using TweetLib.Browser.Request;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Interfaces {
|
||||||
|
public interface IRequestAdapter<T> {
|
||||||
|
ulong GetIdentifier(T request);
|
||||||
|
|
||||||
|
string GetUrl(T request);
|
||||||
|
|
||||||
|
void SetUrl(T request, string url);
|
||||||
|
|
||||||
|
bool IsTransitionForwardBack(T request);
|
||||||
|
|
||||||
|
bool IsCspReport(T request);
|
||||||
|
|
||||||
|
ResourceType GetResourceType(T request);
|
||||||
|
|
||||||
|
void SetHeader(T request, string header, string value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Interfaces {
|
||||||
|
public interface IResourceHandlerFactory<T> {
|
||||||
|
T CreateResourceHandler(ByteArrayResource resource);
|
||||||
|
string GetMimeTypeFromExtension(string extension);
|
||||||
|
}
|
||||||
|
}
|
9
lib/TweetLib.Browser.CEF/Interfaces/IResponseAdapter.cs
Normal file
9
lib/TweetLib.Browser.CEF/Interfaces/IResponseAdapter.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace TweetLib.Browser.CEF.Interfaces {
|
||||||
|
public interface IResponseAdapter<T> {
|
||||||
|
void SetCharset(T response, string charset);
|
||||||
|
void SetMimeType(T response, string mimeType);
|
||||||
|
void SetStatus(T response, int statusCode, string statusText);
|
||||||
|
void SetHeader(T response, string header, string value);
|
||||||
|
string? GetHeader(T response, string header);
|
||||||
|
}
|
||||||
|
}
|
5
lib/TweetLib.Browser.CEF/Lib.cs
Normal file
5
lib/TweetLib.Browser.CEF/Lib.cs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
[assembly: AssemblyTitle("TweetDuck Browser CEF Library")]
|
||||||
|
[assembly: AssemblyDescription("TweetDuck Browser CEF Library")]
|
||||||
|
[assembly: AssemblyProduct("TweetLib.Browser.CEF")]
|
@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Logic {
|
||||||
|
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
||||||
|
public sealed class ByteArrayResourceHandlerLogic<TResponse> {
|
||||||
|
public delegate void WriteToOut<T>(T dataOut, byte[] dataIn, int position, int length);
|
||||||
|
|
||||||
|
private readonly ByteArrayResource resource;
|
||||||
|
private readonly IResponseAdapter<TResponse> responseAdapter;
|
||||||
|
|
||||||
|
private int position;
|
||||||
|
|
||||||
|
public ByteArrayResourceHandlerLogic(ByteArrayResource resource, IResponseAdapter<TResponse> responseAdapter) {
|
||||||
|
this.resource = resource;
|
||||||
|
this.responseAdapter = responseAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Open(out bool handleRequest) {
|
||||||
|
position = 0;
|
||||||
|
handleRequest = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetResponseHeaders(TResponse response, out long responseLength, out string? redirectUrl) {
|
||||||
|
responseLength = resource.Length;
|
||||||
|
redirectUrl = null;
|
||||||
|
|
||||||
|
responseAdapter.SetMimeType(response, resource.MimeType);
|
||||||
|
responseAdapter.SetStatus(response, (int) resource.StatusCode, resource.StatusText);
|
||||||
|
responseAdapter.SetCharset(response, "utf-8");
|
||||||
|
responseAdapter.SetHeader(response, "Access-Control-Allow-Origin", "*");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Skip(long bytesToSkip, out long bytesSkipped, IDisposable callback) {
|
||||||
|
callback.Dispose();
|
||||||
|
|
||||||
|
position = (int) (position + bytesToSkip);
|
||||||
|
bytesSkipped = bytesToSkip;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Read<T>(WriteToOut<T> write, T dataOut, int bytesToRead, out int bytesRead, IDisposable callback) {
|
||||||
|
callback.Dispose();
|
||||||
|
|
||||||
|
if (bytesToRead > 0) {
|
||||||
|
write(dataOut, resource.Contents, position, bytesToRead);
|
||||||
|
position += bytesToRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesRead = bytesToRead;
|
||||||
|
return bytesRead > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Read<T>(WriteToOut<T> write, T dataOut, out int bytesRead, IDisposable callback) {
|
||||||
|
return Read(write, dataOut, resource.Length - position, out bytesRead, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
73
lib/TweetLib.Browser.CEF/Logic/DownloadRequestClientLogic.cs
Normal file
73
lib/TweetLib.Browser.CEF/Logic/DownloadRequestClientLogic.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Logic {
|
||||||
|
public sealed class DownloadRequestClientLogic {
|
||||||
|
public enum RequestStatus {
|
||||||
|
Unknown,
|
||||||
|
Success,
|
||||||
|
Failed
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly FileStream fileStream;
|
||||||
|
private readonly Action? onSuccess;
|
||||||
|
private readonly Action<Exception>? onError;
|
||||||
|
|
||||||
|
private bool hasFailed;
|
||||||
|
|
||||||
|
public DownloadRequestClientLogic(FileStream fileStream, Action? onSuccess, Action<Exception>? onError) {
|
||||||
|
this.fileStream = fileStream;
|
||||||
|
this.onSuccess = onSuccess;
|
||||||
|
this.onError = onError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetAuthCredentials(IDisposable callback) {
|
||||||
|
callback.Dispose();
|
||||||
|
|
||||||
|
hasFailed = true;
|
||||||
|
fileStream.Dispose();
|
||||||
|
onError?.Invoke(new Exception("This URL requires authentication."));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnDownloadData(Stream data) {
|
||||||
|
if (hasFailed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
data.CopyTo(fileStream);
|
||||||
|
} catch (Exception e) {
|
||||||
|
fileStream.Dispose();
|
||||||
|
onError?.Invoke(e);
|
||||||
|
hasFailed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "SwitchStatementMissingSomeEnumCasesNoDefault")]
|
||||||
|
public void OnRequestComplete(RequestStatus status) {
|
||||||
|
if (hasFailed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEmpty = fileStream.Position == 0;
|
||||||
|
fileStream.Dispose();
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case RequestStatus.Failed:
|
||||||
|
onError?.Invoke(new Exception("Unknown error."));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RequestStatus.Success when isEmpty:
|
||||||
|
onError?.Invoke(new Exception("File is empty."));
|
||||||
|
return;
|
||||||
|
|
||||||
|
case RequestStatus.Success:
|
||||||
|
onSuccess?.Invoke();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
lib/TweetLib.Browser.CEF/Logic/DragHandlerLogic.cs
Normal file
37
lib/TweetLib.Browser.CEF/Logic/DragHandlerLogic.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Logic {
|
||||||
|
public sealed class DragHandlerLogic<TDragData, TRequest> {
|
||||||
|
private readonly IScriptExecutor executor;
|
||||||
|
private readonly RequestHandlerLogic<TRequest> requestHandlerLogic;
|
||||||
|
private readonly IDragDataAdapter<TDragData> dragDataAdapter;
|
||||||
|
|
||||||
|
public DragHandlerLogic(IScriptExecutor executor, RequestHandlerLogic<TRequest> requestHandlerLogic, IDragDataAdapter<TDragData> dragDataAdapter) {
|
||||||
|
this.executor = executor;
|
||||||
|
this.requestHandlerLogic = requestHandlerLogic;
|
||||||
|
this.dragDataAdapter = dragDataAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TriggerDragStart(string type, string? data = null) {
|
||||||
|
executor.RunFunction("window.TDGF_onGlobalDragStart && window.TDGF_onGlobalDragStart", type, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnDragEnter(TDragData dragData) {
|
||||||
|
var link = dragDataAdapter.GetLink(dragData);
|
||||||
|
requestHandlerLogic.BlockNextUserNavUrl = link;
|
||||||
|
|
||||||
|
if (dragDataAdapter.IsLink(dragData)) {
|
||||||
|
TriggerDragStart("link", link);
|
||||||
|
}
|
||||||
|
else if (dragDataAdapter.IsFragment(dragData)) {
|
||||||
|
TriggerDragStart("text", dragDataAdapter.GetFragmentAsText(dragData).Trim());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
TriggerDragStart("unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
lib/TweetLib.Browser.CEF/Logic/LifeSpanHandlerLogic.cs
Normal file
37
lib/TweetLib.Browser.CEF/Logic/LifeSpanHandlerLogic.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Logic {
|
||||||
|
public sealed class LifeSpanHandlerLogic {
|
||||||
|
public enum TargetDisposition {
|
||||||
|
NewBackgroundTab,
|
||||||
|
NewForegroundTab,
|
||||||
|
NewPopup,
|
||||||
|
NewWindow,
|
||||||
|
Other
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly IPopupHandler popupHandler;
|
||||||
|
|
||||||
|
public LifeSpanHandlerLogic(IPopupHandler popupHandler) {
|
||||||
|
this.popupHandler = popupHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnBeforePopup(string targetUrl, TargetDisposition targetDisposition) {
|
||||||
|
switch (targetDisposition) {
|
||||||
|
case TargetDisposition.NewBackgroundTab:
|
||||||
|
case TargetDisposition.NewForegroundTab:
|
||||||
|
case TargetDisposition.NewPopup when !popupHandler.IsPopupAllowed(targetUrl):
|
||||||
|
case TargetDisposition.NewWindow:
|
||||||
|
popupHandler.OpenExternalBrowser(targetUrl);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DoClose() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
lib/TweetLib.Browser.CEF/Logic/RequestHandlerLogic.cs
Normal file
29
lib/TweetLib.Browser.CEF/Logic/RequestHandlerLogic.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Logic {
|
||||||
|
public sealed class RequestHandlerLogic<TRequest> {
|
||||||
|
internal string BlockNextUserNavUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
private readonly IRequestAdapter<TRequest> requestAdapter;
|
||||||
|
private readonly LifeSpanHandlerLogic lifeSpanHandlerLogic;
|
||||||
|
|
||||||
|
public RequestHandlerLogic(IRequestAdapter<TRequest> requestAdapter, LifeSpanHandlerLogic lifeSpanHandlerLogic) {
|
||||||
|
this.requestAdapter = requestAdapter;
|
||||||
|
this.lifeSpanHandlerLogic = lifeSpanHandlerLogic;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldBlockNav(string url) {
|
||||||
|
bool block = url == BlockNextUserNavUrl;
|
||||||
|
BlockNextUserNavUrl = string.Empty;
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnBeforeBrowse(TRequest request, bool userGesture) {
|
||||||
|
return requestAdapter.IsTransitionForwardBack(request) || (userGesture && ShouldBlockNav(requestAdapter.GetUrl(request)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnOpenUrlFromTab(string targetUrl, bool userGesture, LifeSpanHandlerLogic.TargetDisposition targetDisposition) {
|
||||||
|
return (userGesture && ShouldBlockNav(targetUrl)) || lifeSpanHandlerLogic.OnBeforePopup(targetUrl, targetDisposition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Logic {
|
||||||
|
public sealed class ResourceRequestHandlerFactoryLogic<TResourceRequestHandler, TResourceHandler, TRequest> where TResourceHandler : class {
|
||||||
|
private readonly IRequestAdapter<TRequest> requestAdapter;
|
||||||
|
private readonly TResourceRequestHandler handler;
|
||||||
|
private readonly ResourceHandlerRegistry<TResourceHandler> registry;
|
||||||
|
|
||||||
|
public ResourceRequestHandlerFactoryLogic(IRequestAdapter<TRequest> requestAdapter, TResourceRequestHandler handler, ResourceHandlerRegistry<TResourceHandler> registry) {
|
||||||
|
this.handler = handler;
|
||||||
|
this.registry = registry;
|
||||||
|
this.requestAdapter = requestAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "RedundantAssignment")]
|
||||||
|
public TResourceRequestHandler GetResourceRequestHandler(TRequest request, ref bool disableDefaultHandling) {
|
||||||
|
disableDefaultHandling = registry.HasHandler(requestAdapter.GetUrl(request));
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
using TweetLib.Browser.Request;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Logic {
|
||||||
|
public sealed class ResourceRequestHandlerLogic<TRequest, TResponse, TResourceHandler> where TResourceHandler : class {
|
||||||
|
private readonly IRequestAdapter<TRequest> requestAdapter;
|
||||||
|
private readonly IResponseAdapter<TResponse> responseAdapter;
|
||||||
|
private readonly ResourceHandlerRegistry<TResourceHandler> resourceHandlerRegistry;
|
||||||
|
private readonly IResourceRequestHandler? resourceRequestHandler;
|
||||||
|
|
||||||
|
private readonly Dictionary<ulong, IResponseProcessor> responseProcessors = new ();
|
||||||
|
|
||||||
|
public ResourceRequestHandlerLogic(IRequestAdapter<TRequest> requestAdapter, IResponseAdapter<TResponse> responseAdapter, ResourceHandlerRegistry<TResourceHandler> resourceHandlerRegistry, IResourceRequestHandler? resourceRequestHandler) {
|
||||||
|
this.requestAdapter = requestAdapter;
|
||||||
|
this.responseAdapter = responseAdapter;
|
||||||
|
this.resourceHandlerRegistry = resourceHandlerRegistry;
|
||||||
|
this.resourceRequestHandler = resourceRequestHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnBeforeResourceLoad(TRequest request, IDisposable callback) {
|
||||||
|
if (requestAdapter.IsCspReport(request)) {
|
||||||
|
callback.Dispose();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resourceRequestHandler != null) {
|
||||||
|
var result = resourceRequestHandler.Handle(requestAdapter.GetUrl(request), requestAdapter.GetResourceType(request));
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case RequestHandleResult.Redirect redirect:
|
||||||
|
requestAdapter.SetUrl(request, redirect.Url);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RequestHandleResult.Process process:
|
||||||
|
requestAdapter.SetHeader(request, "Accept-Encoding", "identity");
|
||||||
|
responseProcessors[requestAdapter.GetIdentifier(request)] = process.Processor;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RequestHandleResult.Cancel:
|
||||||
|
callback.Dispose();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TResourceHandler? GetResourceHandler(TRequest request) {
|
||||||
|
return resourceHandlerRegistry.GetHandler(requestAdapter.GetUrl(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseFilterLogic? GetResourceResponseFilter(TRequest request, TResponse response) {
|
||||||
|
if (responseProcessors.TryGetValue(requestAdapter.GetIdentifier(request), out var processor) && int.TryParse(responseAdapter.GetHeader(response, "Content-Length"), out int totalBytes)) {
|
||||||
|
return new ResponseFilterLogic(processor, totalBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnResourceLoadComplete(TRequest request) {
|
||||||
|
responseProcessors.Remove(requestAdapter.GetIdentifier(request));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using CefSharp;
|
|
||||||
using TweetLib.Browser.Interfaces;
|
using TweetLib.Browser.Interfaces;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
namespace TweetLib.Browser.CEF.Logic {
|
||||||
sealed class ResponseFilter : IResponseFilter {
|
public sealed class ResponseFilterLogic {
|
||||||
|
public enum FilterStatus {
|
||||||
|
NeedMoreData,
|
||||||
|
Done
|
||||||
|
}
|
||||||
|
|
||||||
private enum State {
|
private enum State {
|
||||||
Reading,
|
Reading,
|
||||||
Writing,
|
Writing,
|
||||||
@ -17,26 +21,21 @@ private enum State {
|
|||||||
private State state;
|
private State state;
|
||||||
private int offset;
|
private int offset;
|
||||||
|
|
||||||
public ResponseFilter(IResponseProcessor processor, int totalBytes) {
|
internal ResponseFilterLogic(IResponseProcessor processor, int totalBytes) {
|
||||||
this.processor = processor;
|
this.processor = processor;
|
||||||
this.responseData = new byte[totalBytes];
|
this.responseData = new byte[totalBytes];
|
||||||
this.state = State.Reading;
|
this.state = State.Reading;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool InitFilter() {
|
public FilterStatus Filter(Stream? dataIn, out long dataInRead, Stream dataOut, long dataOutLength, out long dataOutWritten) {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten) {
|
|
||||||
int responseLength = responseData.Length;
|
int responseLength = responseData.Length;
|
||||||
|
|
||||||
if (state == State.Reading) {
|
if (state == State.Reading) {
|
||||||
int bytesToRead = Math.Min(responseLength - offset, (int) Math.Min(dataIn?.Length ?? 0, int.MaxValue));
|
int bytesToRead = Math.Min(responseLength - offset, (int) Math.Min(dataIn?.Length ?? 0, int.MaxValue));
|
||||||
|
int bytesRead = dataIn?.Read(responseData, offset, bytesToRead) ?? 0;
|
||||||
|
|
||||||
dataIn?.Read(responseData, offset, bytesToRead);
|
offset += bytesRead;
|
||||||
offset += bytesToRead;
|
dataInRead = bytesRead;
|
||||||
|
|
||||||
dataInRead = bytesToRead;
|
|
||||||
dataOutWritten = 0;
|
dataOutWritten = 0;
|
||||||
|
|
||||||
if (offset >= responseLength) {
|
if (offset >= responseLength) {
|
||||||
@ -48,7 +47,7 @@ FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream d
|
|||||||
return FilterStatus.NeedMoreData;
|
return FilterStatus.NeedMoreData;
|
||||||
}
|
}
|
||||||
else if (state == State.Writing) {
|
else if (state == State.Writing) {
|
||||||
int bytesToWrite = Math.Min(responseLength - offset, (int) Math.Min(dataOut.Length, int.MaxValue));
|
int bytesToWrite = Math.Min(responseLength - offset, (int) Math.Min(dataOutLength, int.MaxValue));
|
||||||
|
|
||||||
if (bytesToWrite > 0) {
|
if (bytesToWrite > 0) {
|
||||||
dataOut.Write(responseData, offset, bytesToWrite);
|
dataOut.Write(responseData, offset, bytesToWrite);
|
||||||
@ -70,7 +69,5 @@ FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream d
|
|||||||
throw new InvalidOperationException("This resource filter cannot be reused.");
|
throw new InvalidOperationException("This resource filter cannot be reused.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
21
lib/TweetLib.Browser.CEF/Logic/SchemeHandlerFactoryLogic.cs
Normal file
21
lib/TweetLib.Browser.CEF/Logic/SchemeHandlerFactoryLogic.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Logic {
|
||||||
|
public sealed class SchemeHandlerFactoryLogic<TRequest, TResourceHandler> where TResourceHandler : class {
|
||||||
|
private readonly ICustomSchemeHandler handler;
|
||||||
|
private readonly IRequestAdapter<TRequest> requestAdapter;
|
||||||
|
private readonly ISchemeResourceVisitor<TResourceHandler> resourceVisitor;
|
||||||
|
|
||||||
|
public SchemeHandlerFactoryLogic(ICustomSchemeHandler handler, IRequestAdapter<TRequest> requestAdapter, IResourceHandlerFactory<TResourceHandler> resourceHandlerFactory) {
|
||||||
|
this.handler = handler;
|
||||||
|
this.requestAdapter = requestAdapter;
|
||||||
|
this.resourceVisitor = new SchemeResourceVisitor<TResourceHandler>(resourceHandlerFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TResourceHandler? Create(TRequest request) {
|
||||||
|
return Uri.TryCreate(requestAdapter.GetUrl(request), UriKind.Absolute, out var uri) ? handler.Resolve(uri)?.Visit(resourceVisitor) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
lib/TweetLib.Browser.CEF/Logic/SchemeResourceVisitor.cs
Normal file
29
lib/TweetLib.Browser.CEF/Logic/SchemeResourceVisitor.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
using TweetLib.Browser.Request;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Logic {
|
||||||
|
internal abstract class SchemeResourceVisitor {
|
||||||
|
protected static readonly SchemeResource.Status FileIsEmpty = new (HttpStatusCode.NoContent, "File is empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class SchemeResourceVisitor<TResourceHandler> : SchemeResourceVisitor, ISchemeResourceVisitor<TResourceHandler> {
|
||||||
|
private readonly IResourceHandlerFactory<TResourceHandler> factory;
|
||||||
|
|
||||||
|
public SchemeResourceVisitor(IResourceHandlerFactory<TResourceHandler> factory) {
|
||||||
|
this.factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TResourceHandler Status(SchemeResource.Status status) {
|
||||||
|
return factory.CreateResourceHandler(new ByteArrayResource(Array.Empty<byte>(), statusCode: status.Code, statusText: status.Message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TResourceHandler File(SchemeResource.File file) {
|
||||||
|
byte[] contents = file.Contents;
|
||||||
|
return contents.Length == 0 ? Status(FileIsEmpty) : factory.CreateResourceHandler(new ByteArrayResource(contents, factory.GetMimeTypeFromExtension(file.Extension)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
lib/TweetLib.Browser.CEF/TweetLib.Browser.CEF.csproj
Normal file
20
lib/TweetLib.Browser.CEF/TweetLib.Browser.CEF.csproj
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<Platforms>x86</Platforms>
|
||||||
|
<LangVersion>9</LangVersion>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\..\Version.cs" Link="Version.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\TweetLib.Browser\TweetLib.Browser.csproj" />
|
||||||
|
<ProjectReference Include="..\TweetLib.Utils\TweetLib.Utils.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -1,8 +1,13 @@
|
|||||||
|
using System.IO;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using TweetLib.Utils.Collections;
|
using TweetLib.Utils.Collections;
|
||||||
|
|
||||||
namespace TweetLib.Core.Features.Chromium {
|
namespace TweetLib.Browser.CEF.Utils {
|
||||||
public static class CefUtils {
|
public static class CefUtils {
|
||||||
|
public static string GetCacheFolder(string storagePath) {
|
||||||
|
return Path.Combine(storagePath, "Cache");
|
||||||
|
}
|
||||||
|
|
||||||
public static CommandLineArgs ParseCommandLineArguments(string argumentString) {
|
public static CommandLineArgs ParseCommandLineArguments(string argumentString) {
|
||||||
CommandLineArgs args = new CommandLineArgs();
|
CommandLineArgs args = new CommandLineArgs();
|
||||||
|
|
28
lib/TweetTest.Browser.CEF/TweetTest.Browser.CEF.fsproj
Normal file
28
lib/TweetTest.Browser.CEF/TweetTest.Browser.CEF.fsproj
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net472</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\TweetLib.Browser.CEF\TweetLib.Browser.CEF.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Utils\TestCefUtils.fs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -1,6 +1,6 @@
|
|||||||
namespace TweetTest.Core.Features.Chromium.CefUtils
|
namespace TweetTest.Browser.CEF.Utils.CefUtils
|
||||||
|
|
||||||
open TweetLib.Core.Features.Chromium
|
open TweetLib.Browser.CEF.Utils
|
||||||
open Xunit
|
open Xunit
|
||||||
|
|
||||||
|
|
@ -22,7 +22,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Features\Chromium\TestCefUtils.fs" />
|
|
||||||
<Compile Include="Features\Twitter\TestTwitterUrls.fs" />
|
<Compile Include="Features\Twitter\TestTwitterUrls.fs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user