diff --git a/Core/FormBrowser.cs b/Core/FormBrowser.cs index 02ef99d5..0a2f5977 100644 --- a/Core/FormBrowser.cs +++ b/Core/FormBrowser.cs @@ -78,7 +78,7 @@ public FormBrowser(){ this.browser = new TweetDeckBrowser(this, new TweetDeckBridge.Browser(this, notification)); this.contextMenu = ContextMenuBrowser.CreateMenu(this); - this.plugins.Register(browser, PluginEnvironment.Browser, true); + this.plugins.Register(browser, PluginEnvironment.Browser, this, true); Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update diff --git a/Core/Notification/Example/FormNotificationExample.cs b/Core/Notification/Example/FormNotificationExample.cs index b0938189..a85fdb7d 100644 --- a/Core/Notification/Example/FormNotificationExample.cs +++ b/Core/Notification/Example/FormNotificationExample.cs @@ -30,7 +30,7 @@ protected override FormBorderStyle NotificationBorderStyle{ public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){ browser.LoadingStateChanged += browser_LoadingStateChanged; - string exampleTweetHTML = ScriptLoader.LoadResource("pages/example.html", true)?.Replace("{avatar}", TweetNotification.AppLogo.Url) ?? string.Empty; + string exampleTweetHTML = ScriptLoader.LoadResourceSilent("pages/example.html")?.Replace("{avatar}", TweetNotification.AppLogo.Url) ?? string.Empty; #if DEBUG exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>"); diff --git a/Core/Notification/FormNotificationBase.cs b/Core/Notification/FormNotificationBase.cs index 54c6e3f0..0d2aee67 100644 --- a/Core/Notification/FormNotificationBase.cs +++ b/Core/Notification/FormNotificationBase.cs @@ -185,7 +185,7 @@ public virtual void ResumeNotification(){ } protected virtual string GetTweetHTML(TweetNotification tweet){ - return tweet.GenerateHtml(IsCursorOverBrowser ? "td-notification td-hover" : "td-notification"); + return tweet.GenerateHtml(IsCursorOverBrowser ? "td-notification td-hover" : "td-notification", this); } protected virtual void LoadTweet(TweetNotification tweet){ diff --git a/Core/Notification/FormNotificationMain.cs b/Core/Notification/FormNotificationMain.cs index f75b0a1d..796ec5c9 100644 --- a/Core/Notification/FormNotificationMain.cs +++ b/Core/Notification/FormNotificationMain.cs @@ -15,11 +15,6 @@ namespace TweetDuck.Core.Notification{ abstract partial class FormNotificationMain : FormNotificationBase, ITweetDeckBrowser{ - private const string NotificationScriptFile = "notification.js"; - - private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile); - private static readonly string NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile); - private readonly PluginManager plugins; private readonly int timerBarHeight; @@ -88,7 +83,7 @@ protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, b browser.LoadingStateChanged += Browser_LoadingStateChanged; browser.FrameLoadEnd += Browser_FrameLoadEnd; - plugins.Register(this, PluginEnvironment.Notification); + plugins.Register(this, PluginEnvironment.Notification, this); mouseHookDelegate = MouseHookProc; Disposed += (sender, args) => StopMouseHook(true); @@ -106,7 +101,7 @@ void ITweetDeckBrowser.OnFrameLoaded(Action<IFrame> callback){ browser.FrameLoadEnd += (sender, args) => { IFrame frame = args.Frame; - if (frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){ + if (frame.IsMain && browser.Address != "about:blank"){ callback(frame); } }; @@ -194,9 +189,9 @@ private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEvent } private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ - if (e.Frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){ + if (e.Frame.IsMain && browser.Address != "about:blank"){ e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification)); - ScriptLoader.ExecuteScript(e.Frame, NotificationJS, NotificationScriptIdentifier); + ScriptLoader.ExecuteScript(e.Frame, ScriptLoader.LoadResource("notification.js", this), "root:notification"); } } diff --git a/Core/Notification/Screenshot/FormNotificationScreenshotable.cs b/Core/Notification/Screenshot/FormNotificationScreenshotable.cs index 7fffd165..7ea6812a 100644 --- a/Core/Notification/Screenshot/FormNotificationScreenshotable.cs +++ b/Core/Notification/Screenshot/FormNotificationScreenshotable.cs @@ -27,7 +27,7 @@ public FormNotificationScreenshotable(Action callback, FormBrowser owner, Plugin return; } - string script = ScriptLoader.LoadResource("screenshot.js", true); + string script = ScriptLoader.LoadResourceSilent("screenshot.js"); if (script == null){ this.InvokeAsyncSafe(callback); @@ -44,7 +44,7 @@ public FormNotificationScreenshotable(Action callback, FormBrowser owner, Plugin } protected override string GetTweetHTML(TweetNotification tweet){ - string html = tweet.GenerateHtml("td-screenshot"); + string html = tweet.GenerateHtml("td-screenshot", this); foreach(InjectedHTML injection in plugins.NotificationInjections){ html = injection.InjectInto(html); diff --git a/Core/Notification/TweetNotification.cs b/Core/Notification/TweetNotification.cs index bcae8b72..782d5b1d 100644 --- a/Core/Notification/TweetNotification.cs +++ b/Core/Notification/TweetNotification.cs @@ -1,5 +1,6 @@ using System; using System.Text; +using System.Windows.Forms; using CefSharp; using TweetDuck.Core.Bridge; using TweetDuck.Data; @@ -8,7 +9,6 @@ namespace TweetDuck.Core.Notification{ sealed class TweetNotification{ private const string DefaultHeadLayout = @"<html class=""scroll-v os-windows dark txt-size--14"" lang=""en-US"" id=""tduck"" data-td-font=""medium"" data-td-theme=""dark""><head><meta charset=""utf-8""><link href=""https://ton.twimg.com/tweetdeck-web/web/dist/bundle.4b1f87e09d.css"" rel=""stylesheet""><style type='text/css'>body { background: rgb(34, 36, 38) !important }</style>"; - private static readonly string CustomCSS = ScriptLoader.LoadResource("styles/notification.css") ?? string.Empty; public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandler.FromByteArray(Properties.Resources.avatar, "image/png")); public static TweetNotification Example(string html, int characters){ @@ -53,11 +53,11 @@ public int GetDisplayDuration(int value){ return 2000+Math.Max(1000, value*characters); } - public string GenerateHtml(string bodyClasses){ + public string GenerateHtml(string bodyClasses, Control sync){ StringBuilder build = new StringBuilder(); build.Append("<!DOCTYPE html>"); build.Append(TweetDeckBridge.NotificationHeadLayout ?? DefaultHeadLayout); - build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>"); + build.Append("<style type='text/css'>").Append(ScriptLoader.LoadResource("styles/notification.css", sync) ?? string.Empty).Append("</style>"); if (!string.IsNullOrEmpty(Program.UserConfig.CustomNotificationCSS)){ build.Append("<style type='text/css'>").Append(Program.UserConfig.CustomNotificationCSS).Append("</style>"); diff --git a/Core/TweetDeckBrowser.cs b/Core/TweetDeckBrowser.cs index 7392a804..d9a3288d 100644 --- a/Core/TweetDeckBrowser.cs +++ b/Core/TweetDeckBrowser.cs @@ -9,6 +9,7 @@ using TweetDuck.Core.Controls; using TweetDuck.Core.Handling; using TweetDuck.Core.Handling.General; +using TweetDuck.Core.Management; using TweetDuck.Core.Notification; using TweetDuck.Core.Other.Interfaces; using TweetDuck.Core.Utils; @@ -41,7 +42,7 @@ public bool IsTweetDeckWebsite{ public TweetDeckBrowser(FormBrowser owner, TweetDeckBridge bridge){ RequestHandlerBrowser requestHandler = new RequestHandlerBrowser(); - + this.browser = new ChromiumWebBrowser(TwitterUtils.TweetDeckURL){ DialogHandler = new FileDialogHandler(), DragHandler = new DragHandlerBrowser(requestHandler), @@ -168,7 +169,7 @@ 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"); + ScriptLoader.ExecuteScript(frame, "TD.storage.Account.prototype.requiresConsent = function(){ return false; }", "gen:gdpr"); } if (Program.UserConfig.FirstRun){ @@ -183,7 +184,7 @@ private void browser_LoadError(object sender, LoadErrorEventArgs e){ } if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){ - string errorPage = ScriptLoader.LoadResource("pages/error.html", true); + string errorPage = ScriptLoader.LoadResourceSilent("pages/error.html"); if (errorPage != null){ browser.LoadHtml(errorPage.Replace("{err}", BrowserUtils.GetErrorName(e.ErrorCode)), "http://td/error"); @@ -232,7 +233,7 @@ public void UpdateProperties(){ } public void InjectBrowserCSS(){ - browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css", false, browser)?.TrimEnd() ?? string.Empty); + browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css", browser)?.TrimEnd() ?? string.Empty); } public void ReinjectCustomCSS(string css){ diff --git a/Plugins/Enums/PluginEnvironment.cs b/Plugins/Enums/PluginEnvironment.cs index 60a28ffa..4b46b1ca 100644 --- a/Plugins/Enums/PluginEnvironment.cs +++ b/Plugins/Enums/PluginEnvironment.cs @@ -24,14 +24,6 @@ public static bool IncludesDisabledPlugins(this PluginEnvironment environment){ return environment == PluginEnvironment.Browser; } - public static string GetScriptIdentifier(this PluginEnvironment environment){ - switch(environment){ - case PluginEnvironment.Browser: return "root:plugins:browser"; - case PluginEnvironment.Notification: return "root:plugins:notification"; - default: return null; - } - } - public static string GetPluginScriptFile(this PluginEnvironment environment){ switch(environment){ case PluginEnvironment.Browser: return "browser.js"; diff --git a/Plugins/PluginManager.cs b/Plugins/PluginManager.cs index bfcd903a..843f1303 100644 --- a/Plugins/PluginManager.cs +++ b/Plugins/PluginManager.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Windows.Forms; using TweetDuck.Core.Other.Interfaces; using TweetDuck.Data; using TweetDuck.Plugins.Enums; @@ -12,15 +13,7 @@ namespace TweetDuck.Plugins{ sealed class PluginManager{ - private static IReadOnlyDictionary<PluginEnvironment, string> LoadSetupScripts(){ - return PluginEnvironmentExtensions.Map( - null, - ScriptLoader.LoadResource("plugins.browser.js"), - ScriptLoader.LoadResource("plugins.notification.js") - ); - } - - private static readonly IReadOnlyDictionary<PluginEnvironment, string> PluginSetupScripts = LoadSetupScripts(); + private static readonly IReadOnlyDictionary<PluginEnvironment, string> PluginSetupScriptNames = PluginEnvironmentExtensions.Map(null, "plugins.browser.js", "plugins.notification.js"); public string PathOfficialPlugins => Path.Combine(rootPath, "official"); public string PathCustomPlugins => Path.Combine(rootPath, "user"); @@ -54,8 +47,8 @@ public PluginManager(string rootPath, string configPath){ Config.PluginChangedState += Config_PluginChangedState; } - public void Register(ITweetDeckBrowser browser, PluginEnvironment environment, bool asMainBrowser = false){ - browser.OnFrameLoaded(frame => ExecutePlugins(frame, environment)); + public void Register(ITweetDeckBrowser browser, PluginEnvironment environment, Control sync, bool asMainBrowser = false){ + browser.OnFrameLoaded(frame => ExecutePlugins(frame, environment, sync)); browser.RegisterBridge("$TDP", bridge); if (asMainBrowser){ @@ -152,13 +145,11 @@ IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){ Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors)); } - private void ExecutePlugins(IFrame frame, PluginEnvironment environment){ - if (!HasAnyPlugin(environment)){ + private void ExecutePlugins(IFrame frame, PluginEnvironment environment, Control sync){ + if (!HasAnyPlugin(environment) || !ScriptLoader.ExecuteFile(frame, PluginSetupScriptNames[environment], sync)){ return; } - ScriptLoader.ExecuteScript(frame, PluginSetupScripts[environment], environment.GetScriptIdentifier()); - bool includeDisabled = environment.IncludesDisabledPlugins(); if (includeDisabled){ diff --git a/Resources/ScriptLoader.cs b/Resources/ScriptLoader.cs index 2cf10233..f370c5ad 100644 --- a/Resources/ScriptLoader.cs +++ b/Resources/ScriptLoader.cs @@ -1,5 +1,6 @@ using CefSharp; using System; +using System.Collections.Generic; using System.IO; using System.Text; using System.Windows.Forms; @@ -15,17 +16,29 @@ namespace TweetDuck.Resources{ static class ScriptLoader{ - public static string LoadResource(string name, bool silent = false, Control sync = null){ + private static readonly Dictionary<string, string> CachedData = new Dictionary<string, string>(16); + + public static string LoadResourceSilent(string name){ + return LoadResource(name, null); + } + + public static string LoadResource(string name, Control sync){ + if (CachedData.TryGetValue(name, out string resourceData)){ + return resourceData; + } + + string path = Program.ScriptPath; + + #if DEBUG + if (Directory.Exists(HotSwapTargetDir)){ + path = Path.Combine(HotSwapTargetDir, "scripts"); + Debug.WriteLine("Hot swap active, redirecting "+name); + } + #endif + + string resource; + try{ - string path = Program.ScriptPath; - - #if DEBUG - if (Directory.Exists(HotSwapTargetDir)){ - path = Path.Combine(HotSwapTargetDir, "scripts"); - Debug.WriteLine("Hot swap active, redirecting "+name); - } - #endif - string contents = File.ReadAllText(Path.Combine(path, name), Encoding.UTF8); int separator; @@ -34,7 +47,7 @@ public static string LoadResource(string name, bool silent = false, Control sync // #<version>\n if (contents[0] != '#'){ - ShowLoadError(silent, sync, $"File {name} appears to be corrupted, please try reinstalling the app."); + ShowLoadError(sync, $"File {name} appears to be corrupted, please try reinstalling the app."); separator = 0; } else{ @@ -42,44 +55,33 @@ public static string LoadResource(string name, bool silent = false, Control sync string fileVersion = contents.Substring(1, separator-1).TrimEnd(); if (fileVersion != Program.VersionTag){ - ShowLoadError(silent, 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(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."); } } - return contents.Substring(separator).TrimStart(); + resource = contents.Substring(separator).TrimStart(); }catch(Exception ex){ - ShowLoadError(silent, sync, $"Could not load {name}. The program will continue running with limited functionality.\n\n{ex.Message}"); - return null; + ShowLoadError(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 = null){ - string script = LoadResource(file, sync == null, sync); - ExecuteScript(frame, script, GetRootIdentifier(file)); + 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, "td:"+identifier, 1); + frame.ExecuteJavaScriptAsync(script, identifier, 1); } } - public static string GetRootIdentifier(string file){ - return "root:"+Path.GetFileNameWithoutExtension(file); - } - - private static void ShowLoadError(bool silent, Control sync, string message){ - if (silent){ - return; - } - - if (sync == null){ - FormMessage.Error("Resource Error", message, FormMessage.OK); - } - else{ - sync.InvokeSafe(() => FormMessage.Error("Resource Error", message, FormMessage.OK)); - } + private static void ShowLoadError(Control sync, string message){ + sync?.InvokeSafe(() => FormMessage.Error("Resource Error", message, FormMessage.OK)); } #if DEBUG @@ -126,20 +128,18 @@ public static void HotSwap(){ sw.Stop(); Debug.WriteLine("Finished rebuild script in "+sw.ElapsedMilliseconds+" ms"); + CachedData.Clear(); + // Force update plugin manager setup scripts string newPluginRoot = Path.Combine(HotSwapTargetDir, "plugins"); const BindingFlags flagsInstance = BindingFlags.Instance | BindingFlags.NonPublic; - const BindingFlags flagsStatic = BindingFlags.Static | BindingFlags.NonPublic; Type typePluginManager = typeof(PluginManager); Type typeFormBrowser = typeof(FormBrowser); // ReSharper disable PossibleNullReferenceException - object pluginSetupScripts = typePluginManager.GetMethod("LoadSetupScripts", flagsStatic).Invoke(null, new object[0]); - typePluginManager.GetField("PluginSetupScripts", flagsStatic).SetValue(null, pluginSetupScripts); - object instPluginManager = typeFormBrowser.GetField("plugins", flagsInstance).GetValue(FormManager.TryFind<FormBrowser>()); typePluginManager.GetField("rootPath", flagsInstance).SetValue(instPluginManager, newPluginRoot);