summaryrefslogtreecommitdiffstats
path: root/comm/chat/modules/InteractiveBrowser.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'comm/chat/modules/InteractiveBrowser.sys.mjs')
-rw-r--r--comm/chat/modules/InteractiveBrowser.sys.mjs138
1 files changed, 138 insertions, 0 deletions
diff --git a/comm/chat/modules/InteractiveBrowser.sys.mjs b/comm/chat/modules/InteractiveBrowser.sys.mjs
new file mode 100644
index 0000000000..700bea8a61
--- /dev/null
+++ b/comm/chat/modules/InteractiveBrowser.sys.mjs
@@ -0,0 +1,138 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+export class CancelledError extends Error {
+ constructor() {
+ super("Interactive browser request was cancelled");
+ }
+}
+
+export var InteractiveBrowser = {
+ /**
+ * URL to redirect to for completion of the redirect.
+ *
+ * @type {string}
+ */
+ COMPLETION_URL: "https://localhost",
+
+ /**
+ * Open an interactive browser prompt that should be redirected to the completion URL.
+ *
+ * @param {string} url - URL to start the interaction from.
+ * @param {string} promptText - Prompt for the user for context to the interaction.
+ * @returns {Promise<object>} Resolves when the redirect succeeds, else rejects.
+ */
+ waitForRedirect(url, promptText) {
+ return this._browserRequest(url).then(({ window, webProgress, signal }) => {
+ window.document.title = promptText;
+ return this._listenForRedirect({
+ window,
+ webProgress,
+ signal,
+ });
+ });
+ },
+
+ /**
+ * Open a browser window to request an interaction from the user.
+ *
+ * @param {string} url - URL to load in the browser window
+ * @returns {Promise<object>} If the url is loaded, resolves with an object
+ * containing the |window|, |webRequest| and a |signal|. The |signal| is an
+ * AbortSignal that gets triggered, when the "request is cancelled", i.e. the
+ * window is closed.
+ */
+ _browserRequest(url) {
+ return new Promise((resolve, reject) => {
+ let browserRequest = {
+ promptText: "",
+ iconURI: "",
+ url,
+ _active: true,
+ abortController: new AbortController(),
+ cancelled() {
+ if (!this._active) {
+ return;
+ }
+ reject(new CancelledError());
+ this.abortController.abort();
+ this._active = false;
+ },
+ loaded(window, webProgress) {
+ if (!this._active) {
+ return;
+ }
+ resolve({ window, webProgress, signal: this.abortController.signal });
+ },
+ };
+ Services.obs.notifyObservers(browserRequest, "browser-request");
+ });
+ },
+
+ /**
+ * Listen for a browser window to redirect to the specified URL.
+ *
+ * @param {Window} param0.window - Window to listen in.
+ * @param {nsIWebProgress} param0.webProgress - Web progress instance.
+ * @param {AbortSignal} param0.signal - Abort signal indicating that this should no longer listen for redirects.
+ * @returns {Promise<string>} Resolves with the resulting redirect URL.
+ */
+ _listenForRedirect({ window, webProgress, signal }) {
+ return new Promise((resolve, reject) => {
+ let listener = {
+ QueryInterface: ChromeUtils.generateQI([
+ Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference,
+ ]),
+ _abortListener: () => {
+ listener._cleanUp();
+ reject(new CancelledError());
+ },
+ _cleanUp() {
+ signal.removeEventListener("abort", listener._abortListener);
+ webProgress.removeProgressListener(this);
+ window.close();
+ },
+ _checkForRedirect(currentUrl) {
+ if (!currentUrl.startsWith(InteractiveBrowser.COMPLETION_URL)) {
+ return;
+ }
+ resolve(currentUrl);
+
+ this._cleanUp();
+ },
+ onStateChange(aWebProgress, request, stateFlags, aStatus) {
+ const wpl = Ci.nsIWebProgressListener;
+ if (stateFlags & (wpl.STATE_START | wpl.STATE_IS_NETWORK)) {
+ try {
+ this._checkForRedirect(request.name);
+ } catch (error) {
+ // Ignore |name| not implemented exception
+ if (error.result !== Cr.NS_ERROR_NOT_IMPLEMENTED) {
+ throw error;
+ }
+ }
+ }
+ },
+ onLocationChange(webProgress, request, location) {
+ this._checkForRedirect(location.spec);
+ },
+ onProgressChange() {},
+ onStatusChange() {},
+ onSecurityChange() {},
+ };
+
+ if (signal.aborted) {
+ reject(new CancelledError());
+ return;
+ }
+ signal.addEventListener("abort", listener._abortListener);
+ webProgress.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL);
+ const browser = window.document.getElementById("requestFrame");
+ if (browser.currentURI.spec) {
+ listener._checkForRedirect(browser.currentURI.spec);
+ }
+ });
+ },
+};