diff --git a/omega-target-chromium-extension/src/module/proxy/index.coffee b/omega-target-chromium-extension/src/module/proxy/index.coffee index 637cab8..1889ccb 100644 --- a/omega-target-chromium-extension/src/module/proxy/index.coffee +++ b/omega-target-chromium-extension/src/module/proxy/index.coffee @@ -1,8 +1,14 @@ +FirefoxProxyImpl = require('./proxy_impl_firefox') ListenerProxyImpl = require('./proxy_impl_listener') SettingsProxyImpl = require('./proxy_impl_settings') ScriptProxyImpl = require('./proxy_impl_script') -exports.proxyImpls = [ListenerProxyImpl, ScriptProxyImpl, SettingsProxyImpl] +exports.proxyImpls = [ + FirefoxProxyImpl, + ListenerProxyImpl, + ScriptProxyImpl, + SettingsProxyImpl +] exports.getProxyImpl = (log) -> for Impl in exports.proxyImpls if Impl.isSupported() diff --git a/omega-target-chromium-extension/src/module/proxy/proxy_impl_firefox.coffee b/omega-target-chromium-extension/src/module/proxy/proxy_impl_firefox.coffee new file mode 100644 index 0000000..15fb999 --- /dev/null +++ b/omega-target-chromium-extension/src/module/proxy/proxy_impl_firefox.coffee @@ -0,0 +1,121 @@ +OmegaTarget = require('omega-target') +# The browser only accepts native promises as onRequest return values. +# DO NOT USE Bluebird Promises here! +NativePromise = Promise ? null +ProxyImpl = require('./proxy_impl') + +blobUrl = null + +class FirefoxProxyImpl extends ProxyImpl + @isSupported: -> + # chrome.contextMenus check is Android or PC, + # browser.proxy.settings doesn't support on Firefox Android + return !!chrome.contextMenus and + Promise? and + browser?.proxy?.onRequest? and + browser?.proxy?.settings + features: ['fullUrl', 'socks5Auth'] + constructor: -> + super(arguments...) + @_optionsReady = new NativePromise (resolve) => + @_optionsReadyCallback = resolve + # We want to register listeners early so that it can start blocking requests + # when starting the browser & extension, returning correct results later. + @_initRequestListeners() + _initRequestListeners: -> + browser.proxy.onRequest.addListener(@onRequest.bind(this), + {urls: [""]}) + browser.proxy.onError.addListener(@onError.bind(this)) + watchProxyChange: (callback) -> null + applyProfile: (profile, state, options) -> + if blobUrl + URL.revokeObjectURL(blobUrl) + if browser.extension.isAllowedIncognitoAccess() + if profile.profileType is 'DirectProfile' + browser.proxy.settings.set({ + value: { + proxyType: 'none' + } + }) + else if profile.profileType is 'SystemProfile' + # Clear proxy settings, returning proxy control to Firefox. + browser.proxy.settings.clear({}) + else + pacScript = @getProfilePacScript(profile, state, options) + blob = new Blob( + [pacScript], + { type: 'application/x-ns-proxy-autoconfig' }) + blobUrl = URL.createObjectURL(blob) + browser.proxy.settings.set({ + value: { + proxyType: 'autoConfig', + autoConfigUrl: blobUrl + } + }) + @_options = options + @_profile = profile + @_optionsReadyCallback?() + @_optionsReadyCallback = null + return @setProxyAuth(profile, options) + onRequest: (requestDetails) -> + return undefined if browser.extension.isAllowedIncognitoAccess() + # TODO 将来可以在这里实现按标签进行代理控制功能 + #return undefined + # The browser only recognizes native promises return values, not Bluebird. + return NativePromise.resolve(@_optionsReady.then(=> + request = OmegaPac.Conditions.requestFromUrl(requestDetails.url) + profile = @_profile + while profile + result = OmegaPac.Profiles.match(profile, request) + if not result + switch profile.profileType + when 'DirectProfile' + return {type: 'direct'} + when 'SystemProfile' + # Returning undefined means using the default proxy from previous. + # https://hg.mozilla.org/mozilla-central/rev/9f0ee2f582a2#l1.337 + return undefined + else + throw new Error('Unsupported profile: ' + profile.profileType) + if Array.isArray(result) + proxy = result[2] + auth = result[3] + return @proxyInfo(proxy, auth) if proxy + next = result[0] + else if result.profileName + next = OmegaPac.Profiles.nameAsKey(result.profileName) + else + break + profile = OmegaPac.Profiles.byKey(next, @_options) + + throw new Error('Profile not found: ' + next) + )) + onError: (error) -> + @log.error(error) + proxyInfo: (proxy, auth) -> + proxyInfo = + type: proxy.scheme + host: proxy.host + port: proxy.port + if proxyInfo.type == 'socks5' + # MOZ: SOCKS5 proxies should be specified as "type": "socks". + # https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/proxy/ProxyInfo + proxyInfo.type = 'socks' + if auth + # Username & password here are only available for SOCKS5. + # https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/proxy/ProxyInfo + # HTTP proxy auth must be handled via webRequest.onAuthRequired. + proxyInfo.username = auth.username + proxyInfo.password = auth.password + if proxyInfo.type == 'socks' + # Enable SOCKS remote DNS. + # TODO(catus): Maybe allow the users to configure this? + proxyInfo.proxyDNS = true + + # TODO(catus): Maybe allow proxyDNS for socks4? Server may support SOCKS4a. + # It cannot default to true though, since SOCKS4 servers that does not have + # the SOCKS4a extension may simply refuse to work. + + return [proxyInfo] + +module.exports = FirefoxProxyImpl diff --git a/omega-web/src/popup/grant_permissions.html b/omega-web/src/popup/grant_permissions.html index 508db06..67b28c2 100644 --- a/omega-web/src/popup/grant_permissions.html +++ b/omega-web/src/popup/grant_permissions.html @@ -2,10 +2,48 @@ - + Permissions required + - - + +
    +
  1. +
    +

    + Site permissions is required. The Api proxy.onRequest requires site permissions. +

    +
    +
  2. +
  3. +
    +

    IncognitoAccess is required.According to Firefox API requirements.Changing proxy settings requires private browsing window access.

    +
    + +
    +
    +
  4. +
+ loading... diff --git a/omega-web/src/popup/img/AllowedIncognitoAccess.png b/omega-web/src/popup/img/AllowedIncognitoAccess.png new file mode 100644 index 0000000..7a248ae Binary files /dev/null and b/omega-web/src/popup/img/AllowedIncognitoAccess.png differ diff --git a/omega-web/src/popup/js/grant_permissions.js b/omega-web/src/popup/js/grant_permissions.js index 3f35174..9e4933e 100644 --- a/omega-web/src/popup/js/grant_permissions.js +++ b/omega-web/src/popup/js/grant_permissions.js @@ -1,9 +1,32 @@ +window.addEventListener('load', async function(){ + const dialogEl = document.querySelector('.loading-dialog') + dialogEl.showModal() + dialogEl.addEventListener('cancel', (e)=>e.preventDefault()) + const permissionValue = {origins: [""]} + const hasPermission = await browser.permissions.contains(permissionValue); + const isAllowedIncognitoAccess = !chrome.contextMenus || await browser.extension.isAllowedIncognitoAccess() + if (hasPermission) { + document.querySelector('.site-permissions-required').remove() + } + if (isAllowedIncognitoAccess) { + document.querySelector('.incognito-access-required').remove() + } + dialogEl.remove() + document.body.style.opacity = ''; +}, {once: true}) const btn = document.querySelector('#grant-permissions-btn') btn.onclick = async ()=>{ const permissionValue = {origins: [""]} const hasPermission = await browser.permissions.request(permissionValue); + const isAllowedIncognitoAccess = !chrome.contextMenus || await browser.extension.isAllowedIncognitoAccess() if (hasPermission) { + document.querySelector('.site-permissions-required')?.remove() + } + if (isAllowedIncognitoAccess) { + document.querySelector('.incognito-access-required')?.remove() + } + if (hasPermission && isAllowedIncognitoAccess) { location.href = 'index.html' } } diff --git a/omega-web/src/popup/js/loader.js b/omega-web/src/popup/js/loader.js index 6045fee..e63ada0 100644 --- a/omega-web/src/popup/js/loader.js +++ b/omega-web/src/popup/js/loader.js @@ -25,11 +25,14 @@ $script('../js/omega_target_popup.js', 'om-target', function() { } const permissionValue = {origins: [""]} if (globalThis.browser && browser.proxy && browser.proxy.onRequest){ - chrome.permissions.contains(permissionValue).then((hasPermission)=>{ - if (!hasPermission) { - location.href = 'grant_permissions.html' - } else { + Promise.all([browser.permissions.contains(permissionValue), browser.extension.isAllowedIncognitoAccess()]) + .then(([sitePermissions, isAllowedIncognitoAccess])=>{ + // chrome.contextMenus check is Android or PC, + // browser.proxy.settings doesn't support Android + if (sitePermissions && (!chrome.contextMenus || isAllowedIncognitoAccess)) { init(); + } else { + location.href = 'grant_permissions.html' } }) } else {