diff options
Diffstat (limited to '')
-rw-r--r-- | comm/chat/modules/InteractiveBrowser.sys.mjs | 138 |
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); + } + }); + }, +}; |