Create mainWorldContentScriptBridge

These classes are the base of a communication bridge between the main
world and content scripts, which has been extracted from the main world
options watcher.

This will be used by the main world i18n component.

Bug: twpowertools:157
Change-Id: I08553b05648ad8453203ab08ecf01e824af15fea
diff --git a/src/common/mainWorldOptionsWatcher/Client.js b/src/common/mainWorldOptionsWatcher/Client.js
index 3e5a9f9..a37b394 100644
--- a/src/common/mainWorldOptionsWatcher/Client.js
+++ b/src/common/mainWorldOptionsWatcher/Client.js
@@ -1,76 +1,35 @@
-export const kDefaultTimeout = 10 * 1000;  // 10 seconds
+import MainWorldContentScriptBridgeClient from '../mainWorldContentScriptBridge/Client.js';
 
 // Main World OptionsWatcher client (used in scripts injected into the Main
 // World (MW) to get the options).
-export default class MWOptionsWatcherClient {
+export default class MWOptionsWatcherClient extends
+    MainWorldContentScriptBridgeClient {
   constructor(options, CSTarget, MWTarget, timeout) {
-    if (!CSTarget || !MWTarget)
-      throw new Error(
-          `[MWOptionsWatcherClient] CSTarget and MWTarget are compulsory.`);
-
-    this.CSTarget = CSTarget;
-    this.MWTarget = MWTarget;
-    this.timeout = timeout ?? kDefaultTimeout;
+    super(CSTarget, MWTarget, timeout);
     this.#setUp(options);
   }
 
   #setUp(options) {
-    this.#sendRequestWithoutCallback('setUp', {options});
+    this._sendRequestWithoutCallback('setUp', {options});
   }
 
   async getOption(option) {
     if (!option) return null;
-    return this.#sendRequest('getOption', {option});
+    return this._sendRequest('getOption', {option});
   }
 
   async getOptions(options) {
     if (!options || options?.length === 0) return [];
-    return this.#sendRequest('getOptions', {options});
+    return this._sendRequest('getOptions', {options});
   }
 
   async isEnabled(option) {
     if (!option) return null;
-    return this.#sendRequest('isEnabled', {option});
+    return this._sendRequest('isEnabled', {option});
   }
 
   async areEnabled(options) {
     if (!options || options?.length === 0) return [];
-    return this.#sendRequest('areEnabled', {options});
-  }
-
-  #sendRequestWithoutCallback(action, request, uuid) {
-    if (!uuid) uuid = self.crypto.randomUUID();
-    const data = {
-      target: this.CSTarget,
-      uuid,
-      action,
-      request,
-    };
-    window.postMessage(data, '*');
-  }
-
-  #sendRequest(action, request) {
-    return new Promise((res, rej) => {
-      const uuid = self.crypto.randomUUID();
-
-      let timeoutId;
-      let listener = e => {
-        if (e.source !== window || e.data?.target !== this.MWTarget ||
-            e.data?.uuid !== uuid)
-          return;
-
-        window.removeEventListener('message', listener);
-        clearTimeout(timeoutId);
-        res(e.data?.response);
-      };
-      window.addEventListener('message', listener);
-
-      timeoutId = setTimeout(() => {
-        window.removeEventListener('message', listener);
-        rej(new Error('Timed out while waiting response.'));
-      }, this.timeout);
-
-      this.#sendRequestWithoutCallback(action, request, uuid);
-    });
+    return this._sendRequest('areEnabled', {options});
   }
 }