diff options
Diffstat (limited to 'ext/js')
| -rw-r--r-- | ext/js/background/request-builder.js | 294 | ||||
| -rw-r--r-- | ext/js/extension/environment.js | 16 | 
2 files changed, 91 insertions, 219 deletions
| diff --git a/ext/js/background/request-builder.js b/ext/js/background/request-builder.js index 663e242b..7ee89539 100644 --- a/ext/js/background/request-builder.js +++ b/ext/js/background/request-builder.js @@ -31,7 +31,6 @@ class RequestBuilder {       * Creates a new instance.       */      constructor() { -        this._onBeforeSendHeadersExtraInfoSpec = ['blocking', 'requestHeaders', 'extraHeaders'];          this._textEncoder = new TextEncoder();          this._ruleIds = new Set();      } @@ -42,6 +41,7 @@ class RequestBuilder {      async prepare() {          try {              await this._clearDynamicRules(); +            await this._clearSessionRules();          } catch (e) {              // NOP          } @@ -54,15 +54,50 @@ class RequestBuilder {       * @returns {Promise<Response>} The response of the `fetch` call.       */      async fetchAnonymous(url, init) { -        if (isObject(chrome.declarativeNetRequest)) { -            return await this._fetchAnonymousDeclarative(url, init); +        const id = this._getNewRuleId(); +        const originUrl = this._getOriginURL(url); +        url = encodeURI(decodeURI(url)); + +        this._ruleIds.add(id); +        try { +            const addRules = [{ +                id, +                priority: 1, +                condition: { +                    urlFilter: `|${this._escapeDnrUrl(url)}|`, +                    resourceTypes: ['xmlhttprequest'] +                }, +                action: { +                    type: 'modifyHeaders', +                    requestHeaders: [ +                        { +                            operation: 'remove', +                            header: 'Cookie' +                        }, +                        { +                            operation: 'set', +                            header: 'Origin', +                            value: originUrl +                        } +                    ], +                    responseHeaders: [ +                        { +                            operation: 'remove', +                            header: 'Set-Cookie' +                        } +                    ] +                } +            }]; + +            await this._updateSessionRules({addRules}); +            try { +                return await fetch(url, init); +            } finally { +                await this._tryUpdateSessionRules({removeRuleIds: [id]}); +            } +        } finally { +            this._ruleIds.delete(id);          } -        const originURL = this._getOriginURL(url); -        const headerModifications = [ -            ['cookie', null], -            ['origin', {name: 'Origin', value: originURL}] -        ]; -        return await this._fetchInternal(url, init, headerModifications);      }      /** @@ -125,145 +160,56 @@ class RequestBuilder {      // Private -    async _fetchInternal(url, init, headerModifications) { -        const filter = { -            urls: [this._getMatchURL(url)], -            types: ['xmlhttprequest'] -        }; - -        let requestId = null; -        const onBeforeSendHeadersCallback = (details) => { -            if (requestId !== null || details.url !== url) { return {}; } -            ({requestId} = details); - -            if (headerModifications === null) { return {}; } - -            const requestHeaders = details.requestHeaders; -            this._modifyHeaders(requestHeaders, headerModifications); -            return {requestHeaders}; -        }; - -        let errorDetailsTimer = null; -        let {promise: errorDetailsPromise, resolve: errorDetailsResolve} = deferPromise(); -        const onErrorOccurredCallback = (details) => { -            if (errorDetailsResolve === null || details.requestId !== requestId) { return; } -            if (errorDetailsTimer !== null) { -                clearTimeout(errorDetailsTimer); -                errorDetailsTimer = null; -            } -            errorDetailsResolve(details); -            errorDetailsResolve = null; -        }; - -        const eventListeners = []; -        const onBeforeSendHeadersExtraInfoSpec = (headerModifications !== null ? this._onBeforeSendHeadersExtraInfoSpec : []); -        this._addWebRequestEventListener(chrome.webRequest.onBeforeSendHeaders, onBeforeSendHeadersCallback, filter, onBeforeSendHeadersExtraInfoSpec, eventListeners); -        this._addWebRequestEventListener(chrome.webRequest.onErrorOccurred, onErrorOccurredCallback, filter, void 0, eventListeners); +    async _clearSessionRules() { +        const rules = await this._getSessionRules(); -        try { -            return await fetch(url, init); -        } catch (e) { -            // onErrorOccurred is not always invoked by this point, so a delay is needed -            if (errorDetailsResolve !== null) { -                errorDetailsTimer = setTimeout(() => { -                    errorDetailsTimer = null; -                    if (errorDetailsResolve === null) { return; } -                    errorDetailsResolve(null); -                    errorDetailsResolve = null; -                }, 100); -            } -            const details = await errorDetailsPromise; -            if (details !== null) { -                const data = {details}; -                this._assignErrorData(e, data); -            } -            throw e; -        } finally { -            this._removeWebRequestEventListeners(eventListeners); -        } -    } +        if (rules.length === 0) { return; } -    _addWebRequestEventListener(target, callback, filter, extraInfoSpec, eventListeners) { -        try { -            for (let i = 0; i < 2; ++i) { -                try { -                    if (typeof extraInfoSpec === 'undefined') { -                        target.addListener(callback, filter); -                    } else { -                        target.addListener(callback, filter, extraInfoSpec); -                    } -                    break; -                } catch (e) { -                    // Firefox doesn't support the 'extraHeaders' option and will throw the following error: -                    // Type error for parameter extraInfoSpec (Error processing 2: Invalid enumeration value "extraHeaders") for [target]. -                    if (i === 0 && `${e.message}`.includes('extraHeaders') && Array.isArray(extraInfoSpec)) { -                        const index = extraInfoSpec.indexOf('extraHeaders'); -                        if (index >= 0) { -                            extraInfoSpec.splice(index, 1); -                            continue; -                        } -                    } -                    throw e; -                } -            } -        } catch (e) { -            console.log(e); -            return; +        const removeRuleIds = []; +        for (const {id} of rules) { +            removeRuleIds.push(id);          } -        eventListeners.push({target, callback}); -    } -    _removeWebRequestEventListeners(eventListeners) { -        for (const {target, callback} of eventListeners) { -            try { -                target.removeListener(callback); -            } catch (e) { -                console.log(e); -            } -        } +        await this._updateSessionRules({removeRuleIds});      } -    _getMatchURL(url) { -        const url2 = new URL(url); -        return `${url2.protocol}//${url2.host}${url2.pathname}${url2.search}`.replace(/\*/g, '%2a'); +    _getSessionRules() { +        return new Promise((resolve, reject) => { +            chrome.declarativeNetRequest.getSessionRules((result) => { +                const e = chrome.runtime.lastError; +                if (e) { +                    reject(new Error(e.message)); +                } else { +                    resolve(result); +                } +            }); +        });      } -    _getOriginURL(url) { -        const url2 = new URL(url); -        return `${url2.protocol}//${url2.host}`; +    _updateSessionRules(options) { +        return new Promise((resolve, reject) => { +            chrome.declarativeNetRequest.updateSessionRules(options, () => { +                const e = chrome.runtime.lastError; +                if (e) { +                    reject(new Error(e.message)); +                } else { +                    resolve(); +                } +            }); +        });      } -    _modifyHeaders(headers, modifications) { -        modifications = new Map(modifications); - -        for (let i = 0, ii = headers.length; i < ii; ++i) { -            const header = headers[i]; -            const name = header.name.toLowerCase(); -            const modification = modifications.get(name); -            if (typeof modification === 'undefined') { continue; } - -            modifications.delete(name); - -            if (modification === null) { -                headers.splice(i, 1); -                --i; -                --ii; -            } else { -                headers[i] = modification; -            } -        } - -        for (const header of modifications.values()) { -            if (header !== null) { -                headers.push(header); -            } +    async _tryUpdateSessionRules(options) { +        try { +            await this._updateSessionRules(options); +            return true; +        } catch (e) { +            return false;          }      }      async _clearDynamicRules() { -        if (!isObject(chrome.declarativeNetRequest)) { return; } - -        const rules = this._getDynamicRules(); +        const rules = await this._getDynamicRules();          if (rules.length === 0) { return; } @@ -275,53 +221,6 @@ class RequestBuilder {          await this._updateDynamicRules({removeRuleIds});      } -    async _fetchAnonymousDeclarative(url, init) { -        const id = this._getNewRuleId(); -        const originUrl = this._getOriginURL(url); -        url = encodeURI(decodeURI(url)); - -        this._ruleIds.add(id); -        try { -            const addRules = [{ -                id, -                priority: 1, -                condition: { -                    urlFilter: `|${this._escapeDnrUrl(url)}|`, -                    resourceTypes: ['xmlhttprequest'] -                }, -                action: { -                    type: 'modifyHeaders', -                    requestHeaders: [ -                        { -                            operation: 'remove', -                            header: 'Cookie' -                        }, -                        { -                            operation: 'set', -                            header: 'Origin', -                            value: originUrl -                        } -                    ], -                    responseHeaders: [ -                        { -                            operation: 'remove', -                            header: 'Set-Cookie' -                        } -                    ] -                } -            }]; - -            await this._updateDynamicRules({addRules}); -            try { -                return await this._fetchInternal(url, init, null); -            } finally { -                await this._tryUpdateDynamicRules({removeRuleIds: [id]}); -            } -        } finally { -            this._ruleIds.delete(id); -        } -    } -      _getDynamicRules() {          return new Promise((resolve, reject) => {              chrome.declarativeNetRequest.getDynamicRules((result) => { @@ -348,15 +247,6 @@ class RequestBuilder {          });      } -    async _tryUpdateDynamicRules(options) { -        try { -            await this._updateDynamicRules(options); -            return true; -        } catch (e) { -            return false; -        } -    } -      _getNewRuleId() {          let id = 1;          while (this._ruleIds.has(id)) { @@ -367,6 +257,11 @@ class RequestBuilder {          return id;      } +    _getOriginURL(url) { +        const url2 = new URL(url); +        return `${url2.protocol}//${url2.host}`; +    } +      _escapeDnrUrl(url) {          return url.replace(/[|*^]/g, (char) => this._urlEncodeUtf8(char));      } @@ -380,25 +275,6 @@ class RequestBuilder {          return result;      } -    _assignErrorData(error, data) { -        try { -            error.data = data; -        } catch (e) { -            // On Firefox, assigning DOMException.data can fail in certain contexts. -            // https://bugzilla.mozilla.org/show_bug.cgi?id=1776555 -            try { -                Object.defineProperty(error, 'data', { -                    configurable: true, -                    enumerable: true, -                    writable: true, -                    value: data -                }); -            } catch (e2) { -                // NOP -            } -        } -    } -      static _joinUint8Arrays(items, totalLength) {          if (items.length === 1) {              const {array, length} = items[0]; diff --git a/ext/js/extension/environment.js b/ext/js/extension/environment.js index ec1e8612..ad5a19ae 100644 --- a/ext/js/extension/environment.js +++ b/ext/js/extension/environment.js @@ -31,8 +31,9 @@ class Environment {      }      async _loadEnvironmentInfo() { -        const browser = await this._getBrowser();          const os = await this._getOperatingSystem(); +        const browser = await this._getBrowser(os); +          return {              browser,              platform: {os} @@ -64,7 +65,7 @@ class Environment {          });      } -    async _getBrowser() { +    async _getBrowser(os) {          try {              if (chrome.runtime.getURL('/').startsWith('ms-browser-extension://')) {                  return 'edge-legacy'; @@ -76,17 +77,12 @@ class Environment {              // NOP          }          if (typeof browser !== 'undefined') { -            try { -                const info = await browser.runtime.getBrowserInfo(); -                if (info.name === 'Fennec') { -                    return 'firefox-mobile'; -                } -            } catch (e) { -                // NOP -            }              if (this._isSafari()) {                  return 'safari';              } +            if (os === 'android') { +                return 'firefox-mobile'; +            }              return 'firefox';          } else {              return 'chrome'; |