diff --git a/omega-i18n/en/messages.json b/omega-i18n/en/messages.json index c7a321e..aecae57 100644 --- a/omega-i18n/en/messages.json +++ b/omega-i18n/en/messages.json @@ -667,6 +667,21 @@ "popup_errorLog": { "message": "Error log" }, + "browserAction_profileDetails_PacProfile": { + "message": "(PAC script)" + }, + "browserAction_profileDetails_SystemProfile": { + "message": "(controlled by other extensions or environment)" + }, + "browserAction_profileDetails_DirectProfile": { + "message": "(not using any proxy)" + }, + "browserAction_profileDetails_SwitchProfile": { + "message": "(switching based on conditions)" + }, + "browserAction_profileDetails_RuleListProfile": { + "message": "(switching based on rule list)" + }, "browserAction_titleNormal": { "message": "SwitchyOmega:: $PROFILE$", "placeholders": { diff --git a/omega-i18n/zh_CN/messages.json b/omega-i18n/zh_CN/messages.json index 6f8cb17..9095fc8 100644 --- a/omega-i18n/zh_CN/messages.json +++ b/omega-i18n/zh_CN/messages.json @@ -492,6 +492,9 @@ } } }, + "options_replaceProfileSuccess": { + "message": "更改选项成功。" + }, "options_modalHeader_deleteProfile": { "message": "删除情景模式" }, @@ -664,6 +667,21 @@ "popup_errorLog": { "message": "错误日志" }, + "browserAction_profileDetails_PacProfile": { + "message": "(PAC 脚本)" + }, + "browserAction_profileDetails_SystemProfile": { + "message": "(由其他扩展或系统环境控制)" + }, + "browserAction_profileDetails_DirectProfile": { + "message": "(不使用任何代理)" + }, + "browserAction_profileDetails_SwitchProfile": { + "message": "(根据条件切换)" + }, + "browserAction_profileDetails_RuleListProfile": { + "message": "(根据规则列表切换)" + }, "browserAction_titleNormal": { "message": "SwitchyOmega:: $PROFILE$", "placeholders": { diff --git a/omega-i18n/zh_HK/messages.json b/omega-i18n/zh_HK/messages.json index 0c8c8fb..99cf49d 100644 --- a/omega-i18n/zh_HK/messages.json +++ b/omega-i18n/zh_HK/messages.json @@ -492,6 +492,9 @@ } } }, + "options_replaceProfileSuccess": { + "message": "更改選項成功。" + }, "options_modalHeader_deleteProfile": { "message": "刪除情景模式" }, @@ -664,6 +667,21 @@ "popup_errorLog": { "message": "錯誤日誌" }, + "browserAction_profileDetails_PacProfile": { + "message": "(PAC 指令碼)" + }, + "browserAction_profileDetails_SystemProfile": { + "message": "(由其他擴展或系統環境控制)" + }, + "browserAction_profileDetails_DirectProfile": { + "message": "(不使用任何代理)" + }, + "browserAction_profileDetails_SwitchProfile": { + "message": "(根據條件切換)" + }, + "browserAction_profileDetails_RuleListProfile": { + "message": "(根據規則列表切換)" + }, "browserAction_titleNormal": { "message": "SwitchyOmega:: $PROFILE$", "placeholders": { diff --git a/omega-i18n/zh_TW/messages.json b/omega-i18n/zh_TW/messages.json index 6bf4c27..a111a66 100644 --- a/omega-i18n/zh_TW/messages.json +++ b/omega-i18n/zh_TW/messages.json @@ -492,6 +492,9 @@ } } }, + "options_replaceProfileSuccess": { + "message": "更改選項成功。" + }, "options_modalHeader_deleteProfile": { "message": "刪除情景模式" }, @@ -664,6 +667,21 @@ "popup_errorLog": { "message": "錯誤日誌" }, + "browserAction_profileDetails_PacProfile": { + "message": "(PAC 指令碼)" + }, + "browserAction_profileDetails_SystemProfile": { + "message": "(由其他擴展或系統環境控制)" + }, + "browserAction_profileDetails_DirectProfile": { + "message": "(不使用任何代理)" + }, + "browserAction_profileDetails_SwitchProfile": { + "message": "(根據條件切換)" + }, + "browserAction_profileDetails_RuleListProfile": { + "message": "(根據規則列表切換)" + }, "browserAction_titleNormal": { "message": "SwitchyOmega:: $PROFILE$", "placeholders": { diff --git a/omega-pac/src/profiles.coffee b/omega-pac/src/profiles.coffee index d2c2804..ae4dc21 100644 --- a/omega-pac/src/profiles.coffee +++ b/omega-pac/src/profiles.coffee @@ -139,7 +139,9 @@ module.exports = exports = handler = exports._handler(profile) cache.directReferenceSet = handler.directReferenceSet.call(exports, profile) allReferenceSet: (profile, options, opt_out) -> + o_profile = profile profile = exports.byName(profile, options) + throw new Error("Profile #{o_profile} does not exist!") if not profile? result = opt_out ? {} result[exports.nameAsKey(profile.name)] = profile.name for key, name of exports.directReferenceSet(profile) diff --git a/omega-target-chromium-extension/background.coffee b/omega-target-chromium-extension/background.coffee index 360cc6e..ef40313 100644 --- a/omega-target-chromium-extension/background.coffee +++ b/omega-target-chromium-extension/background.coffee @@ -127,10 +127,9 @@ options.setProxyNotControllable(null) timeout = null options.watchProxyChange (details) -> - lastProxyChangeAt = Date.now() + notControllableBefore = options.proxyNotControllable() + internal = false switch details['levelOfControl'] - when "controllable_by_this_extension" - break when "controlled_by_other_extensions", "not_controllable" reason = if details['levelOfControl'] == 'not_controllable' @@ -141,7 +140,9 @@ options.watchProxyChange (details) -> else options.setProxyNotControllable(null) - return if details['levelOfControl'] == 'controlled_by_this_extension' + if details['levelOfControl'] == 'controlled_by_this_extension' + internal = true + return if not notControllableBefore Log.log('external proxy: ', details) # Chromium will send chrome.proxy.settings.onChange on extension unload, @@ -151,16 +152,16 @@ options.watchProxyChange (details) -> # To workaround this issue, wait for some time before setting the proxy. # However this will cause some delay before the settings are processed. clearTimeout(timeout) if timeout? + parsed = null timeout = setTimeout (-> - options.setExternalProfile( - options.parseExternalProfile(details), - noRevert: true) + options.setExternalProfile(parsed, {noRevert: true, internal: internal}) ), 500 + + parsed = options.parseExternalProfile(details) return external = false options.currentProfileChanged = (reason) -> - profile = options.currentProfile() iconCache = {} if reason == 'external' @@ -176,19 +177,21 @@ options.currentProfileChanged = (reason) -> realCurrentName = current.defaultProfileName currentName += " [#{dispName(realCurrentName)}]" current = options.profile(realCurrentName) - title = - if profile.name == '' - details = profile.pacUrl ? options.printFixedProfile(profile) - details = details ? profile.profileType - else - chrome.i18n.getMessage('browserAction_titleNormal', [currentName]) - if external and profile.profileType != 'SystemProfile' + + details = options.printProfile(current) + if currentName + title = chrome.i18n.getMessage('browserAction_titleWithResult', [ + currentName, '', details]) + else + title = details + + if external and current.profileType != 'SystemProfile' message = chrome.i18n.getMessage('browserAction_titleExternalProxy') title = message + '\n' + title options.setBadge() tabs.resetAll( - icon: drawIcon(profile.color) + icon: drawIcon(current.color) title: title ) diff --git a/omega-target-chromium-extension/src/options.coffee b/omega-target-chromium-extension/src/options.coffee index 4aa592a..aa7d435 100644 --- a/omega-target-chromium-extension/src/options.coffee +++ b/omega-target-chromium-extension/src/options.coffee @@ -35,7 +35,7 @@ class ChromeOptions extends OmegaTarget.Options return results _proxyNotControllable: null - proxyNotControllable: => @_proxyNotControllable + proxyNotControllable: -> @_proxyNotControllable setProxyNotControllable: (reason) -> @_proxyNotControllable = reason if reason @@ -110,7 +110,8 @@ class ChromeOptions extends OmegaTarget.Options watcher(details) chrome.proxy.settings.onChange.addListener @_proxyChangeListener @_proxyChangeWatchers.push(callback) - applyProfileProxy: (profile) -> + applyProfileProxy: (profile, meta) -> + meta ?= profile if profile.profileType == 'SystemProfile' # Clear proxy settings, returning proxy control to Chromium. return proxySettings.clearAsync({}).then => @@ -135,10 +136,10 @@ class ChromeOptions extends OmegaTarget.Options data: null mandatory: true setPacScript = @pacForProfile(profile).then (script) -> - profileName = OmegaPac.PacGenerator.ascii(JSON.stringify(profile.name)) + profileName = OmegaPac.PacGenerator.ascii(JSON.stringify(meta.name)) profileName = profileName.replace(/\*/g, '\\u002a') profileName = profileName.replace(/\\/g, '\\u002f') - prefix = "/*OmegaProfile*#{profileName}*#{profile.revision}*/" + prefix = "/*OmegaProfile*#{profileName}*#{meta.revision}*/" config['pacScript'].data = prefix + script return setPacScript ?= Promise.resolve() @@ -193,8 +194,22 @@ class ChromeOptions extends OmegaTarget.Options result += "#{scheme.scheme}: #{pacResult}\n" else result += "#{pacResult}\n" + result ||= chrome.i18n.getMessage( + 'browserAction_profileDetails_DirectProfile') return result + printProfile: (profile) -> + type = profile.profileType + if type.indexOf('RuleListProfile') >= 0 + type = 'RuleListProfile' + + if type == 'FixedProfile' + @printFixedProfile(profile) + else if type == 'PacProfile' and profile.pacUrl + profile.pacUrl + else + chrome.i18n.getMessage('browserAction_profileDetails_' + type) || null + upgrade: (options, changes) -> super(options).catch (err) => if not options?['schemaVersion'] diff --git a/omega-target-chromium-extension/src/parse_external_profile.coffee b/omega-target-chromium-extension/src/parse_external_profile.coffee index a2b181f..a82cae4 100644 --- a/omega-target-chromium-extension/src/parse_external_profile.coffee +++ b/omega-target-chromium-extension/src/parse_external_profile.coffee @@ -63,7 +63,7 @@ module.exports = (details, options, fixedProfileConfig) -> proxies = {} for prop in props result = OmegaPac.Profiles.pacResult(details.value.rules[prop]) - if prop == 'singleProxy' + if prop == 'singleProxy' and details.value.rules[prop]? proxies['fallbackProxy'] = result else proxies[prop] = result diff --git a/omega-target/src/options.coffee b/omega-target/src/options.coffee index b083f97..4ae924e 100644 --- a/omega-target/src/options.coffee +++ b/omega-target/src/options.coffee @@ -86,6 +86,14 @@ class Options toString: -> "" + ###* + # Return a localized, human-readable description of the given profile. + # In base class, this method is not implemented and will always return null. + # @param {?{}} profile The profile to print + # @returns {string} Description of the profile with details + ### + printProfile: (profile) -> null + ###* # Upgrade options from previous versions. # For now, this method only supports schemaVersion 1 and 2. If so, it upgrades @@ -268,7 +276,7 @@ class Options # Get PAC script for profile. # @param {?string|Object} profile The name of the profile, or the profile. # @param {bool=false} compress Compress the script if true. - # @returns {String} The compiled + # @returns {string} The compiled ### pacForProfile: (profile, compress = false) -> ast = OmegaPac.PacGenerator.script(@_options, profile) @@ -288,11 +296,16 @@ class Options name: p.name profileType: p.profileType color: p.color + desc: @printProfile(p) builtin: if p.builtin then true if p.profileType == 'VirtualProfile' profiles[key].defaultProfileName = p.defaultProfileName if not allReferenceSet? - allReferenceSet = OmegaPac.Profiles.allReferenceSet profile, @_options + allReferenceSet = + if profile + OmegaPac.Profiles.allReferenceSet profile, @_options + else + {} if allReferenceSet[key] profiles[key].validResultProfiles = OmegaPac.Profiles.validResultProfilesFor(p, @_options) @@ -357,7 +370,7 @@ class Options @_watchingProfiles = OmegaPac.Profiles.allReferenceSet(@_tempProfile, @_options) - @applyProfileProxy(@_tempProfile) + @applyProfileProxy(@_tempProfile, profile) else @applyProfileProxy(profile) @@ -381,9 +394,10 @@ class Options # Set proxy settings based on the given profile. # In base class, this method is not implemented and will always reject. # @param {{}} profile The profile to apply + # @param {{}=profile} meta The metadata of the profile, like name and revision # @returns {Promise} A promise which is fulfilled when the proxy is set. ### - applyProfileProxy: (profile) -> + applyProfileProxy: (profile, meta) -> Promise.reject new Error('not implemented') ###* @@ -707,6 +721,8 @@ class Options # @param {{}} profile The external profile # @param {?{}} args Extra arguments # @param {boolean=false} args.noRevert If true, do not revert changes. + # @param {boolean=false} args.internal If true, treat the profile change as + # caused by the options itself instead of external reasons. # @returns {Promise} A promise which is fulfilled when the profile is set ### setExternalProfile: (profile, args) -> @@ -717,8 +733,11 @@ class Options return p = OmegaPac.Profiles.byName(profile.name, @_options) if p - @applyProfile(p.name, - {proxy: false, system: @_isSystem, reason: 'external'}) + if args?.internal + @applyProfile(p.name, {proxy: false}) + else + @applyProfile(p.name, + {proxy: false, system: @_isSystem, reason: 'external'}) else @_currentProfileName = null @_externalProfile = profile diff --git a/omega-web/src/coffee/omega_decoration.coffee b/omega-web/src/coffee/omega_decoration.coffee index 32b0232..1e45710 100644 --- a/omega-web/src/coffee/omega_decoration.coffee +++ b/omega-web/src/coffee/omega_decoration.coffee @@ -98,7 +98,7 @@ angular.module('omegaDecoration', []).value('profileIcons', { scope.profileIcon = profileIcons[profile.profileType] break scope.$watch(scope.profiles, ((profiles) -> - scope.currentProfiles = profiles + scope.currentProfiles = profiles || [] if scope.dispProfiles? scope.dispProfiles = currentProfiles updateView() diff --git a/omega-web/src/coffee/popup.coffee b/omega-web/src/coffee/popup.coffee index c9c953d..79847f0 100644 --- a/omega-web/src/coffee/popup.coffee +++ b/omega-web/src/coffee/popup.coffee @@ -9,7 +9,7 @@ module.filter 'dispName', (omegaTarget) -> omegaTarget.getMessage('profile_' + name) || name module.controller 'PopupCtrl', ($scope, $window, $q, omegaTarget, - profileIcons, profileOrder, dispNameFilter) -> + profileIcons, profileOrder, dispNameFilter, getVirtualTarget) -> refreshOnProfileChange = false refresh = -> @@ -33,6 +33,12 @@ module.controller 'PopupCtrl', ($scope, $window, $q, omegaTarget, 'glyphicon-ok' else undefined + $scope.getProfileTitle = (profile, normal) -> + desc = '' + while profile + desc = profile.desc + profile = getVirtualTarget(profile, $scope.availableProfiles) + desc || profile?.name || '' $scope.openOptions = (hash) -> omegaTarget.openOptions(hash).then -> $window.close() diff --git a/omega-web/src/popup.jade b/omega-web/src/popup.jade index 165fb3e..69f8800 100644 --- a/omega-web/src/popup.jade +++ b/omega-web/src/popup.jade @@ -26,10 +26,10 @@ html(lang='en' ng-app='omegaPopup' ng-controller='PopupCtrl' ng-csp) body(ng-class='{"with-condition-form": showConditionForm}') ul.nav.nav-pills.nav-stacked(ng-hide='showConditionForm || proxyNotControllable') li.profile(ng-repeat='profile in builtinProfiles' ng-class='{active: isActive(profile.name), "bg-info": isEffective(profile.name)}') - a(ng-click='applyProfile(profile)') + a(ng-click='applyProfile(profile)' title='{{getProfileTitle(profile)}}') span(omega-profile-inline='profile' icon='getIcon(profile)' options='availableProfiles' disp-name='dispNameFilter') li.profile.external-profile(ng-show='!!externalProfile' ng-class='{active: isActive(""), "bg-info": isEffective("")}') - a(ng-click='nameExternal.open = true') + a(ng-click='nameExternal.open = true' title='{{getProfileTitle(externalProfile)}}') form(name='nameExternalForm' ng-submit='nameExternalForm.$valid && saveExternal()') span(omega-profile-icon='externalProfile' icon='getIcon(externalProfile, "normal")' options='availableProfiles' disp-name='dispNameFilter') = ' ' @@ -39,9 +39,9 @@ html(lang='en' ng-app='omegaPopup' ng-controller='PopupCtrl' ng-csp) li.divider li.profile(ng-repeat='profile in customProfiles' ng-class='{active: isActive(profile.name), "bg-info": isEffective(profile.name)}' dropdown) - a(ng-click='applyProfile(profile)' ng-if='!profile.validResultProfiles') + a(ng-click='applyProfile(profile)' ng-if='!profile.validResultProfiles' title='{{getProfileTitle(profile)}}') span(omega-profile-inline='profile' icon='getIcon(profile)' options='availableProfiles' disp-name='dispNameFilter') - a.profile-with-default-edit(ng-click='applyProfile(profile)' ng-if='!!profile.validResultProfiles') + a.profile-with-default-edit(ng-click='applyProfile(profile)' ng-if='!!profile.validResultProfiles' title='{{getProfileTitle(profile)}}') span(omega-profile-inline='profile' icon='getIcon(profile)' options='availableProfiles' disp-name='dispNameFilter') = ' ' | [{{profile.defaultProfileName}}] @@ -49,7 +49,7 @@ html(lang='en' ng-app='omegaPopup' ng-controller='PopupCtrl' ng-csp) span.glyphicon.glyphicon-chevron-down ul.dropdown-menu(ng-if='!!profile.validResultProfiles') li(ng-repeat='p in profile.validResultProfiles' ng-class='{active: p.name == profile.defaultProfileName}') - a(ng-click='setDefaultProfile(profile.name, p.name)') + a(ng-click='setDefaultProfile(profile.name, p.name)' title='{{getProfileTitle(profile)}}') span(omega-profile-inline='p' options='availableProfiles' disp-name='dispNameFilter') li.divider(ng-show='!!currentDomain && validResultProfiles.length') li(ng-show='!!currentProfileCanAddRule') @@ -66,7 +66,7 @@ html(lang='en' ng-app='omegaPopup' ng-controller='PopupCtrl' ng-csp) ul.dropdown-menu li(ng-repeat='profile in validResultProfiles' ng-class='{active: profile.name == currentTempRuleProfile}' ng-show='!!currentTempRuleProfile || validResultProfiles.length == 1 || profile.name != currentProfileName') - a(ng-click='addTempRule(currentDomain, profile.name)') + a(ng-click='addTempRule(currentDomain, profile.name)' title='{{getProfileTitle(profile)}}') span(omega-profile-inline='profile' options='availableProfiles' disp-name='dispNameFilter') li.divider li