Avatars: cache whether the feature is enabled
Calling isOptionEnabled() every time the feature needs to inject avatars
next to a thread is very expensive, so this CL adds logic for caching
this value and updating it when necessary. It could be further
optimized, but I think we have achieved a sweet spot between
optimization and complexity (which could cause bugs).
Fixed: twpowertools:88
Change-Id: Ia7abba2579a00e14125d9912ea21fa953b3fcd53
diff --git a/package-lock.json b/package-lock.json
index 7a6ad3c..538a7f1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"license": "MIT",
"dependencies": {
"@material/tooltip": "^12.0.0",
+ "async-mutex": "^0.3.2",
"google-protobuf": "^3.18.0-rc.2",
"grpc-web": "^1.2.1",
"idb": "^6.1.2",
@@ -467,6 +468,14 @@
"node": ">=8"
}
},
+ "node_modules/async-mutex": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz",
+ "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==",
+ "dependencies": {
+ "tslib": "^2.3.1"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -2652,6 +2661,14 @@
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true
},
+ "async-mutex": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz",
+ "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==",
+ "requires": {
+ "tslib": "^2.3.1"
+ }
+ },
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
diff --git a/package.json b/package.json
index 3263d41..5765554 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
"private": true,
"dependencies": {
"@material/tooltip": "^12.0.0",
+ "async-mutex": "^0.3.2",
"google-protobuf": "^3.18.0-rc.2",
"grpc-web": "^1.2.1",
"idb": "^6.1.2",
diff --git a/src/contentScripts/communityConsole/avatars.js b/src/contentScripts/communityConsole/avatars.js
index 03d6bcb..2936fac 100644
--- a/src/contentScripts/communityConsole/avatars.js
+++ b/src/contentScripts/communityConsole/avatars.js
@@ -1,3 +1,4 @@
+import {Mutex, withTimeout} from 'async-mutex';
import {waitFor} from 'poll-until-promise';
import {CCApi} from '../../common/api.js';
@@ -12,14 +13,45 @@
this.isFilterSetUp = false;
this.privateForums = [];
this.db = new AvatarsDB();
+ this.featureEnabled = false;
+ this.featureEnabledIsStale = true;
+ this.featureEnabledMutex = withTimeout(new Mutex(), 60 * 1000);
// Preload whether the option is enabled or not. This is because in the case
// avatars should be injected, if we don't preload this the layout will
// shift when injecting the first avatar.
- isOptionEnabled('threadlistavatars').then(isEnabled => {
+ this.isEnabled().then(isEnabled => {
if (isEnabled)
document.body.classList.add('TWPT-threadlistavatars-enabled');
});
+
+ // If the extension settings change, set this.featureEnabled as stale. We
+ // could try only doing this only when we're sure it has changed, but there
+ // are many factors (if the user has changed it manually, if a kill switch
+ // was activated, etc.) so we'll do it every time.
+ chrome.storage.sync.onChanged.addListener(() => {
+ console.debug('[threadListAvatars] Marking featureEnabled as stale.');
+ this.featureEnabledIsStale = true;
+ });
+ }
+
+ // Returns a promise resolving to whether the threadlistavatars feature is
+ // enabled.
+ isEnabled() {
+ // When this.featureEnabled is marked as stale, the next time avatars are
+ // injected there is a flood of calls to isEnabled(), which in turn causes a
+ // flood of calls to isOptionEnabled() because it takes some time for it to
+ // be marked as not stale. Thus, hiding the logic behind a mutex fixes this.
+ return this.featureEnabledMutex.runExclusive(() => {
+ if (!this.featureEnabledIsStale)
+ return Promise.resolve(this.featureEnabled);
+
+ return isOptionEnabled('threadlistavatars').then(isEnabled => {
+ this.featureEnabled = isEnabled;
+ this.featureEnabledIsStale = false;
+ return isEnabled;
+ });
+ });
}
// Gets a list of private forums. If it is already cached, the cached list is
@@ -356,7 +388,7 @@
// Inject avatars for thread summary (thread item) |node| in a thread list if
// the threadlistavatars option is enabled.
injectIfEnabled(node) {
- isOptionEnabled('threadlistavatars').then(isEnabled => {
+ this.isEnabled().then(isEnabled => {
if (isEnabled) {
document.body.classList.add('TWPT-threadlistavatars-enabled');
this.inject(node);