diff --git a/dev/data/manifest-variants.json b/dev/data/manifest-variants.json index 75911771..64271327 100644 --- a/dev/data/manifest-variants.json +++ b/dev/data/manifest-variants.json @@ -154,8 +154,10 @@ {"action": "move", "path": ["content_security_policy_old"], "newPath": ["content_security_policy", "extension_pages"]}, {"action": "move", "path": ["sandbox", "content_security_policy"], "newPath": ["content_security_policy", "sandbox"]}, {"action": "remove", "path": ["permissions"], "item": ""}, + {"action": "remove", "path": ["permissions"], "item": "webRequest"}, + {"action": "remove", "path": ["permissions"], "item": "webRequestBlocking"}, + {"action": "add", "path": ["permissions"], "items": ["declarativeNetRequest", "scripting"]}, {"action": "set", "path": ["host_permissions"], "value": [""], "after": "optional_permissions"}, - {"action": "add", "path": ["permissions"], "items": ["scripting"]}, {"action": "move", "path": ["web_accessible_resources"], "newPath": ["web_accessible_resources_old"]}, {"action": "set", "path": ["web_accessible_resources"], "value": [{"resources": [], "matches": [""]}], "after": "web_accessible_resources_old"}, {"action": "move", "path": ["web_accessible_resources_old"], "newPath": ["web_accessible_resources", 0, "resources"]} diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 322d9400..205c06b1 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -187,6 +187,7 @@ class Backend { yomichan.on('log', this._onLog.bind(this)); + await this._requestBuilder.prepare(); await this._environment.prepare(); this._clipboardReader.browser = this._environment.getInfo().browser; diff --git a/ext/bg/js/request-builder.js b/ext/bg/js/request-builder.js index e4380734..9c469056 100644 --- a/ext/bg/js/request-builder.js +++ b/ext/bg/js/request-builder.js @@ -19,9 +19,22 @@ class RequestBuilder { constructor() { this._extraHeadersSupported = null; this._onBeforeSendHeadersExtraInfoSpec = ['blocking', 'requestHeaders', 'extraHeaders']; + this._textEncoder = new TextEncoder(); + this._ruleIds = new Set(); + } + + async prepare() { + try { + await this._clearDynamicRules(); + } catch (e) { + // NOP + } } async fetchAnonymous(url, init) { + if (isObject(chrome.declarativeNetRequest)) { + return await this._fetchAnonymousDeclarative(url, init); + } const originURL = this._getOriginURL(url); const modifications = [ ['cookie', null], @@ -130,4 +143,124 @@ class RequestBuilder { } } } + + async _clearDynamicRules() { + if (!isObject(chrome.declarativeNetRequest)) { return; } + + const rules = this._getDynamicRules(); + + if (rules.length === 0) { return; } + + const removeRuleIds = []; + for (const {id} of rules) { + removeRuleIds.push(id); + } + + 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 fetch(url, init); + } finally { + await this._tryUpdateDynamicRules({removeRuleIds: [id]}); + } + } finally { + this._ruleIds.delete(id); + } + } + + _getDynamicRules() { + return new Promise((resolve, reject) => { + chrome.declarativeNetRequest.getDynamicRules((result) => { + const e = chrome.runtime.lastError; + if (e) { + reject(new Error(e.message)); + } else { + resolve(result); + } + }); + }); + } + + _updateDynamicRules(options) { + return new Promise((resolve, reject) => { + chrome.declarativeNetRequest.updateDynamicRules(options, () => { + const e = chrome.runtime.lastError; + if (e) { + reject(new Error(e.message)); + } else { + resolve(); + } + }); + }); + } + + async _tryUpdateDynamicRules(options) { + try { + await this._updateDynamicRules(options); + return true; + } catch (e) { + return false; + } + } + + _getNewRuleId() { + let id = 1; + while (this._ruleIds.has(id)) { + const pre = id; + ++id; + if (id === pre) { throw new Error('Could not generate an id'); } + } + return id; + } + + _escapeDnrUrl(url) { + return url.replace(/[|*^]/g, (char) => this._urlEncodeUtf8(char)); + } + + _urlEncodeUtf8(text) { + const array = this._textEncoder.encode(text); + let result = ''; + for (const byte of array) { + result += `%${byte.toString(16).toUpperCase().padStart(2, '0')}`; + } + return result; + } }