mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-04-23 12:15:48 +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 CefSharp;
|
||||
using TweetLib.Browser.CEF.Data;
|
||||
using TweetLib.Browser.Contexts;
|
||||
using TweetLib.Browser.Interfaces;
|
||||
using TweetLib.Core.Features.TweetDeck;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
|
||||
namespace TweetDuck.Browser.Adapters {
|
||||
namespace TweetDuck.Browser.Base {
|
||||
sealed class CefContextMenuModel : IContextMenuBuilder {
|
||||
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.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.WinForms;
|
||||
using TweetLib.Browser.CEF.Logic;
|
||||
using TweetLib.Browser.Interfaces;
|
||||
|
||||
namespace TweetDuck.Browser.Adapters {
|
||||
namespace TweetDuck.Browser.Base {
|
||||
sealed class CefSchemeHandlerFactory : ISchemeHandlerFactory {
|
||||
public static void Register(CefSettings settings, ICustomSchemeHandler handler) {
|
||||
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) {
|
||||
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) {
|
||||
return Uri.TryCreate(request.Url, UriKind.Absolute, out var uri) ? handler.Resolve(uri)?.Visit(CefSchemeResourceVisitor.Instance) : null;
|
||||
IResourceHandler ISchemeHandlerFactory.Create(IBrowser browser, IFrame frame, string schemeName, IRequest request) {
|
||||
return logic.Create(request);
|
||||
}
|
||||
}
|
||||
}
|
@ -47,13 +47,10 @@ public bool IsWaiting {
|
||||
|
||||
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 TweetDeckBrowser browser;
|
||||
private readonly ITweetDeckInterface tweetDeckInterface;
|
||||
private readonly FormNotificationTweet notification;
|
||||
private readonly PluginManager plugins;
|
||||
private readonly UpdateChecker updates;
|
||||
private readonly ContextMenu contextMenu;
|
||||
|
@ -1,13 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using CefSharp;
|
||||
using TweetDuck.Browser.Adapters;
|
||||
using TweetDuck.Browser.Base;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Browser.CEF.Data;
|
||||
using TweetLib.Browser.Contexts;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
abstract class ContextMenuBase : IContextMenuHandler {
|
||||
class ContextMenuBase : IContextMenuHandler {
|
||||
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand) 26500;
|
||||
|
||||
private static readonly HashSet<CefMenuCommand> AllowedCefCommands = new HashSet<CefMenuCommand> {
|
||||
@ -31,11 +32,17 @@ abstract class ContextMenuBase : IContextMenuHandler {
|
||||
protected static UserConfig Config => Program.Config.User;
|
||||
|
||||
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.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) {
|
||||
@ -62,8 +69,13 @@ public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser bro
|
||||
}
|
||||
|
||||
AddSeparator(model);
|
||||
handler.Show(new CefContextMenuModel(model, actionRegistry), CreateContext(parameters));
|
||||
handler?.Show(new CefContextMenuModel(model, actionRegistry), CreateContext(parameters));
|
||||
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) {
|
||||
@ -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) {
|
||||
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator) { // do not add separators if there is nothing to separate
|
||||
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 CefSharp;
|
||||
using TweetDuck.Browser.Adapters;
|
||||
using TweetDuck.Browser.Base;
|
||||
using TweetDuck.Controls;
|
||||
using TweetLib.Browser.Contexts;
|
||||
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) {
|
||||
if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)) {
|
||||
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) {
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
AddDebugMenuItems(model);
|
||||
form.InvokeAsyncSafe(() => form.ContextMenuOpen = true);
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
using TweetDuck.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class JavaScriptDialogHandler : IJsDialogHandler {
|
||||
sealed class CustomJsDialogHandler : IJsDialogHandler {
|
||||
private static FormMessage CreateMessageForm(string caption, string text) {
|
||||
MessageBoxIcon icon = MessageBoxIcon.None;
|
||||
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.Text;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Browser.Adapters;
|
||||
using TweetDuck.Browser.Base;
|
||||
using TweetDuck.Browser.Handling;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Browser.CEF.Data;
|
||||
using TweetLib.Browser.Interfaces;
|
||||
using TweetLib.Core.Features.Notifications;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
@ -88,14 +90,11 @@ protected virtual FormBorderStyle NotificationBorderStyle {
|
||||
|
||||
private readonly FormBrowser owner;
|
||||
|
||||
protected readonly IBrowserComponent browserComponent;
|
||||
protected readonly ChromiumWebBrowser browser;
|
||||
protected readonly CefBrowserComponent browserComponent;
|
||||
private readonly NotificationBrowser browserImpl;
|
||||
|
||||
#pragma warning disable IDE0069 // Disposable fields should be disposed
|
||||
protected readonly ChromiumWebBrowser browser;
|
||||
#pragma warning restore IDE0069 // Disposable fields should be disposed
|
||||
|
||||
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
|
||||
private readonly CefByteArrayResourceHandler resourceHandler = new CefByteArrayResourceHandler();
|
||||
|
||||
private DesktopNotification currentNotification;
|
||||
private int pauseCounter;
|
||||
@ -115,11 +114,10 @@ protected FormNotificationBase(FormBrowser owner, CreateBrowserImplFunc createBr
|
||||
this.owner = owner;
|
||||
this.owner.FormClosed += owner_FormClosed;
|
||||
|
||||
this.browser = new ChromiumWebBrowser(NotificationBrowser.BlankURL) {
|
||||
RequestHandler = new RequestHandlerBase(false)
|
||||
};
|
||||
|
||||
this.browserComponent = new ComponentImpl(browser, this);
|
||||
this.browser = new ChromiumWebBrowser(NotificationBrowser.BlankURL);
|
||||
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.browserImpl = createBrowserImpl(this, browserComponent);
|
||||
|
||||
this.browser.Dock = DockStyle.None;
|
||||
@ -139,29 +137,9 @@ protected FormNotificationBase(FormBrowser owner, CreateBrowserImplFunc createBr
|
||||
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) {
|
||||
if (disposing) {
|
||||
components?.Dispose();
|
||||
resourceHandler.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
@ -207,7 +185,7 @@ public virtual void ResumeNotification() {
|
||||
|
||||
protected virtual void LoadTweet(DesktopNotification tweet) {
|
||||
currentNotification = tweet;
|
||||
resourceHandler.SetHTML(browserImpl.GetTweetHTML(tweet));
|
||||
resourceHandler.SetResource(new ByteArrayResource(browserImpl.GetTweetHTML(tweet), Encoding.UTF8));
|
||||
|
||||
browser.Load(TwitterUrls.TweetDeck);
|
||||
DisplayTooltip(null);
|
||||
|
@ -1,20 +1,21 @@
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Browser.Adapters;
|
||||
using CefSharp;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Dialogs;
|
||||
using TweetDuck.Dialogs.Settings;
|
||||
using TweetDuck.Management;
|
||||
using TweetLib.Browser.CEF.Data;
|
||||
using TweetLib.Core.Features.TweetDeck;
|
||||
|
||||
namespace TweetDuck.Browser.Notification {
|
||||
sealed class SoundNotification : ISoundNotificationHandler {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
using System.Drawing;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Browser.Adapters;
|
||||
using TweetDuck.Browser.Base;
|
||||
using TweetDuck.Browser.Handling;
|
||||
using TweetDuck.Browser.Notification;
|
||||
using TweetDuck.Configuration;
|
||||
@ -11,8 +11,6 @@
|
||||
using TweetLib.Core.Features.TweetDeck;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
using TweetLib.Core.Systems.Updates;
|
||||
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
||||
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
|
||||
using TweetDeckBrowserImpl = TweetLib.Core.Features.TweetDeck.TweetDeckBrowser;
|
||||
|
||||
namespace TweetDuck.Browser {
|
||||
@ -42,24 +40,18 @@ public bool IsTweetDeckWebsite {
|
||||
private readonly ChromiumWebBrowser browser;
|
||||
|
||||
public TweetDeckBrowser(FormBrowser owner, PluginManager pluginManager, ITweetDeckInterface tweetDeckInterface, UpdateChecker updateChecker) {
|
||||
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
|
||||
|
||||
this.browser = new ChromiumWebBrowser(TwitterUrls.TweetDeck) {
|
||||
DialogHandler = new FileDialogHandler(),
|
||||
DragHandler = new DragHandlerBrowser(requestHandler),
|
||||
KeyboardHandler = new CustomKeyboardHandler(owner),
|
||||
RequestHandler = requestHandler
|
||||
KeyboardHandler = new CustomKeyboardHandler(owner)
|
||||
};
|
||||
|
||||
// ReSharper disable once PossiblyImpureMethodCallOnReadonlyVariable
|
||||
this.browser.BrowserSettings.BackgroundColor = (uint) TweetDeckBrowserImpl.BackgroundColor.ToArgb();
|
||||
|
||||
var extraContext = new TweetDeckExtraContext();
|
||||
var resourceHandlerRegistry = new CefResourceHandlerRegistry();
|
||||
var soundNotificationHandler = new SoundNotification(resourceHandlerRegistry);
|
||||
|
||||
this.browserComponent = new ComponentImpl(browser, owner, extraContext, resourceHandlerRegistry);
|
||||
this.browserImpl = new TweetDeckBrowserImpl(browserComponent, tweetDeckInterface, extraContext, soundNotificationHandler, pluginManager, updateChecker);
|
||||
this.browserComponent = new CefBrowserComponent(browser, handler => new ContextMenuBrowser(owner, handler, extraContext));
|
||||
this.browserImpl = new TweetDeckBrowserImpl(browserComponent, tweetDeckInterface, extraContext, new SoundNotification(browserComponent.ResourceHandlerRegistry), pluginManager, updateChecker);
|
||||
|
||||
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)) {
|
||||
browserComponent.PageLoadEnd += (sender, args) => {
|
||||
@ -72,26 +64,6 @@ public TweetDeckBrowser(FormBrowser owner, PluginManager pluginManager, ITweetDe
|
||||
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) {
|
||||
if (!Ready) {
|
||||
browser.Size = size;
|
||||
|
@ -2,12 +2,11 @@
|
||||
using System.Windows.Forms;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Browser;
|
||||
using TweetDuck.Browser.Adapters;
|
||||
using TweetDuck.Browser.Base;
|
||||
using TweetDuck.Browser.Handling;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Management;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Browser.Interfaces;
|
||||
using TweetLib.Core.Features;
|
||||
|
||||
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;
|
||||
#pragma warning restore IDE0069 // Disposable fields should be disposed
|
||||
|
||||
private FormGuide(string url, Form owner) {
|
||||
InitializeComponent();
|
||||
@ -43,13 +40,12 @@ private FormGuide(string url, Form owner) {
|
||||
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
|
||||
|
||||
browser = new ChromiumWebBrowser(url) {
|
||||
KeyboardHandler = new CustomKeyboardHandler(null),
|
||||
RequestHandler = new RequestHandlerBase(true)
|
||||
KeyboardHandler = new CustomKeyboardHandler(null)
|
||||
};
|
||||
|
||||
browser.BrowserSettings.BackgroundColor = (uint) BackColor.ToArgb();
|
||||
|
||||
var browserComponent = new ComponentImpl(browser);
|
||||
var browserComponent = new CefBrowserComponent(browser);
|
||||
var browserImpl = new BaseBrowser(browserComponent);
|
||||
|
||||
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) {
|
||||
if (disposing) {
|
||||
components?.Dispose();
|
||||
|
@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Controls;
|
||||
using TweetLib.Browser.CEF.Utils;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Core.Features.Chromium;
|
||||
|
||||
namespace TweetDuck.Dialogs.Settings {
|
||||
sealed partial class DialogSettingsCefArgs : Form {
|
||||
|
@ -3,11 +3,12 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TweetLib.Browser.CEF.Utils;
|
||||
using TweetLib.Core;
|
||||
|
||||
namespace TweetDuck.Management {
|
||||
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 Timer autoClearTimer;
|
||||
|
@ -5,16 +5,16 @@
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Application;
|
||||
using TweetDuck.Browser;
|
||||
using TweetDuck.Browser.Adapters;
|
||||
using TweetDuck.Browser.Base;
|
||||
using TweetDuck.Browser.Handling;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Dialogs;
|
||||
using TweetDuck.Management;
|
||||
using TweetDuck.Updates;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Browser.CEF.Utils;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Core.Application;
|
||||
using TweetLib.Core.Features.Chromium;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.Plugins.Config;
|
||||
using TweetLib.Core.Features.TweetDeck;
|
||||
|
@ -66,6 +66,10 @@
|
||||
<Project>{eefb1f37-7cad-46bd-8042-66e7b502ab02}</Project>
|
||||
<Name>TweetLib.Browser</Name>
|
||||
</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">
|
||||
<Project>{93ba3cb4-a812-4949-b07d-8d393fb38937}</Project>
|
||||
<Name>TweetLib.Core</Name>
|
||||
@ -91,29 +95,32 @@
|
||||
<Compile Include="Application\FileDialogs.cs" />
|
||||
<Compile Include="Application\MessageDialogs.cs" />
|
||||
<Compile Include="Application\SystemHandler.cs" />
|
||||
<Compile Include="Browser\Adapters\CefBrowserComponent.cs" />
|
||||
<Compile Include="Browser\Adapters\CefContextMenuActionRegistry.cs" />
|
||||
<Compile Include="Browser\Adapters\CefContextMenuModel.cs" />
|
||||
<Compile Include="Browser\Adapters\CefResourceHandlerFactory.cs" />
|
||||
<Compile Include="Browser\Adapters\CefResourceHandlerRegistry.cs" />
|
||||
<Compile Include="Browser\Adapters\CefResourceRequestHandler.cs" />
|
||||
<Compile Include="Browser\Adapters\CefSchemeHandlerFactory.cs" />
|
||||
<Compile Include="Browser\Adapters\CefSchemeResourceVisitor.cs" />
|
||||
<Compile Include="Browser\Base\CefBrowserAdapter.cs" />
|
||||
<Compile Include="Browser\Base\CefBrowserComponent.cs" />
|
||||
<Compile Include="Browser\Base\CefContextMenuModel.cs" />
|
||||
<Compile Include="Browser\Base\CefDownloadRequestClient.cs" />
|
||||
<Compile Include="Browser\Base\CefDragDataAdapter.cs" />
|
||||
<Compile Include="Browser\Base\CefDragHandler.cs" />
|
||||
<Compile Include="Browser\Base\CefErrorCodeAdapter.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\ContextMenuBase.cs" />
|
||||
<Compile Include="Browser\Handling\ContextMenuBrowser.cs" />
|
||||
<Compile Include="Browser\Handling\ContextMenuGuide.cs" />
|
||||
<Compile Include="Browser\Handling\ContextMenuNotification.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\JavaScriptDialogHandler.cs" />
|
||||
<Compile Include="Browser\Handling\RequestHandlerBase.cs" />
|
||||
<Compile Include="Browser\Handling\RequestHandlerBrowser.cs" />
|
||||
<Compile Include="Browser\Handling\ResourceHandlerNotification.cs" />
|
||||
<Compile Include="Browser\Handling\ResponseFilter.cs" />
|
||||
<Compile Include="Browser\Handling\CustomJsDialogHandler.cs" />
|
||||
<Compile Include="Browser\Handling\PopupHandler.cs" />
|
||||
<Compile Include="Browser\Notification\FormNotificationExample.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
|
@ -14,10 +14,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Core", "lib\TweetL
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Browser", "lib\TweetLib.Browser\TweetLib.Browser.csproj", "{EEFB1F37-7CAD-46BD-8042-66E7B502AB02}"
|
||||
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}"
|
||||
EndProject
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TweetTest.Core", "lib\TweetTest.Core\TweetTest.Core.fsproj", "{2D7DFBA6-057D-4111-B61B-E4CB1E2BF369}"
|
||||
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}"
|
||||
EndProject
|
||||
Global
|
||||
@ -50,6 +54,10 @@ Global
|
||||
{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.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.Build.0 = Debug|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}.Release|x86.ActiveCfg = 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.Build.0 = Debug|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.IO;
|
||||
using CefSharp;
|
||||
using TweetLib.Browser.Interfaces;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class ResponseFilter : IResponseFilter {
|
||||
namespace TweetLib.Browser.CEF.Logic {
|
||||
public sealed class ResponseFilterLogic {
|
||||
public enum FilterStatus {
|
||||
NeedMoreData,
|
||||
Done
|
||||
}
|
||||
|
||||
private enum State {
|
||||
Reading,
|
||||
Writing,
|
||||
@ -17,26 +21,21 @@ private enum State {
|
||||
private State state;
|
||||
private int offset;
|
||||
|
||||
public ResponseFilter(IResponseProcessor processor, int totalBytes) {
|
||||
internal ResponseFilterLogic(IResponseProcessor processor, int totalBytes) {
|
||||
this.processor = processor;
|
||||
this.responseData = new byte[totalBytes];
|
||||
this.state = State.Reading;
|
||||
}
|
||||
|
||||
public bool InitFilter() {
|
||||
return true;
|
||||
}
|
||||
|
||||
FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten) {
|
||||
public FilterStatus Filter(Stream? dataIn, out long dataInRead, Stream dataOut, long dataOutLength, out long dataOutWritten) {
|
||||
int responseLength = responseData.Length;
|
||||
|
||||
if (state == State.Reading) {
|
||||
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 += bytesToRead;
|
||||
|
||||
dataInRead = bytesToRead;
|
||||
offset += bytesRead;
|
||||
dataInRead = bytesRead;
|
||||
dataOutWritten = 0;
|
||||
|
||||
if (offset >= responseLength) {
|
||||
@ -48,7 +47,7 @@ FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream d
|
||||
return FilterStatus.NeedMoreData;
|
||||
}
|
||||
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) {
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
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 TweetLib.Utils.Collections;
|
||||
|
||||
namespace TweetLib.Core.Features.Chromium {
|
||||
namespace TweetLib.Browser.CEF.Utils {
|
||||
public static class CefUtils {
|
||||
public static string GetCacheFolder(string storagePath) {
|
||||
return Path.Combine(storagePath, "Cache");
|
||||
}
|
||||
|
||||
public static CommandLineArgs ParseCommandLineArguments(string argumentString) {
|
||||
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
|
||||
|
||||
|
@ -22,7 +22,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Features\Chromium\TestCefUtils.fs" />
|
||||
<Compile Include="Features\Twitter\TestTwitterUrls.fs" />
|
||||
</ItemGroup>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user