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);