1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-05-12 14:34:08 +02:00

Initial refactoring of ScriptLoader & making it accessible in TweetLib.Core

This commit is contained in:
chylex 2019-08-21 05:21:12 +02:00
parent a34a02e14d
commit d505b3305b
12 changed files with 96 additions and 68 deletions

View File

@ -1,15 +1,12 @@
using System.Windows.Forms;
using System.IO;
using CefSharp;
using TweetDuck.Resources;
using TweetLib.Core.Browser;
namespace TweetDuck.Core.Adapters{
sealed class CefScriptExecutor : IScriptExecutor{
private readonly Control sync;
private readonly IWebBrowser browser;
public CefScriptExecutor(Control sync, IWebBrowser browser){
this.sync = sync;
public CefScriptExecutor(IWebBrowser browser){
this.browser = browser;
}
@ -19,12 +16,26 @@ public void RunFunction(string name, params object[] args){
public void RunScript(string identifier, string script){
using IFrame frame = browser.GetMainFrame();
ScriptLoader.ExecuteScript(frame, script, identifier);
RunScript(frame, script, identifier);
}
public bool RunFile(string file){
using IFrame frame = browser.GetMainFrame();
return ScriptLoader.ExecuteFile(frame, file, sync);
return RunFile(frame, file);
}
// Helpers
public static void RunScript(IFrame frame, string script, string identifier){
if (script != null){
frame.ExecuteJavaScriptAsync(script, identifier, 1);
}
}
public static bool RunFile(IFrame frame, string file){
string script = Program.Resources.Load(file);
RunScript(frame, script, "root:" + Path.GetFileNameWithoutExtension(file));
return script != null;
}
}
}

View File

@ -6,12 +6,12 @@
using TweetDuck.Core.Utils;
using System.Linq;
using TweetDuck.Configuration;
using TweetDuck.Core.Adapters;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Management;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Other;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Resources;
using TweetLib.Core.Features.Twitter;
using TweetLib.Core.Utils;
@ -194,7 +194,7 @@ public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser,
}
protected static void DeselectAll(IFrame frame){
ScriptLoader.ExecuteScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect");
CefScriptExecutor.RunScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect");
}
protected static void OpenBrowser(Control control, string url){

View File

@ -2,7 +2,6 @@
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Controls;
using TweetDuck.Resources;
using TweetLib.Core.Features.Plugins;
namespace TweetDuck.Core.Notification.Example{
@ -32,7 +31,7 @@ protected override FormBorderStyle NotificationBorderStyle{
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){
browser.LoadingStateChanged += browser_LoadingStateChanged;
string exampleTweetHTML = ScriptLoader.LoadResourceSilent("pages/example.html")?.Replace("{avatar}", TweetNotification.AppLogo.Url) ?? string.Empty;
string exampleTweetHTML = Program.Resources.LoadSilent("pages/example.html")?.Replace("{avatar}", AppLogo.Url) ?? string.Empty;
#if DEBUG
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");

View File

@ -2,12 +2,12 @@
using System;
using System.Drawing;
using System.Windows.Forms;
using TweetDuck.Core.Adapters;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
using TweetDuck.Resources;
using TweetLib.Core.Data;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums;
@ -74,7 +74,7 @@ protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, b
browser.LoadingStateChanged += Browser_LoadingStateChanged;
browser.FrameLoadEnd += Browser_FrameLoadEnd;
plugins.Register(PluginEnvironment.Notification, new PluginDispatcher(this, browser));
plugins.Register(PluginEnvironment.Notification, new PluginDispatcher(browser));
mouseHookDelegate = MouseHookProc;
Disposed += (sender, args) => StopMouseHook(true);
@ -155,7 +155,7 @@ private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (frame.IsMain && browser.Address != "about:blank"){
frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
ScriptLoader.ExecuteFile(frame, "notification.js", this);
CefScriptExecutor.RunFile(frame, "notification.js");
}
}

View File

@ -3,10 +3,10 @@
using System.Drawing.Imaging;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Adapters;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils;
using TweetDuck.Resources;
using TweetLib.Core.Data;
using TweetLib.Core.Features.Plugins;
@ -29,16 +29,15 @@ public FormNotificationScreenshotable(Action callback, FormBrowser owner, Plugin
return;
}
string script = ScriptLoader.LoadResourceSilent("screenshot.js");
string script = Program.Resources.LoadSilent("screenshot.js");
if (script == null){
this.InvokeAsyncSafe(callback);
return;
}
using(IFrame frame = args.Browser.MainFrame){
ScriptLoader.ExecuteScript(frame, script.Replace("{width}", realWidth.ToString()).Replace("{frames}", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot");
}
using IFrame frame = args.Browser.MainFrame;
CefScriptExecutor.RunScript(frame, script.Replace("{width}", realWidth.ToString()).Replace("{frames}", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot");
};
SetNotificationSize(realWidth, 1024);

View File

@ -7,8 +7,8 @@
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Utils;
using System.Text.RegularExpressions;
using TweetDuck.Core.Adapters;
using TweetDuck.Data;
using TweetDuck.Resources;
namespace TweetDuck.Core.Other{
sealed partial class FormGuide : Form, FormManager.IAppDialog{
@ -116,7 +116,7 @@ private void browser_LoadingStateChanged(object sender, LoadingStateChangedEvent
}
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
ScriptLoader.ExecuteScript(e.Frame, "Array.prototype.forEach.call(document.getElementsByTagName('A'), ele => ele.addEventListener('click', e => { e.preventDefault(); window.open(ele.getAttribute('href')); }))", "gen:links");
CefScriptExecutor.RunScript(e.Frame, "Array.prototype.forEach.call(document.getElementsByTagName('A'), ele => ele.addEventListener('click', e => { e.preventDefault(); window.open(ele.getAttribute('href')); }))", "gen:links");
}
}
}

View File

@ -5,6 +5,7 @@
using CefSharp;
using CefSharp.WinForms;
using TweetDuck.Configuration;
using TweetDuck.Core.Adapters;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
@ -12,7 +13,6 @@
using TweetDuck.Core.Notification;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
using TweetDuck.Resources;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums;
@ -48,7 +48,7 @@ public bool IsTweetDeckWebsite{
private string prevSoundNotificationPath = null;
public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridge tdBridge, UpdateBridge updateBridge){
resourceHandlerFactory.RegisterHandler(TweetNotification.AppLogo);
resourceHandlerFactory.RegisterHandler(FormNotificationBase.AppLogo);
resourceHandlerFactory.RegisterHandler(TwitterUtils.LoadingSpinner);
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
@ -78,7 +78,7 @@ public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridg
this.browser.SetupZoomEvents();
owner.Controls.Add(browser);
plugins.Register(PluginEnvironment.Browser, new PluginDispatcher(owner, browser));
plugins.Register(PluginEnvironment.Browser, new PluginDispatcher(browser));
Config.MuteToggled += Config_MuteToggled;
Config.SoundNotificationChanged += Config_SoundNotificationInfoChanged;
@ -123,10 +123,10 @@ private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
if (frame.IsMain){
if (TwitterUtils.IsTwitterWebsite(frame)){
string css = ScriptLoader.LoadResource("styles/twitter.css", browser);
string css = Program.Resources.Load("styles/twitter.css");
resourceHandlerFactory.RegisterHandler(TwitterStyleUrl, ResourceHandler.FromString(css, mimeType: "text/css"));
ScriptLoader.ExecuteFile(frame, "twitter.js", browser);
CefScriptExecutor.RunFile(frame, "twitter.js");
}
if (!TwitterUtils.IsTwitterLogin2FactorWebsite(frame)){
@ -141,7 +141,7 @@ private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (frame.IsMain){
if (TwitterUtils.IsTweetDeckWebsite(frame)){
UpdateProperties();
ScriptLoader.ExecuteFile(frame, "code.js", browser);
CefScriptExecutor.RunFile(frame, "code.js");
InjectBrowserCSS();
ReinjectCustomCSS(Config.CustomBrowserCSS);
@ -150,15 +150,15 @@ private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
TweetDeckBridge.ResetStaticProperties();
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)){
ScriptLoader.ExecuteScript(frame, "TD.storage.Account.prototype.requiresConsent = function(){ return false; }", "gen:gdpr");
CefScriptExecutor.RunScript(frame, "TD.storage.Account.prototype.requiresConsent = function(){ return false; }", "gen:gdpr");
}
if (Config.FirstRun){
ScriptLoader.ExecuteFile(frame, "introduction.js", browser);
CefScriptExecutor.RunFile(frame, "introduction.js");
}
}
ScriptLoader.ExecuteFile(frame, "update.js", browser);
CefScriptExecutor.RunFile(frame, "update.js");
}
if (frame.Url == ErrorUrl){
@ -172,7 +172,7 @@ private void browser_LoadError(object sender, LoadErrorEventArgs e){
}
if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){
string errorPage = ScriptLoader.LoadResourceSilent("pages/error.html");
string errorPage = Program.Resources.LoadSilent("pages/error.html");
if (errorPage != null){
resourceHandlerFactory.RegisterHandler(ErrorUrl, ResourceHandler.FromString(errorPage.Replace("{err}", BrowserUtils.GetErrorName(e.ErrorCode))));
@ -226,7 +226,7 @@ public void UpdateProperties(){
}
public void InjectBrowserCSS(){
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css", browser)?.TrimEnd() ?? string.Empty);
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", Program.Resources.Load("styles/browser.css")?.TrimEnd() ?? string.Empty);
}
public void ReinjectCustomCSS(string css){

View File

@ -1,5 +1,4 @@
using System;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Adapters;
using TweetDuck.Core.Utils;
@ -14,10 +13,10 @@ sealed class PluginDispatcher : IPluginDispatcher{
private readonly IWebBrowser browser;
private readonly IScriptExecutor executor;
public PluginDispatcher(Control sync, IWebBrowser browser){
public PluginDispatcher(IWebBrowser browser){
this.browser = browser;
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
this.executor = new CefScriptExecutor(sync, browser);
this.executor = new CefScriptExecutor(browser);
}
void IPluginDispatcher.AttachBridge(string name, object bridge){

View File

@ -13,6 +13,7 @@
using TweetDuck.Core.Management;
using TweetDuck.Core.Utils;
using TweetDuck.Impl;
using TweetDuck.Resources;
using TweetLib.Core;
using TweetLib.Core.Collections;
using TweetLib.Core.Utils;
@ -51,16 +52,19 @@ static class Program{
public static Reporter Reporter { get; }
public static ConfigManager Config { get; }
public static ScriptLoader Resources { get; }
static Program(){
Reporter = new Reporter(ErrorLogFilePath);
Reporter.SetupUnhandledExceptionHandler("TweetDuck Has Failed :(");
Config = new ConfigManager();
Resources = new ScriptLoader();
Lib.Initialize(new App.Builder{
ErrorHandler = Reporter,
SystemHandler = new SystemHandler()
SystemHandler = new SystemHandler(),
ResourceHandler = Resources
});
}
@ -158,6 +162,7 @@ private static void Main(){
Application.ApplicationExit += (sender, args) => ExitCleanup();
FormBrowser mainForm = new FormBrowser();
Resources.Initialize(mainForm);
Application.Run(mainForm);
if (mainForm.UpdateInstallerPath != null){

View File

@ -1,11 +1,11 @@
using CefSharp;
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other;
using TweetLib.Core.Application;
#if DEBUG
using System.Diagnostics;
@ -15,14 +15,35 @@
#endif
namespace TweetDuck.Resources{
static class ScriptLoader{
sealed class ScriptLoader : IAppResourceHandler{
private static readonly Dictionary<string, string> CachedData = new Dictionary<string, string>(16);
public static string LoadResourceSilent(string name){
return LoadResource(name, null);
public static void ClearCache(){
CachedData.Clear();
}
public static string LoadResource(string name, Control sync){
private Control sync;
public void Initialize(Control sync){
this.sync = sync;
}
public string Load(string path){
return LoadInternal(path, silent: false);
}
public string LoadSilent(string path){
return LoadInternal(path, silent: true);
}
private string LoadInternal(string name, bool silent){
if (sync == null){
throw new InvalidOperationException("Cannot use ScriptLoader before initialization.");
}
else if (sync.IsDisposed){
return null; // better than crashing I guess...?
}
if (CachedData.TryGetValue(name, out string resourceData)){
return resourceData;
}
@ -32,7 +53,7 @@ public static string LoadResource(string name, Control sync){
#if DEBUG
if (Directory.Exists(HotSwapTargetDir)){
path = Path.Combine(HotSwapTargetDir, "scripts");
Debug.WriteLine("Hot swap active, redirecting "+name);
Debug.WriteLine($"Hot swap active, redirecting {name}");
}
#endif
@ -47,7 +68,7 @@ public static string LoadResource(string name, Control sync){
// #<version>\n
if (contents[0] != '#'){
ShowLoadError(sync, $"File {name} appears to be corrupted, please try reinstalling the app.");
ShowLoadError(silent ? null : sync, $"File {name} appears to be corrupted, please try reinstalling the app.");
separator = 0;
}
else{
@ -55,39 +76,25 @@ public static string LoadResource(string name, Control sync){
string fileVersion = contents.Substring(1, separator-1).TrimEnd();
if (fileVersion != Program.VersionTag){
ShowLoadError(sync, $"File {name} is made for a different version of TweetDuck ({fileVersion}) and may not function correctly in this version, please try reinstalling the app.");
ShowLoadError(silent ? null : sync, $"File {name} is made for a different version of TweetDuck ({fileVersion}) and may not function correctly in this version, please try reinstalling the app.");
}
}
resource = contents.Substring(separator).TrimStart();
}catch(Exception ex){
ShowLoadError(sync, $"Could not load {name}. The program will continue running with limited functionality.\n\n{ex.Message}");
ShowLoadError(silent ? null : sync, $"Could not load {name}. The program will continue running with limited functionality.\n\n{ex.Message}");
resource = null;
}
return CachedData[name] = resource;
}
public static bool ExecuteFile(IFrame frame, string file, Control sync){
string script = LoadResource(file, sync);
ExecuteScript(frame, script, "root:"+Path.GetFileNameWithoutExtension(file));
return script != null;
}
public static void ExecuteScript(IFrame frame, string script, string identifier){
if (script != null){
frame.ExecuteJavaScriptAsync(script, identifier, 1);
}
}
public static void ClearCache(){
CachedData.Clear();
}
private static void ShowLoadError(Control sync, string message){
sync?.InvokeSafe(() => FormMessage.Error("Resource Error", message, FormMessage.OK));
}
// TODO move hot swap to another implementation
#if DEBUG
private static readonly string HotSwapProjectRoot = FixPathSlash(Path.GetFullPath(Path.Combine(Program.ProgramPath, "../../../")));
private static readonly string HotSwapTargetDir = FixPathSlash(Path.Combine(HotSwapProjectRoot, "bin", "tmp"));
@ -104,7 +111,7 @@ static ScriptLoader(){
public static void HotSwap(){
if (!File.Exists(HotSwapRebuildScript)){
Debug.WriteLine("Failed resource hot swap, missing rebuild script: "+HotSwapRebuildScript);
Debug.WriteLine($"Failed resource hot swap, missing rebuild script: {HotSwapRebuildScript}");
return;
}
@ -124,13 +131,13 @@ public static void HotSwap(){
return;
}
else if (process.ExitCode != 0){
Debug.WriteLine("Failed resource hot swap, script exited with code "+process.ExitCode);
Debug.WriteLine($"Failed resource hot swap, script exited with code {process.ExitCode}");
return;
}
}
sw.Stop();
Debug.WriteLine("Finished rebuild script in "+sw.ElapsedMilliseconds+" ms");
Debug.WriteLine($"Finished rebuild script in {sw.ElapsedMilliseconds} ms");
ClearCache();
@ -159,7 +166,7 @@ private static void ResetHotSwap(){
}
private static string FixPathSlash(string path){
return path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)+'\\';
return path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + '\\';
}
#endif
}

View File

@ -5,18 +5,21 @@ namespace TweetLib.Core{
public sealed class App{
public static IAppErrorHandler ErrorHandler { get; private set; }
public static IAppSystemHandler SystemHandler { get; private set; }
public static IAppResourceHandler ResourceHandler { get; private set; }
// Builder
public sealed class Builder{
public IAppErrorHandler? ErrorHandler { get; set; }
public IAppSystemHandler? SystemHandler { get; set; }
public IAppResourceHandler? ResourceHandler { get; set; }
// Validation
internal void Initialize(){
App.ErrorHandler = Validate(ErrorHandler, nameof(ErrorHandler))!;
App.SystemHandler = Validate(SystemHandler, nameof(SystemHandler))!;
App.ResourceHandler = Validate(ResourceHandler, nameof(ResourceHandler))!;
}
private T Validate<T>(T obj, string name){

View File

@ -0,0 +1,5 @@
namespace TweetLib.Core.Application{
public interface IAppResourceHandler{
string? Load(string path);
}
}