diff --git a/Configuration/UserConfig.cs b/Configuration/UserConfig.cs index f2446c23..4e108ac9 100644 --- a/Configuration/UserConfig.cs +++ b/Configuration/UserConfig.cs @@ -19,7 +19,7 @@ public override Type BindToType(string assemblyName, string typeName){ } } - private const int CurrentFileVersion = 11; + private const int CurrentFileVersion = 12; // START OF CONFIGURATION @@ -202,6 +202,9 @@ private void UpgradeFile(){ if (fileVersion == 10){ NotificationSize = TweetNotification.Size.Auto; + } + + if (fileVersion == 11){ BrowserMemoryThreshold = 350; ++fileVersion; } diff --git a/Core/FormBrowser.cs b/Core/FormBrowser.cs index baf2ba72..f89e4353 100644 --- a/Core/FormBrowser.cs +++ b/Core/FormBrowser.cs @@ -32,6 +32,7 @@ sealed partial class FormBrowser : Form{ private readonly UpdateHandler updates; private readonly FormNotificationTweet notification; private readonly ContextMenu contextMenu; + private readonly MemoryUsageTracker memoryUsageTracker; private bool isLoaded; private bool isBrowserReady; @@ -50,6 +51,7 @@ public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings) this.plugins.PluginChangedState += plugins_PluginChangedState; this.contextMenu = ContextMenuBrowser.CreateMenu(this); + this.memoryUsageTracker = new MemoryUsageTracker("TDGF_tryRunCleanup"); this.notification = new FormNotificationTweet(this, plugins){ #if DEBUG @@ -87,6 +89,8 @@ public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings) Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update Disposed += (sender, args) => { + memoryUsageTracker.Dispose(); + browser.Dispose(); contextMenu.Dispose(); @@ -165,6 +169,8 @@ private void browser_LoadingStateChanged(object sender, LoadingStateChangedEvent private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){ if (e.Frame.IsMain){ + memoryUsageTracker.Stop(); + if (Config.ZoomLevel != 100){ BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel); } @@ -190,6 +196,10 @@ private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ } TweetDeckBridge.ResetStaticProperties(); + + if (Config.EnableBrowserGCReload){ + memoryUsageTracker.Start(this, e.Browser, Config.BrowserMemoryThreshold); + } } } @@ -424,6 +434,13 @@ public void OpenSettings(Type startTab){ if (!Config.EnableTrayHighlight){ trayIcon.HasNotifications = false; } + + if (Config.EnableBrowserGCReload){ + memoryUsageTracker.Start(this, browser.GetBrowser(), Config.BrowserMemoryThreshold); + } + else{ + memoryUsageTracker.Stop(); + } UpdateProperties(PropertyBridge.Properties.ExpandLinksOnHover | PropertyBridge.Properties.SwitchAccountSelectors | PropertyBridge.Properties.HasCustomNotificationSound); diff --git a/Core/Other/Settings/TabSettingsAdvanced.Designer.cs b/Core/Other/Settings/TabSettingsAdvanced.Designer.cs index 287d859d..4304bcd9 100644 --- a/Core/Other/Settings/TabSettingsAdvanced.Designer.cs +++ b/Core/Other/Settings/TabSettingsAdvanced.Designer.cs @@ -24,7 +24,6 @@ protected override void Dispose(bool disposing) { /// </summary> private void InitializeComponent() { this.components = new System.ComponentModel.Container(); - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TabSettingsAdvanced)); this.btnClearCache = new System.Windows.Forms.Button(); this.checkHardwareAcceleration = new System.Windows.Forms.CheckBox(); this.toolTip = new System.Windows.Forms.ToolTip(this.components); @@ -35,17 +34,17 @@ private void InitializeComponent() { this.btnOpenAppFolder = new System.Windows.Forms.Button(); this.btnOpenDataFolder = new System.Windows.Forms.Button(); this.checkBrowserGCReload = new System.Windows.Forms.CheckBox(); + this.numMemoryThreshold = new TweetDuck.Core.Controls.NumericUpDownEx(); this.labelApp = new System.Windows.Forms.Label(); this.panelApp = new System.Windows.Forms.Panel(); this.labelPerformance = new System.Windows.Forms.Label(); this.panelPerformance = new System.Windows.Forms.Panel(); - this.numMemoryThreshold = new TweetDuck.Core.Controls.NumericUpDownEx(); this.labelMemoryUsage = new System.Windows.Forms.Label(); this.panelConfiguration = new System.Windows.Forms.Panel(); this.labelConfiguration = new System.Windows.Forms.Label(); + ((System.ComponentModel.ISupportInitialize)(this.numMemoryThreshold)).BeginInit(); this.panelApp.SuspendLayout(); this.panelPerformance.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numMemoryThreshold)).BeginInit(); this.panelConfiguration.SuspendLayout(); this.SuspendLayout(); // @@ -147,9 +146,38 @@ private void InitializeComponent() { this.checkBrowserGCReload.Size = new System.Drawing.Size(190, 17); this.checkBrowserGCReload.TabIndex = 4; this.checkBrowserGCReload.Text = "Enable Browser Memory Threshold"; - this.toolTip.SetToolTip(this.checkBrowserGCReload, resources.GetString("checkBrowserGCReload.ToolTip")); this.checkBrowserGCReload.UseVisualStyleBackColor = true; // + // numMemoryThreshold + // + this.numMemoryThreshold.Increment = new decimal(new int[] { + 50, + 0, + 0, + 0}); + this.numMemoryThreshold.Location = new System.Drawing.Point(202, 82); + this.numMemoryThreshold.Maximum = new decimal(new int[] { + 3000, + 0, + 0, + 0}); + this.numMemoryThreshold.Minimum = new decimal(new int[] { + 200, + 0, + 0, + 0}); + this.numMemoryThreshold.Name = "numMemoryThreshold"; + this.numMemoryThreshold.Size = new System.Drawing.Size(97, 20); + this.numMemoryThreshold.TabIndex = 3; + this.numMemoryThreshold.TextSuffix = " MB"; + this.toolTip.SetToolTip(this.numMemoryThreshold, "Minimum amount of memory usage by the browser process to trigger the cleanup.\r\nTh" + + "is is not a limit, the usage is allowed to exceed this value."); + this.numMemoryThreshold.Value = new decimal(new int[] { + 350, + 0, + 0, + 0}); + // // labelApp // this.labelApp.AutoSize = true; @@ -199,36 +227,6 @@ private void InitializeComponent() { this.panelPerformance.Size = new System.Drawing.Size(322, 105); this.panelPerformance.TabIndex = 3; // - // numMemoryThreshold - // - this.numMemoryThreshold.Increment = new decimal(new int[] { - 50, - 0, - 0, - 0}); - this.numMemoryThreshold.Location = new System.Drawing.Point(202, 82); - this.numMemoryThreshold.Maximum = new decimal(new int[] { - 3000, - 0, - 0, - 0}); - this.numMemoryThreshold.Minimum = new decimal(new int[] { - 200, - 0, - 0, - 0}); - this.numMemoryThreshold.Name = "numMemoryThreshold"; - this.numMemoryThreshold.Size = new System.Drawing.Size(97, 20); - this.numMemoryThreshold.TabIndex = 3; - this.numMemoryThreshold.TextSuffix = " MB"; - this.toolTip.SetToolTip(this.numMemoryThreshold, "Minimum amount of memory usage by the browser process to trigger the cleanup.\r\nTh" + - "is is not a limit, the usage is allowed to exceed this value."); - this.numMemoryThreshold.Value = new decimal(new int[] { - 350, - 0, - 0, - 0}); - // // labelMemoryUsage // this.labelMemoryUsage.AutoSize = true; @@ -273,10 +271,10 @@ private void InitializeComponent() { this.Controls.Add(this.labelApp); this.Name = "TabSettingsAdvanced"; this.Size = new System.Drawing.Size(340, 328); + ((System.ComponentModel.ISupportInitialize)(this.numMemoryThreshold)).EndInit(); this.panelApp.ResumeLayout(false); this.panelPerformance.ResumeLayout(false); this.panelPerformance.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numMemoryThreshold)).EndInit(); this.panelConfiguration.ResumeLayout(false); this.ResumeLayout(false); this.PerformLayout(); diff --git a/Core/Utils/MemoryUsageTracker.cs b/Core/Utils/MemoryUsageTracker.cs new file mode 100644 index 00000000..d31ab7a3 --- /dev/null +++ b/Core/Utils/MemoryUsageTracker.cs @@ -0,0 +1,91 @@ +using System; +using System.Diagnostics; +using System.Timers; +using System.Windows.Forms; +using CefSharp; +using Timer = System.Timers.Timer; + +namespace TweetDuck.Core.Utils{ + sealed class MemoryUsageTracker : IDisposable{ + private const int IntervalMemoryCheck = 60000*30; // 30 minutes + private const int IntervalCleanupAttempt = 60000*5; // 5 minutes + + private readonly string script; + private readonly Timer timer; + private Form owner; + private IBrowser browser; + + private long threshold; + private bool needsCleanup; + + public MemoryUsageTracker(string cleanupFunctionName){ + this.script = $"window.{cleanupFunctionName} && window.{cleanupFunctionName}()"; + + this.timer = new Timer{ Interval = IntervalMemoryCheck }; + this.timer.Elapsed += timer_Elapsed; + } + + public void Start(Form owner, IBrowser browser, int thresholdMB){ + Stop(); + + this.owner = owner; + this.browser = browser; + this.threshold = thresholdMB*1024L*1024L; + this.timer.SynchronizingObject = owner; + this.timer.Start(); + } + + public void Stop(){ + timer.Stop(); + timer.SynchronizingObject = null; + owner = null; + browser = null; + SetNeedsCleanup(false); + } + + public void Dispose(){ + timer.SynchronizingObject = null; + timer.Dispose(); + owner = null; + browser = null; + } + + private void SetNeedsCleanup(bool value){ + if (needsCleanup != value){ + needsCleanup = value; + timer.Interval = value ? IntervalCleanupAttempt : IntervalMemoryCheck; // restarts timer + } + } + + private void timer_Elapsed(object sender, ElapsedEventArgs e){ + if (owner == null || browser == null){ + return; + } + + if (needsCleanup){ + if (!owner.ContainsFocus){ + using(IFrame frame = browser.MainFrame){ + frame.EvaluateScriptAsync(script).ContinueWith(task => { + JavascriptResponse response = task.Result; + + if (response.Success && (response.Result as bool? ?? false)){ + SetNeedsCleanup(false); + } + }); + } + } + } + else{ + try{ + using(Process process = BrowserProcesses.FindProcess(browser)){ + if (process?.PrivateMemorySize64 > threshold){ + SetNeedsCleanup(true); + } + } + }catch{ + // ignore I guess? + } + } + } + } +} diff --git a/Resources/Scripts/code.js b/Resources/Scripts/code.js index 12d2a6f9..6ff4382b 100644 --- a/Resources/Scripts/code.js +++ b/Resources/Scripts/code.js @@ -784,6 +784,40 @@ }; } + // + // Block: Memory cleanup check and execution. + // + window.TDGF_tryRunCleanup = function(){ + // all textareas are empty + if ($("textarea").is(function(){ + return $(this).val().length > 0; + })){ + return false; + } + + // no modals are visible + if ($("#open-modal").is(":visible") || !$(".js-modals-container").is(":empty")){ + return false; + } + + // all columns are in a default state + if ($("section.js-column").is(".is-shifted-1,.is-shifted-2")){ + return false; + } + + // all columns are scrolled to top + if ($(".js-column-scroller").is(function(){ + return $(this).scrollTop() > 0; + })){ + return false; + } + + // cleanup + window.gc && window.gc(); + window.location.reload(); + return true; + }; + // // Block: Disable TweetDeck metrics. // diff --git a/TweetDuck.csproj b/TweetDuck.csproj index 7f5ced4f..c689bcae 100644 --- a/TweetDuck.csproj +++ b/TweetDuck.csproj @@ -203,6 +203,7 @@ </Compile> <Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" /> <Compile Include="Core\Utils\InjectedHTML.cs" /> + <Compile Include="Core\Utils\MemoryUsageTracker.cs" /> <Compile Include="Core\Utils\TwoKeyDictionary.cs" /> <Compile Include="Core\Utils\WindowState.cs" /> <Compile Include="Core\Utils\WindowsUtils.cs" />