feat: add stylesheet script type
This script type will help in adding stylesheets dynamically based on
whether an option is enabled or not.
Change-Id: I946dadb2674bb0f98112adba3ac204c1e04f765e
diff --git a/src/common/StylesheetManager.ts b/src/common/StylesheetManager.ts
new file mode 100644
index 0000000..6867dd4
--- /dev/null
+++ b/src/common/StylesheetManager.ts
@@ -0,0 +1,33 @@
+import { StylesheetAttributes, injectStylesheet } from './contentScriptsUtils';
+
+export default class StylesheetManager {
+ private injectedElement: HTMLElement;
+
+ constructor(
+ /**
+ * Relative path to the stylesheet from the extension root.
+ */
+ public stylesheet: string,
+
+ /**
+ * Attributes to include in the injected <link> element.
+ */
+ public attributes: StylesheetAttributes = {},
+ ) {}
+
+ isInjected() {
+ return this.injectedElement !== undefined;
+ }
+
+ inject() {
+ this.injectedElement = injectStylesheet(
+ chrome.runtime.getURL(this.stylesheet),
+ this.attributes,
+ );
+ }
+
+ remove() {
+ this.injectedElement.remove();
+ this.injectedElement = undefined;
+ }
+}
diff --git a/src/common/architecture/scripts/stylesheet/StylesheetScript.ts b/src/common/architecture/scripts/stylesheet/StylesheetScript.ts
new file mode 100644
index 0000000..991afa5
--- /dev/null
+++ b/src/common/architecture/scripts/stylesheet/StylesheetScript.ts
@@ -0,0 +1,62 @@
+import StylesheetManager from '../../../StylesheetManager';
+import { StylesheetAttributes } from '../../../contentScriptsUtils';
+import OptionsProvider from '../../../options/OptionsProvider';
+import DependenciesProviderSingleton, {
+ OptionsProviderDependency,
+} from '../../dependenciesProvider/DependenciesProvider';
+import Script, { ScriptEnvironment, ScriptRunPhase } from '../Script';
+
+/**
+ * Script which injects a stylesheet depending on a set condition. It
+ * dynamically reevaluates the condition when the options configuration changes.
+ */
+export default abstract class StylesheetScript extends Script {
+ readonly environment = ScriptEnvironment.ContentScript;
+ readonly runPhase = ScriptRunPhase.Start;
+
+ /**
+ * Relative path to the stylesheet from the extension root.
+ */
+ abstract readonly stylesheet: string;
+ /**
+ * Attributes to include in the injected <link> element.
+ */
+ readonly attributes: StylesheetAttributes = {};
+
+ protected optionsProvider: OptionsProvider;
+ private stylesheetManager: StylesheetManager;
+
+ constructor() {
+ super();
+ const dependenciesProvider = DependenciesProviderSingleton.getInstance();
+ this.optionsProvider = dependenciesProvider.getDependency(
+ OptionsProviderDependency,
+ );
+ }
+
+ /**
+ * Condition which decides whether the stylesheet should be injected or not.
+ *
+ * @returns {boolean} Whether the stylesheet should be injected.
+ */
+ abstract shouldBeInjected(): Promise<boolean>;
+
+ execute() {
+ this.stylesheetManager = new StylesheetManager(
+ this.stylesheet,
+ this.attributes,
+ );
+ this.optionsProvider.addListener(this.evaluateInjection.bind(this));
+ this.evaluateInjection();
+ }
+
+ async evaluateInjection() {
+ const shouldBeInjected = await this.shouldBeInjected();
+ if (!this.stylesheetManager.isInjected() && shouldBeInjected) {
+ this.stylesheetManager.inject();
+ }
+ if (this.stylesheetManager.isInjected() && !shouldBeInjected) {
+ this.stylesheetManager.remove();
+ }
+ }
+}