feat: add StartupDataStorage

This class will let scripts retrieve and modify the startup data object.

Change-Id: I83298612425ab8a87ce5b83c7b83c62088af56ac
diff --git a/package-lock.json b/package-lock.json
index 897349c..866a587 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
       "version": "0.0.0",
       "license": "MIT",
       "dependencies": {
+        "@datastructures-js/queue": "^4.2.3",
         "@lit/localize": "^0.12.0",
         "@material/banner": "^14.0.0",
         "@material/mwc-circular-progress": "^0.27.0",
@@ -794,6 +795,11 @@
         "@jridgewell/sourcemap-codec": "^1.4.10"
       }
     },
+    "node_modules/@datastructures-js/queue": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/@datastructures-js/queue/-/queue-4.2.3.tgz",
+      "integrity": "sha512-GWVMorC/xi2V2ta+Z/CPgPGHL2ZJozcj48g7y2nIX5GIGZGRrbShSHgvMViJwHJurUzJYOdIdRZnWDRrROFwJA=="
+    },
     "node_modules/@discoveryjs/json-ext": {
       "version": "0.5.3",
       "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz",
@@ -8911,6 +8917,11 @@
         }
       }
     },
+    "@datastructures-js/queue": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/@datastructures-js/queue/-/queue-4.2.3.tgz",
+      "integrity": "sha512-GWVMorC/xi2V2ta+Z/CPgPGHL2ZJozcj48g7y2nIX5GIGZGRrbShSHgvMViJwHJurUzJYOdIdRZnWDRrROFwJA=="
+    },
     "@discoveryjs/json-ext": {
       "version": "0.5.3",
       "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz",
diff --git a/package.json b/package.json
index cc18ce9..3624206 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
   },
   "private": true,
   "dependencies": {
+    "@datastructures-js/queue": "^4.2.3",
     "@lit/localize": "^0.12.0",
     "@material/banner": "^14.0.0",
     "@material/mwc-circular-progress": "^0.27.0",
diff --git a/src/common/architecture/dependenciesProvider/DependenciesProvider.ts b/src/common/architecture/dependenciesProvider/DependenciesProvider.ts
index 5c7ec33..221cc51 100644
--- a/src/common/architecture/dependenciesProvider/DependenciesProvider.ts
+++ b/src/common/architecture/dependenciesProvider/DependenciesProvider.ts
@@ -3,16 +3,19 @@
 import OptionsProvider from '../../OptionsProvider';
 import WorkflowsImport from '../../../features/workflows/core/communityConsole/import';
 import Workflows from '../../../features/workflows/core/communityConsole/workflows';
+import StartupDataStorage from '../../../contentScripts/communityConsole/utils/StartupDataStorage';
 
 export const AutoRefreshDependency = 'autoRefresh';
 export const ExtraInfoDependency = 'extraInfo';
 export const OptionsProviderDependency = 'optionsProvider';
+export const StartupDataStorageDependency = 'startupDataStorage';
 export const WorkflowsDependency = 'workflows';
 export const WorkflowsImportDependency = 'workflowsImport';
 export const DependenciesToClass = {
   [AutoRefreshDependency]: AutoRefresh,
   [ExtraInfoDependency]: ExtraInfo,
   [OptionsProviderDependency]: OptionsProvider,
+  [StartupDataStorageDependency]: StartupDataStorage,
   [WorkflowsDependency]: Workflows,
   [WorkflowsImportDependency]: WorkflowsImport,
 };
diff --git a/src/contentScripts/communityConsole/utils/StartupDataStorage.ts b/src/contentScripts/communityConsole/utils/StartupDataStorage.ts
new file mode 100644
index 0000000..6e67a5d
--- /dev/null
+++ b/src/contentScripts/communityConsole/utils/StartupDataStorage.ts
@@ -0,0 +1,48 @@
+import { Queue } from '@datastructures-js/queue';
+import StartupDataModel from '../../../models/StartupData';
+
+type StartupDataModification = (startupData: StartupDataModel) => void;
+
+export default class StartupDataStorage {
+  private startupData: StartupDataModel;
+  private modificationsQueue: Queue<StartupDataModification>;
+
+  constructor() {
+    const rawData = this.getHtmlElement().getAttribute('data-startup');
+    this.startupData = new StartupDataModel(rawData ? JSON.parse(rawData) : {});
+    this.modificationsQueue = new Queue();
+  }
+
+  get(): StartupDataModel {
+    return this.startupData;
+  }
+
+  enqueueModification(
+    modification: StartupDataModification,
+  ): StartupDataStorage {
+    this.modificationsQueue.enqueue(modification);
+    return this;
+  }
+
+  applyModifications(): StartupDataStorage {
+    while (!this.modificationsQueue.isEmpty()) {
+      const modification = this.modificationsQueue.dequeue();
+      modification(this.startupData);
+    }
+    this.persistToDOM();
+    return this;
+  }
+
+  private persistToDOM() {
+    const serializedData = JSON.stringify(this.startupData.data);
+    this.getHtmlElement().setAttribute('data-startup', serializedData);
+  }
+
+  private getHtmlElement() {
+    const htmlElement = document.querySelector('html');
+    if (!htmlElement) {
+      throw new Error("StartupDataStorage: couldn't find the html element.");
+    }
+    return htmlElement;
+  }
+}
diff --git a/src/scripts/Scripts.ts b/src/scripts/Scripts.ts
index 5696ddb..2f4db19 100644
--- a/src/scripts/Scripts.ts
+++ b/src/scripts/Scripts.ts
@@ -4,10 +4,14 @@
 import MWI18nServerScript from './mainWorldServers/MWI18nServerScript.script';
 import MWOptionsWatcherServerScript from './mainWorldServers/MWOptionsWatcherServerScript.script';
 import OptionsProviderSetUpScript from './optionsProvider/optionsProvider.script';
+import ApplyStartupDataModificationsOnMainScript from './startupDataStorage/applyStartupDataModificationsOnMain.script';
+import ApplyStartupDataModificationsOnStartScript from './startupDataStorage/applyStartupDataModificationsOnStart.script';
 import XHRInterceptorScript from './xhrInterceptor/xhrInterceptor.script';
 
 export default class StandaloneScripts extends ScriptFilterListProvider {
   private scripts: ConcreteScript[] = [
+    ApplyStartupDataModificationsOnMainScript,
+    ApplyStartupDataModificationsOnStartScript,
     InjectLitComponentsScript,
     MWI18nServerScript,
     MWOptionsWatcherServerScript,
diff --git a/src/scripts/startupDataStorage/applyStartupDataModificationsOnMain.script.ts b/src/scripts/startupDataStorage/applyStartupDataModificationsOnMain.script.ts
new file mode 100644
index 0000000..2b9642d
--- /dev/null
+++ b/src/scripts/startupDataStorage/applyStartupDataModificationsOnMain.script.ts
@@ -0,0 +1,10 @@
+import { ScriptRunPhase } from '../../common/architecture/scripts/Script';
+import BaseApplyStartupDataModificationsScript from './baseApplyStartupDataModifications.script';
+
+/**
+ * Applies pending startup data modifications which have been added by other
+ * scripts at the main run phase.
+ */
+export default class ApplyStartupDataModificationsOnMainScript extends BaseApplyStartupDataModificationsScript {
+  runPhase = ScriptRunPhase.Main;
+}
diff --git a/src/scripts/startupDataStorage/applyStartupDataModificationsOnStart.script.ts b/src/scripts/startupDataStorage/applyStartupDataModificationsOnStart.script.ts
new file mode 100644
index 0000000..0903e1b
--- /dev/null
+++ b/src/scripts/startupDataStorage/applyStartupDataModificationsOnStart.script.ts
@@ -0,0 +1,10 @@
+import { ScriptRunPhase } from '../../common/architecture/scripts/Script';
+import BaseApplyStartupDataModificationsScript from './baseApplyStartupDataModifications.script';
+
+/**
+ * Applies pending startup data modifications which have been added by other
+ * scripts at the start run phase.
+ */
+export default class ApplyStartupDataModificationsOnStartScript extends BaseApplyStartupDataModificationsScript {
+  runPhase = ScriptRunPhase.Start;
+}
diff --git a/src/scripts/startupDataStorage/baseApplyStartupDataModifications.script.ts b/src/scripts/startupDataStorage/baseApplyStartupDataModifications.script.ts
new file mode 100644
index 0000000..1686b77
--- /dev/null
+++ b/src/scripts/startupDataStorage/baseApplyStartupDataModifications.script.ts
@@ -0,0 +1,26 @@
+import DependenciesProviderSingleton, {
+  StartupDataStorageDependency,
+} from '../../common/architecture/dependenciesProvider/DependenciesProvider';
+import Script, {
+  ScriptEnvironment,
+  ScriptPage,
+} from '../../common/architecture/scripts/Script';
+
+/**
+ * Base class which applies pending startup data modifications which have been
+ * added by other scripts.
+ */
+export default abstract class BaseApplyStartupDataModificationsScript extends Script {
+  priority = 2 ** 32;
+
+  page = ScriptPage.CommunityConsole;
+  environment = ScriptEnvironment.ContentScript;
+
+  execute() {
+    const dependenciesProvider = DependenciesProviderSingleton.getInstance();
+    const startupDataStoarge = dependenciesProvider.getDependency(
+      StartupDataStorageDependency,
+    );
+    startupDataStoarge.applyModifications();
+  }
+}