├── .circleci └── config.yml ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .tern-project ├── AUTHORS ├── COPYING ├── README.md ├── omega-build ├── Gruntfile.coffee └── package.json ├── omega-locales ├── ach │ └── LC_MESSAGES │ │ └── omega-web.po ├── cs │ └── LC_MESSAGES │ │ └── omega-web.po ├── de │ └── LC_MESSAGES │ │ └── omega-web.po ├── en_US │ └── LC_MESSAGES │ │ └── omega-web.po ├── es │ └── LC_MESSAGES │ │ └── omega-web.po ├── es_AR │ └── LC_MESSAGES │ │ └── omega-web.po ├── fa │ └── LC_MESSAGES │ │ └── omega-web.po ├── fr │ └── LC_MESSAGES │ │ └── omega-web.po ├── he_IL │ └── LC_MESSAGES │ │ └── omega-web.po ├── id │ └── LC_MESSAGES │ │ └── omega-web.po ├── is │ └── LC_MESSAGES │ │ └── omega-web.po ├── it │ └── LC_MESSAGES │ │ └── omega-web.po ├── ja │ └── LC_MESSAGES │ │ └── omega-web.po ├── lzh │ └── LC_MESSAGES │ │ └── omega-web.po ├── nb_NO │ └── LC_MESSAGES │ │ └── omega-web.po ├── nl │ └── LC_MESSAGES │ │ └── omega-web.po ├── pl │ └── LC_MESSAGES │ │ └── omega-web.po ├── pt │ └── LC_MESSAGES │ │ └── omega-web.po ├── pt_BR │ └── LC_MESSAGES │ │ └── omega-web.po ├── ru │ └── LC_MESSAGES │ │ └── omega-web.po ├── si │ └── LC_MESSAGES │ │ └── omega-web.po ├── sk │ └── LC_MESSAGES │ │ └── omega-web.po ├── sl │ └── LC_MESSAGES │ │ └── omega-web.po ├── tr │ └── LC_MESSAGES │ │ └── omega-web.po ├── uk │ └── LC_MESSAGES │ │ └── omega-web.po ├── zh_CN │ └── LC_MESSAGES │ │ └── omega-web.po ├── zh_Hant │ └── LC_MESSAGES │ │ └── omega-web.po └── zh_TW │ └── LC_MESSAGES │ └── omega-web.po ├── omega-pac ├── .gitattributes ├── .gitignore ├── Gruntfile.coffee ├── grunt │ ├── aliases.coffee │ ├── browserify.coffee │ ├── coffeelint.coffee │ ├── mochaTest.coffee │ └── watch.coffee ├── index.coffee ├── package.json ├── src │ ├── conditions.coffee │ ├── pac_generator.coffee │ ├── profiles.coffee │ ├── rule_list.coffee │ ├── shexp_utils.coffee │ └── utils.coffee ├── test │ ├── conditions.coffee │ ├── pac_generator.coffee │ ├── profiles.coffee │ ├── rule_list.coffee │ ├── shexp_utils.coffee │ └── utils.coffee ├── uglifyjs-shim.js └── uglifyjs.js ├── omega-target-chromium-extension ├── .gitignore ├── Gruntfile.coffee ├── grunt-po2crx.coffee ├── grunt │ ├── aliases.coffee │ ├── browserify.coffee │ ├── coffee.coffee │ ├── coffeelint.coffee │ ├── compress.coffee │ ├── copy.coffee │ ├── mochaTest.coffee │ ├── po2crx.coffee │ └── watch.coffee ├── index.coffee ├── omega_target_shim.js ├── overlay │ ├── background.html │ └── manifest.json ├── package.json └── src │ ├── coffee │ ├── background.coffee │ ├── background_preload.coffee │ ├── omega_debug.coffee │ └── omega_target_web.coffee │ ├── js │ ├── omega_target_popup.js │ └── omega_webext_proxy_script.js │ └── module │ ├── chrome_api.coffee │ ├── chrome_port.coffee │ ├── external_api.coffee │ ├── fetch_url.coffee │ ├── index.coffee │ ├── inspect.coffee │ ├── options.coffee │ ├── proxy │ ├── index.coffee │ ├── proxy_auth.coffee │ ├── proxy_impl.coffee │ ├── proxy_impl_listener.coffee │ ├── proxy_impl_script.coffee │ └── proxy_impl_settings.coffee │ ├── storage.coffee │ ├── switchysharp.coffee │ ├── tabs.coffee │ ├── upgrade.coffee │ └── web_request_monitor.coffee ├── omega-target ├── .gitignore ├── Gruntfile.coffee ├── grunt │ ├── aliases.coffee │ ├── browserify.coffee │ ├── coffeelint.coffee │ ├── mochaTest.coffee │ └── watch.coffee ├── index.coffee ├── omega_pac_shim.js ├── package.json ├── src │ ├── browser_storage.coffee │ ├── default_options.coffee │ ├── errors.coffee │ ├── log.coffee │ ├── options.coffee │ ├── options_sync.coffee │ ├── storage.coffee │ └── utils.coffee └── test │ └── options_sync.coffee └── omega-web ├── .gitattributes ├── .gitignore ├── Gruntfile.coffee ├── bower.json ├── grunt ├── aliases.coffee ├── autoprefixer.coffee ├── bower.coffee ├── coffee.coffee ├── coffeelint.coffee ├── copy.coffee ├── jade.coffee ├── less.coffee ├── mochaTest.coffee ├── ngAnnotate.coffee └── watch.coffee ├── img └── icons │ ├── draw_omega.js │ ├── omega-128.png │ ├── omega-128.svg │ ├── omega-48.png │ ├── omega-64.png │ ├── omega-action-16.png │ ├── omega-action-19.png │ ├── omega-action-24.png │ ├── omega-action-32.png │ └── omega-action.svg ├── lib └── jquery-ui-1.10.4.custom.min.js ├── package.json └── src ├── coffee ├── log_error.coffee ├── omega_decoration.coffee ├── options.coffee ├── options_guide.coffee ├── popup.coffee └── switch_profile_guide.coffee ├── less ├── common.less ├── options.less └── popup.less ├── omega ├── app.coffee ├── controllers │ ├── about.coffee │ ├── fixed_profile.coffee │ ├── io.coffee │ ├── master.coffee │ ├── pac_profile.coffee │ ├── profile.coffee │ ├── quick_switch.coffee │ ├── rule_list_profile.coffee │ └── switch_profile.coffee ├── directives.coffee └── filters.coffee ├── options.jade ├── partials ├── about.jade ├── apply_options_confirm.jade ├── cannot_delete_profile.jade ├── delete_attached.jade ├── delete_profile.jade ├── fixed_auth_edit.jade ├── general.jade ├── input_group_clear.jade ├── io.jade ├── new_profile.jade ├── omega_profile_select.jade ├── options_welcome.jade ├── profile.jade ├── profile_fixed.jade ├── profile_pac.jade ├── profile_rule_list.jade ├── profile_switch.jade ├── profile_unsupported.jade ├── profile_virtual.jade ├── rename_profile.jade ├── replace_profile.jade ├── reset_options_confirm.jade ├── rule_remove_confirm.jade ├── rule_reset_confirm.jade └── ui.jade ├── popup.jade └── popup ├── css ├── dialog.css └── index.css ├── index.html ├── js ├── i18n.js ├── index.js ├── keyboard.js ├── keyboard_help.js ├── loader.js ├── profiles.js └── proxy_not_controllable.js └── proxy_not_controllable.html /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:7.10 6 | 7 | working_directory: ~/repo 8 | 9 | steps: 10 | - checkout 11 | 12 | - restore_cache: 13 | keys: 14 | - v2-dependencies-build-{{ checksum "omega-build/package.json" }} 15 | - v2-dependencies-build 16 | - restore_cache: 17 | keys: 18 | - v2-dependencies-pac-{{ checksum "omega-pac/package.json" }} 19 | - v2-dependencies-pac 20 | - restore_cache: 21 | keys: 22 | - v2-dependencies-target-{{ checksum "omega-target/package.json" }} 23 | - v2-dependencies-target 24 | - restore_cache: 25 | keys: 26 | - v2-dependencies-web-{{ checksum "omega-web/package.json" }} 27 | - v2-dependencies-web 28 | - restore_cache: 29 | keys: 30 | - v2-dependencies-webbower-{{ checksum "omega-web/bower.json" }} 31 | - v2-dependencies-webbower 32 | - restore_cache: 33 | keys: 34 | - v2-dependencies-targetchromium-{{ checksum "omega-target-chromium-extension/package.json" }} 35 | - v2-dependencies-targetchromium 36 | 37 | - run: sudo npm install -g grunt-cli@1.2.0 bower web-ext json 38 | - run: (cd omega-build && npm run deps) 39 | 40 | - save_cache: 41 | paths: 42 | - omega-build/node_modules 43 | key: v2-dependencies-build-{{ checksum "omega-build/package.json" }} 44 | - save_cache: 45 | paths: 46 | - omega-pac/node_modules 47 | key: v2-dependencies-pac-{{ checksum "omega-pac/package.json" }} 48 | - save_cache: 49 | paths: 50 | - omega-web/node_modules 51 | key: v2-dependencies-web-{{ checksum "omega-web/package.json" }} 52 | - save_cache: 53 | paths: 54 | - omega-web/bower_components 55 | key: v2-dependencies-webbower-{{ checksum "omega-web/bower.json" }} 56 | - save_cache: 57 | paths: 58 | - omega-target-chromium-extension/node_modules 59 | key: v2-dependencies-targetchromium-{{ checksum "omega-target-chromium-extension/package.json" }} 60 | 61 | - run: (cd omega-build && sudo npm run dev) 62 | - run: (cd omega-pac && npm test) 63 | - run: (cd omega-target && npm test) 64 | - run: (cd omega-build && grunt) 65 | 66 | - run: 67 | name: Prepare for package builds 68 | command: | 69 | cd omega-target-chromium-extension 70 | mkdir packages 71 | base_ver=$(json -f "overlay/manifest.json" version) 72 | commit_rev=$(git rev-parse --short HEAD) 73 | FULL_VER="${base_ver}-${CIRCLE_BUILD_NUM}.ci.${commit_rev}" 74 | echo "export FULL_VER='$FULL_VER'" >> $BASH_ENV 75 | cd .. 76 | 77 | - run: 78 | name: Build unsigned ZIP package for Chromium 79 | command: | 80 | cd omega-target-chromium-extension 81 | grunt release 82 | dest_file="packages/SwitchyOmega_${FULL_VER}_Chromium_UNSIGNED.zip" 83 | mv release.zip "$dest_file" 84 | cd .. 85 | 86 | - run: (cd omega-target-chromium-extension && web-ext -s build lint) 87 | 88 | - run: 89 | name: Build unsigned ZIP package for Firefox 90 | command: | 91 | cd omega-target-chromium-extension 92 | web-ext -s build build 93 | dest_file="packages/SwitchyOmega_${FULL_VER}_Firefox_UNSIGNED.xpi" 94 | mv web-ext-artifacts/*.zip "$dest_file" 95 | cd .. 96 | - store_artifacts: 97 | path: omega-target-chromium-extension/packages 98 | destination: packages 99 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 25 | 26 | ### SwitchyOmega version / SwitchyOmega 版本 27 | 28 | 29 | ### Browser version & OS version / 浏览器名称、版本及操作系统版本 30 | 31 | 32 | ### Problem description / 问题描述 33 | 34 | (Please provide as much detail as possible. We recommend the following format.) 35 | (请尽可能多提供一些细节。我们推荐使用下面的格式。) 36 | 37 | #### Steps to reproduce issue / 重现错误所需步骤 38 | 39 | (What did you do? / 你做了什么?) 40 | 41 | 1. 42 | 2. 43 | 3. 44 | 45 | #### Expected behavior / 期望发生的情况 46 | 47 | 48 | #### Actual (or suggested) behavior / 实际发生的情况(或建议修改后的行为) 49 | 50 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### What does this PR do? 2 | - [ ] Bug fix 3 | - [ ] Improvement 4 | - [ ] New feature 5 | 6 | (Note: Translations and typo fixes should be done on https://hosted.weblate.org/projects/switchyomega/ instead of PR.) 7 | 8 | Please explain the changes in details here... 9 | 10 | #### Compatibility 11 | 12 | Is this PR compatible with old versions? Can users simply upgrade the extension? 13 | Please describe any possible breaking changes (or surprising UX differences). 14 | 15 | #### Screenshots (if applicable) 16 | 17 | --- 18 | 19 | After creating the PR: 20 | 21 | - Please make sure the CircleCI test passes. Feel free to add more commits for 22 | bug or style fixes. 23 | - Any merge conflicts should be fixed on *your* side. Prefer rebasing to merging. 24 | - Allow some time for project maintainers to review and merge the change. 25 | - New features & behavior changes are subject to discussion. Please understand 26 | that project maintainers may reject new features, or request changes. 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | -------------------------------------------------------------------------------- /.tern-project: -------------------------------------------------------------------------------- 1 | { 2 | "libs": [ 3 | "chai" 4 | ], 5 | "plugins": { 6 | "node": {}, 7 | "coffee": {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # SwitchyOmega authors: 2 | 3 | FelisCatus 4 | 5 | # SwitchyOmega translators: 6 | 7 | Filip 8 | Michal Čihař 9 | masoud Rahmani 10 | 11 | # SwitchyOmega includes or links to (unchanged): 12 | # * jQuery UI (custom build) 13 | # => omega-web/lib/jquery-ui-*.js 14 | # => Copyright 2014 jQuery Foundation and other contributors; Licensed MIT 15 | # * Many npm packages and bower packages 16 | # => **/node_modules, **/bower_components 17 | # => Please refer to their project homepages or npm package pages for the 18 | # copyright and license information of each package. 19 | -------------------------------------------------------------------------------- /omega-build/Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (grunt) -> 2 | submodules = ['omega-pac', 'omega-target', 'omega-web', 'omega-target-*'] 3 | hubConfig = 4 | all: 5 | options: 6 | concurrent: Infinity 7 | src: "../*/Gruntfile.*" 8 | for module in submodules 9 | hubConfig[module] = 10 | src: "../#{module}/Gruntfile.*" 11 | 12 | hubAll = (task) -> "hub:#{module}:#{task}" for module in submodules 13 | 14 | grunt.initConfig { 15 | hub: hubConfig 16 | } 17 | 18 | grunt.loadNpmTasks 'grunt-hub' 19 | 20 | grunt.registerTask 'default', hubAll('default') 21 | grunt.registerTask 'test', hubAll('test') 22 | grunt.registerTask 'watch', ['hub:all:watch'] 23 | -------------------------------------------------------------------------------- /omega-build/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "omega-build", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "grunt": "~0.4.1", 7 | "grunt-hub": "^0.7.0" 8 | }, 9 | "scripts": { 10 | "deps": "npm install && (cd ../omega-pac && npm install) && (cd ../omega-target && npm install) && (cd ../omega-web && npm install && bower install) && (cd ../omega-target-chromium-extension/ && npm install)", 11 | "dev": "(cd ../omega-pac && npm run dev) && (cd ../omega-target && npm run dev) && (cd ../omega-web && npm run dev) && (cd ../omega-target-chromium-extension/ && npm run dev)" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /omega-pac/.gitattributes: -------------------------------------------------------------------------------- 1 | uglifyjs.js linguist-vendored 2 | -------------------------------------------------------------------------------- /omega-pac/.gitignore: -------------------------------------------------------------------------------- 1 | /index.js 2 | /omega_pac.min.js 3 | -------------------------------------------------------------------------------- /omega-pac/Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = require('load-grunt-config') 2 | -------------------------------------------------------------------------------- /omega-pac/grunt/aliases.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | default: [ 3 | 'coffeelint' 4 | 'browserify' 5 | ] 6 | test: ['mochaTest'] 7 | -------------------------------------------------------------------------------- /omega-pac/grunt/browserify.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | index: 3 | files: 4 | 'index.js': 'index.coffee' 5 | options: 6 | transform: ['coffeeify'] 7 | exclude: ['uglify-js', 'ip-address'] 8 | browserifyOptions: 9 | extensions: '.coffee' 10 | builtins: [] 11 | standalone: 'index.coffee' 12 | debug: true 13 | browser: 14 | files: 15 | 'omega_pac.min.js': './index.coffee' 16 | options: 17 | alias: [ 18 | './index.coffee:OmegaPac' 19 | ] 20 | transform: ['coffeeify'] 21 | plugin: 22 | if process.env.BUILD == 'release' 23 | [['minifyify', {map: false}]] 24 | else 25 | [] 26 | browserifyOptions: 27 | extensions: '.coffee' 28 | standalone: 'OmegaPac' 29 | -------------------------------------------------------------------------------- /omega-pac/grunt/coffeelint.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | options: 3 | arrow_spacing: level: 'error' 4 | colon_assignment_spacing: 5 | level: 'error' 6 | spacing: 7 | left: 0 8 | right: 1 9 | missing_fat_arrows: level: 'warn' 10 | no_empty_functions: level: 'error' 11 | no_empty_param_list: level: 'error' 12 | no_interpolation_in_single_quotes: level: 'error' 13 | no_stand_alone_at: level: 'error' 14 | space_operators: level: 'error' 15 | # https://github.com/clutchski/coffeelint/issues/525 16 | indentation: level: 'ignore' 17 | 18 | gruntfile: ['Gruntfile.coffee'] 19 | tasks: ['grunt/**/*.coffee'] 20 | src: ['src/**/*.coffee', 'test/**/*.coffee'] 21 | -------------------------------------------------------------------------------- /omega-pac/grunt/mochaTest.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | test: 3 | options: 4 | reporter: 'spec' 5 | require: 'coffee-script/register' 6 | src: ['test/**/*.coffee'] 7 | -------------------------------------------------------------------------------- /omega-pac/grunt/watch.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | grunt: 3 | options: 4 | reload: true 5 | files: 6 | 'grunt/*' 7 | tasks: ['coffeelint:tasks', 'default'] 8 | src: 9 | files: ['src/**/*.coffee', 'test/**/*.coffee'] 10 | tasks: ['default'] 11 | -------------------------------------------------------------------------------- /omega-pac/index.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | Conditions: require('./src/conditions') 3 | PacGenerator: require('./src/pac_generator') 4 | Profiles: require('./src/profiles') 5 | RuleList: require('./src/rule_list') 6 | ShexpUtils: require('./src/shexp_utils') 7 | 8 | for name, value of require('./src/utils.coffee') 9 | module.exports[name] = value 10 | -------------------------------------------------------------------------------- /omega-pac/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "omega-pac", 3 | "version": "0.0.1", 4 | "private": true, 5 | "main": "./index.js", 6 | "devDependencies": { 7 | "chai": "~1.9.1", 8 | "coffee-script": "^1.7.1", 9 | "coffeeify": "^0.7.0", 10 | "coffeelint": "^1.16.0", 11 | "grunt": "^0.4.5", 12 | "grunt-browserify": "^3.0.0", 13 | "grunt-coffeelint": "^0.0.13", 14 | "grunt-contrib-coffee": "^0.11.1", 15 | "grunt-contrib-watch": "^0.6.1", 16 | "grunt-mocha-test": "~0.11.0", 17 | "load-grunt-config": "^0.13.1", 18 | "lolex": "^1.4.0", 19 | "minifyify": "^4.1.1" 20 | }, 21 | "dependencies": { 22 | "ip-address": "^4.0.0", 23 | "tldjs": "^1.5.2", 24 | "uglify-js": "^2.4.15" 25 | }, 26 | "browser": { 27 | "uglify-js": "./uglifyjs-shim.js", 28 | "uglify-js-real": "./uglifyjs.js" 29 | }, 30 | "scripts": { 31 | "dev": "npm link", 32 | "test": "TZ=Europe/London grunt test" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /omega-pac/src/pac_generator.coffee: -------------------------------------------------------------------------------- 1 | U2 = require 'uglify-js' 2 | Profiles = require './profiles' 3 | 4 | # PacGenerator is used like a singleton class instance. 5 | # coffeelint: disable=missing_fat_arrows 6 | module.exports = 7 | ascii: (str) -> 8 | str.replace /[\u0080-\uffff]/g, (char) -> 9 | hex = char.charCodeAt(0).toString(16) 10 | result = '\\u' 11 | result += '0' for _ in [hex.length...4] 12 | result += hex 13 | return result 14 | 15 | compress: (ast) -> 16 | ast.figure_out_scope() 17 | compressor = U2.Compressor(warnings: false, keep_fargs: true, 18 | if_return: false) 19 | compressed_ast = ast.transform(compressor) 20 | compressed_ast.figure_out_scope() 21 | compressed_ast.compute_char_frequency() 22 | compressed_ast.mangle_names() 23 | compressed_ast 24 | 25 | script: (options, profile, args) -> 26 | if typeof profile == 'string' 27 | profile = Profiles.byName(profile, options) 28 | refs = Profiles.allReferenceSet(profile, options, 29 | profileNotFound: args?.profileNotFound) 30 | 31 | profiles = new U2.AST_Object properties: 32 | for key, name of refs when key != '+direct' 33 | p = if typeof profile == 'object' and profile.name == name 34 | profile 35 | else 36 | Profiles.byName(name, options) 37 | if not p? 38 | p = Profiles.profileNotFound(name, args?.profileNotFound) 39 | new U2.AST_ObjectKeyVal(key: key, value: Profiles.compile(p)) 40 | 41 | factory = new U2.AST_Function( 42 | argnames: [ 43 | new U2.AST_SymbolFunarg name: 'init' 44 | new U2.AST_SymbolFunarg name: 'profiles' 45 | ] 46 | body: [new U2.AST_Return value: new U2.AST_Function( 47 | argnames: [ 48 | new U2.AST_SymbolFunarg name: 'url' 49 | new U2.AST_SymbolFunarg name: 'host' 50 | ] 51 | body: [ 52 | new U2.AST_Directive value: 'use strict' 53 | new U2.AST_Var definitions: [ 54 | new U2.AST_VarDef name: new U2.AST_SymbolVar(name: 'result'), value: 55 | new U2.AST_SymbolRef name: 'init' 56 | new U2.AST_VarDef name: new U2.AST_SymbolVar(name: 'scheme'), value: 57 | new U2.AST_Call( 58 | expression: new U2.AST_Dot( 59 | expression: new U2.AST_SymbolRef name: 'url' 60 | property: 'substr' 61 | ) 62 | args: [ 63 | new U2.AST_Number value: 0 64 | new U2.AST_Call( 65 | expression: new U2.AST_Dot( 66 | expression: new U2.AST_SymbolRef name: 'url' 67 | property: 'indexOf' 68 | ) 69 | args: [new U2.AST_String value: ':'] 70 | ) 71 | ] 72 | ) 73 | ] 74 | new U2.AST_Do( 75 | body: new U2.AST_BlockStatement body: [ 76 | new U2.AST_SimpleStatement body: new U2.AST_Assign( 77 | left: new U2.AST_SymbolRef name: 'result' 78 | operator: '=' 79 | right: new U2.AST_Sub( 80 | expression: new U2.AST_SymbolRef name: 'profiles' 81 | property: new U2.AST_SymbolRef name: 'result' 82 | ) 83 | ) 84 | new U2.AST_If( 85 | condition: new U2.AST_Binary( 86 | left: new U2.AST_UnaryPrefix( 87 | operator: 'typeof' 88 | expression: new U2.AST_SymbolRef name: 'result' 89 | ) 90 | operator: '===' 91 | right: new U2.AST_String value: 'function' 92 | ) 93 | body: new U2.AST_SimpleStatement body: new U2.AST_Assign( 94 | left: new U2.AST_SymbolRef name: 'result' 95 | operator: '=' 96 | right: new U2.AST_Call( 97 | expression: new U2.AST_SymbolRef name: 'result' 98 | args: [ 99 | new U2.AST_SymbolRef name: 'url' 100 | new U2.AST_SymbolRef name: 'host' 101 | new U2.AST_SymbolRef name: 'scheme' 102 | ] 103 | ) 104 | ) 105 | ) 106 | ] 107 | condition: new U2.AST_Binary( 108 | left: new U2.AST_Binary( 109 | left: new U2.AST_UnaryPrefix( 110 | operator: 'typeof' 111 | expression: new U2.AST_SymbolRef name: 'result' 112 | ) 113 | operator: '!==' 114 | right: new U2.AST_String value: 'string' 115 | ) 116 | operator: '||' 117 | right: new U2.AST_Binary( 118 | left: new U2.AST_Call( 119 | expression: new U2.AST_Dot( 120 | expression: new U2.AST_SymbolRef name: 'result' 121 | property: 'charCodeAt' 122 | ) 123 | args: [new U2.AST_Number(value: 0)] 124 | ) 125 | operator: '===' 126 | right: new U2.AST_Number value: '+'.charCodeAt(0) 127 | ) 128 | ) 129 | ) 130 | new U2.AST_Return value: new U2.AST_SymbolRef name: 'result' 131 | ] 132 | )] 133 | ) 134 | new U2.AST_Toplevel body: [new U2.AST_Var definitions: [ 135 | new U2.AST_VarDef( 136 | name: new U2.AST_SymbolVar name: 'FindProxyForURL' 137 | value: new U2.AST_Call( 138 | expression: factory 139 | args: [ 140 | Profiles.profileResult profile.name 141 | profiles 142 | ] 143 | ) 144 | ) 145 | ]] 146 | # coffeelint: enable=missing_fat_arrows 147 | -------------------------------------------------------------------------------- /omega-pac/src/shexp_utils.coffee: -------------------------------------------------------------------------------- 1 | module.exports = exports = 2 | regExpMetaChars: do -> 3 | chars = '''\\[\^$.|?*+(){}/''' 4 | set = {} 5 | for i in [0...chars.length] 6 | set[chars.charCodeAt(i)] = true 7 | set 8 | escapeSlash: (pattern) -> 9 | charCodeSlash = 47 # / 10 | charCodeBackSlash = 92 # \ 11 | escaped = false 12 | start = 0 13 | result = '' 14 | for i in [0...pattern.length] 15 | code = pattern.charCodeAt(i) 16 | if code == charCodeSlash and not escaped 17 | result += pattern.substring start, i 18 | result += '\\' 19 | start = i 20 | escaped = (code == charCodeBackSlash and not escaped) 21 | result += pattern.substr start 22 | shExp2RegExp: (pattern, options) -> 23 | trimAsterisk = options?.trimAsterisk || false 24 | start = 0 25 | end = pattern.length 26 | charCodeAsterisk = 42 # '*' 27 | charCodeQuestion = 63 # '?' 28 | if trimAsterisk 29 | while start < end && pattern.charCodeAt(start) == charCodeAsterisk 30 | start++ 31 | while start < end && pattern.charCodeAt(end - 1) == charCodeAsterisk 32 | end-- 33 | if end - start == 1 && pattern.charCodeAt(start) == charCodeAsterisk 34 | return '' 35 | regex = '' 36 | if start == 0 37 | regex += '^' 38 | for i in [start...end] 39 | code = pattern.charCodeAt(i) 40 | switch code 41 | when charCodeAsterisk then regex += '.*' 42 | when charCodeQuestion then regex += '.' 43 | else 44 | if exports.regExpMetaChars[code] >= 0 45 | regex += '\\' 46 | regex += pattern[i] 47 | 48 | if end == pattern.length 49 | regex += '$' 50 | 51 | return regex 52 | -------------------------------------------------------------------------------- /omega-pac/src/utils.coffee: -------------------------------------------------------------------------------- 1 | Revision = 2 | fromTime: (time) -> 3 | time = if time then new Date(time) else new Date() 4 | return time.getTime().toString(16) 5 | compare: (a, b) -> 6 | return 0 if not a and not b 7 | return -1 if not a 8 | return 1 if not b 9 | return 1 if a.length > b.length 10 | return -1 if a.length < b.length 11 | return 1 if a > b 12 | return -1 if a < b 13 | return 0 14 | 15 | exports.Revision = Revision 16 | 17 | class AttachedCache 18 | constructor: (opt_prop, @tag) -> 19 | @prop = opt_prop 20 | if typeof @tag == 'undefined' 21 | @tag = opt_prop 22 | @prop = '_cache' 23 | get: (obj, otherwise) -> 24 | tag = @tag(obj) 25 | cache = @_getCache(obj) 26 | if cache? and cache.tag == tag 27 | return cache.value 28 | value = if typeof otherwise == 'function' then otherwise() else otherwise 29 | @_setCache(obj, {tag: tag, value: value}) 30 | return value 31 | drop: (obj) -> 32 | if obj[@prop]? 33 | obj[@prop] = undefined 34 | _getCache: (obj) -> obj[@prop] 35 | _setCache: (obj, value) -> 36 | if not Object::hasOwnProperty.call obj, @prop 37 | Object.defineProperty obj, @prop, writable: true 38 | obj[@prop] = value 39 | 40 | exports.AttachedCache = AttachedCache 41 | 42 | tld = require('tldjs') 43 | 44 | exports.isIp = (domain) -> 45 | return true if domain.indexOf(':') > 0 # IPv6 46 | lastCharCode = domain.charCodeAt(domain.length - 1) 47 | return true if 48 <= lastCharCode <= 57 # IP address ending with number. 48 | return false 49 | 50 | exports.getBaseDomain = (domain) -> 51 | return domain if exports.isIp(domain) 52 | return tld.getDomain(domain) ? domain 53 | 54 | exports.wildcardForDomain = (domain) -> 55 | return domain if exports.isIp(domain) 56 | return '*.' + exports.getBaseDomain(domain) 57 | 58 | Url = require('url') 59 | exports.wildcardForUrl = (url) -> 60 | domain = Url.parse(url).hostname 61 | return exports.wildcardForDomain(domain) 62 | -------------------------------------------------------------------------------- /omega-pac/test/pac_generator.coffee: -------------------------------------------------------------------------------- 1 | chai = require 'chai' 2 | should = chai.should() 3 | 4 | describe 'PacGenerator', -> 5 | PacGenerator = require '../src/pac_generator.coffee' 6 | 7 | options = 8 | '+auto': 9 | name: 'auto' 10 | profileType: 'SwitchProfile' 11 | revision: 'test' 12 | defaultProfileName: 'direct' 13 | rules: [ 14 | {profileName: 'proxy', condition: 15 | conditionType: 'UrlRegexCondition' 16 | pattern: '^http://(www|www2)\\.example\\.com/' 17 | } 18 | {profileName: 'direct', condition: 19 | conditionType: 'HostLevelsCondition' 20 | minValue: 3 21 | maxValue: 8 22 | } 23 | { 24 | profileName: 'proxy' 25 | condition: {conditionType: 'KeywordCondition', pattern: 'keyword'} 26 | } 27 | {profileName: 'proxy', condition: 28 | conditionType: 'UrlWildcardCondition' 29 | pattern: 'https://ssl.example.com/*' 30 | } 31 | ] 32 | '+proxy': 33 | name: 'proxy' 34 | profileType: 'FixedProfile' 35 | revision: 'test' 36 | fallbackProxy: {scheme: 'http', host: '127.0.0.1', port: 8888} 37 | bypassList: [ 38 | {conditionType: 'BypassCondition', pattern: '127.0.0.1:8080'} 39 | {conditionType: 'BypassCondition', pattern: '127.0.0.1'} 40 | {conditionType: 'BypassCondition', pattern: ''} 41 | ] 42 | 43 | it 'should generate pac scripts from options', -> 44 | ast = PacGenerator.script(options, 'auto') 45 | pac = ast.print_to_string(beautify: true, comments: true) 46 | pac.should.not.be.empty 47 | func = eval("(function () { #{pac}\n return FindProxyForURL; })()") 48 | result = func('http://www.example.com/', 'www.example.com') 49 | result.should.equal('PROXY 127.0.0.1:8888') 50 | it 'should be able to compress pac scripts', -> 51 | ast = PacGenerator.script(options, 'auto') 52 | pac = PacGenerator.compress(ast).print_to_string() 53 | pac.should.not.be.empty 54 | func = eval("(function () { #{pac}\n return FindProxyForURL; })()") 55 | result = func('http://www.example.com/', 'www.example.com') 56 | result.should.equal('PROXY 127.0.0.1:8888') 57 | -------------------------------------------------------------------------------- /omega-pac/test/shexp_utils.coffee: -------------------------------------------------------------------------------- 1 | chai = require 'chai' 2 | should = chai.should() 3 | 4 | describe 'ShexpUtils', -> 5 | ShexpUtils = require '../src/shexp_utils' 6 | describe '#escapeSlash', -> 7 | it 'should escape all forward slashes', -> 8 | regex = ShexpUtils.escapeSlash '/test/' 9 | regex.should.equal '\\/test\\/' 10 | it 'should not escape slashes that are already escaped', -> 11 | regex = ShexpUtils.escapeSlash '\\/test\\/' 12 | regex.should.equal '\\/test\\/' 13 | it 'should know the difference between escaped and unescaped slashes', -> 14 | regex = ShexpUtils.escapeSlash '\\\\/\\/test\\/' 15 | regex.should.equal '\\\\\\/\\/test\\/' 16 | describe '#shExp2RegExp', -> 17 | it 'should escape regex meta chars and back slashes', -> 18 | regex = ShexpUtils.shExp2RegExp 'this.is|a\\test+' 19 | regex.should.equal '^this\\.is\\|a\\\\test\\+$' 20 | -------------------------------------------------------------------------------- /omega-pac/test/utils.coffee: -------------------------------------------------------------------------------- 1 | chai = require 'chai' 2 | should = chai.should() 3 | Utils = require '../src/utils' 4 | 5 | describe 'getBaseDomain', -> 6 | {getBaseDomain} = Utils 7 | it 'should return domains with zero level unchanged', -> 8 | getBaseDomain('someinternaldomain').should.equal('someinternaldomain') 9 | it 'should return domains with one level unchanged', -> 10 | getBaseDomain('example.com').should.equal('example.com') 11 | getBaseDomain('e.test').should.equal('e.test') 12 | getBaseDomain('a.b').should.equal('a.b') 13 | it 'should treat two-segment TLD as one component', -> 14 | getBaseDomain('images.google.co.uk').should.equal('google.co.uk') 15 | getBaseDomain('images.google.co.jp').should.equal('google.co.jp') 16 | getBaseDomain('example.com.cn').should.equal('example.com.cn') 17 | it 'should not mistake short domains with two-segment TLDs', -> 18 | getBaseDomain('a.bc.com').should.equal('bc.com') 19 | getBaseDomain('i.t.co').should.equal('t.co') 20 | it 'should not try to modify IP address literals', -> 21 | getBaseDomain('127.0.0.1').should.equal('127.0.0.1') 22 | getBaseDomain('[::1]').should.equal('[::1]') 23 | getBaseDomain('::f').should.equal('::f') 24 | -------------------------------------------------------------------------------- /omega-pac/uglifyjs-shim.js: -------------------------------------------------------------------------------- 1 | require('uglify-js-real'); 2 | module.exports = UglifyJS; 3 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/.gitignore: -------------------------------------------------------------------------------- 1 | /index.js 2 | /omega_target_*.min.js 3 | 4 | /tmp 5 | /build 6 | /release.zip 7 | /web-ext-artifacts 8 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (grunt) -> 2 | require('load-grunt-config')(grunt) 3 | require('./grunt-po2crx')(grunt) 4 | 5 | grunt.registerTask 'chromium-manifest', -> 6 | manifest = grunt.file.readJSON('overlay/manifest.json') 7 | manifest.permissions = manifest.permissions.filter (p) -> p != 'downloads' 8 | grunt.file.write('tmp/manifest.json', JSON.stringify(manifest)) 9 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/grunt-po2crx.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (grunt) -> 2 | taskDesc = 'Convert gettext PO files to Chromium Extension messages format.' 3 | # coffeelint: disable=missing_fat_arrows 4 | grunt.registerMultiTask 'po2crx', taskDesc, -> 5 | for f in this.files 6 | result = {} 7 | for src in f.src 8 | json = require('po2json').parseFileSync(src) 9 | for own key, value of json when key 10 | message = value[1] 11 | refs = [] 12 | matchCount = 0 13 | message = message.replace /\$(\d+:)?(\w+)\$/g, (_, order, ref) -> 14 | matchCount++ 15 | if order 16 | order = parseInt(order) 17 | else 18 | order = matchCount 19 | ### TODO(catus): Shall we enable this warning? 20 | if matchCount > 1 21 | grunt.log.writeln("In this message: #{key}=#{message}") 22 | grunt.log.writeln( 23 | 'Order not specified for two or more refs in same message.') 24 | ### 25 | refs[order] = ref 26 | return '$' + ref + '$' 27 | 28 | if not matchCount 29 | placeholders = undefined 30 | else 31 | placeholders = {} 32 | for i in [0...refs.length] 33 | placeholder = refs[i] ? ('_unused_' + i) 34 | placeholders[placeholder] = {content: '$' + i} 35 | if message == ' ' 36 | message = '' 37 | result[key] = 38 | message: message 39 | placeholders: placeholders 40 | 41 | grunt.file.write(f.dest, JSON.stringify(result)) 42 | grunt.log.writeln("File \"#{f.dest}\" created.") 43 | # coffeelint: enable=missing_fat_arrows 44 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/grunt/aliases.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | default: [ 3 | 'coffeelint' 4 | 'browserify' 5 | 'coffee' 6 | 'copy' 7 | 'po2crx' 8 | ] 9 | test: ['mochaTest'] 10 | release: ['default', 'chromium-manifest', 'compress'] 11 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/grunt/browserify.coffee: -------------------------------------------------------------------------------- 1 | path = require('path') 2 | module.exports = 3 | index: 4 | files: 5 | 'index.js': 'index.coffee' 6 | options: 7 | transform: ['coffeeify'] 8 | exclude: ['bluebird', 'omega-pac', 'omega-target'] 9 | browserifyOptions: 10 | extensions: '.coffee' 11 | builtins: [] 12 | standalone: 'index.coffee' 13 | debug: true 14 | browser: 15 | files: 16 | 'omega_target_chromium_extension.min.js': 'index.coffee' 17 | options: 18 | alias: [ 19 | './index.coffee:OmegaTargetChromium' 20 | ] 21 | transform: ['coffeeify'] 22 | plugin: 23 | if process.env.BUILD == 'release' 24 | [['minifyify', {map: false}]] 25 | else 26 | [] 27 | browserifyOptions: 28 | extensions: '.coffee' 29 | standalone: 'OmegaTargetChromium' 30 | omega_webext_proxy_script: 31 | files: 32 | 'build/js/omega_webext_proxy_script.min.js': 33 | 'src/js/omega_webext_proxy_script.js' 34 | options: 35 | alias: 36 | 'omega-pac': 'omega-pac/omega_pac.min.js' 37 | plugin: 38 | if process.env.BUILD == 'release' 39 | [['minifyify', {map: false}]] 40 | else 41 | [] 42 | browserifyOptions: 43 | noParse: [require.resolve('omega-pac/omega_pac.min.js')] 44 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/grunt/coffee.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | coffee: 3 | expand: true 4 | cwd: 'src/coffee' 5 | src: ['**/*.coffee'] 6 | dest: 'build/js/' 7 | ext: '.js' 8 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/grunt/coffeelint.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | options: 3 | arrow_spacing: level: 'error' 4 | colon_assignment_spacing: 5 | level: 'error' 6 | spacing: 7 | left: 0 8 | right: 1 9 | missing_fat_arrows: level: 'warn' 10 | no_empty_functions: level: 'error' 11 | no_empty_param_list: level: 'error' 12 | no_interpolation_in_single_quotes: level: 'error' 13 | no_stand_alone_at: level: 'error' 14 | space_operators: level: 'error' 15 | # https://github.com/clutchski/coffeelint/issues/525 16 | indentation: level: 'ignore' 17 | 18 | gruntfile: ['Gruntfile.coffee'] 19 | tasks: ['grunt/**/*.coffee'] 20 | src: ['*.coffee', 'src/**/*.coffee', 'test/**/*.coffee'] 21 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/grunt/compress.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | options: 3 | archive: './release.zip' 4 | mode: 'zip' 5 | build: 6 | files: [ 7 | { 8 | cwd: 'build' 9 | src: ['**', '!manifest.json'] 10 | expand: true 11 | filter: 'isFile' 12 | } 13 | { 14 | cwd: 'tmp/' 15 | src: 'manifest.json' 16 | expand: true 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/grunt/copy.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | web: 3 | expand: true 4 | cwd: '../omega-web/build' 5 | src: ['**/*'] 6 | dest: 'build/' 7 | target: 8 | files: 9 | 'build/js/omega_target.min.js': 10 | 'node_modules/omega-target/omega_target.min.js' 11 | target_self: 12 | src: 'omega_target_chromium_extension.min.js' 13 | dest: 'build/js/' 14 | target_popup: 15 | expand: true 16 | cwd: 'src/js' 17 | src: 'omega_target_popup.js' 18 | dest: 'build/js/' 19 | overlay: 20 | expand: true 21 | cwd: 'overlay' 22 | src: ['**/*'] 23 | dest: 'build/' 24 | docs: 25 | expand: true 26 | cwd: '..' 27 | src: ['COPYING', 'AUTHORS'] 28 | dest: 'build/' 29 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/grunt/mochaTest.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | test: 3 | options: 4 | reporter: 'spec' 5 | require: 'coffee-script/register' 6 | src: ['test/**/*.coffee'] 7 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/grunt/po2crx.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | locales: 3 | files: 4 | 'build/_locales/en/messages.json': 5 | '../omega-locales/en_US/LC_MESSAGES/omega-web.po' 6 | 'build/_locales/zh/messages.json': 7 | '../omega-locales/zh_CN/LC_MESSAGES/omega-web.po' 8 | 'build/_locales/cs/messages.json': 9 | '../omega-locales/cs/LC_MESSAGES/omega-web.po' 10 | 'build/_locales/fa/messages.json': 11 | '../omega-locales/fa/LC_MESSAGES/omega-web.po' 12 | 'build/_locales/zh_CN/messages.json': 13 | '../omega-locales/zh_CN/LC_MESSAGES/omega-web.po' 14 | 'build/_locales/zh_TW/messages.json': 15 | '../omega-locales/zh_TW/LC_MESSAGES/omega-web.po' 16 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/grunt/watch.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | grunt: 3 | options: 4 | reload: true 5 | files: 6 | 'grunt/*' 7 | tasks: ['coffeelint:tasks', 'default'] 8 | po2crx_locales: 9 | files: ['../omega-locales/**/*'] 10 | tasks: ['po2crx:locales'] 11 | copy_web: 12 | files: ['node_modules/omega-web/build/**/*'] 13 | tasks: ['copy:web'] 14 | copy_target: 15 | files: ['node_modules/omega-target/omega_target.min.js'] 16 | tasks: ['copy:target'] 17 | copy_overlay: 18 | files: ['overlay/**/*'] 19 | tasks: ['copy:overlay'] 20 | copy_target_popup: 21 | files: ['src/js/omega_target_popup.js'] 22 | tasks: ['copy:target_popup'] 23 | coffee: 24 | files: ['src/**/*.coffee'] 25 | tasks: ['coffeelint:src', 'browserify', 'coffee', 'copy:target_self'] 26 | browserify_omega_webext_proxy_script: 27 | files: ['src/js/omega_webext_proxy_script.js'] 28 | tasks: ['browserify:omega_webext_proxy_script'] 29 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/index.coffee: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/module') 2 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/omega_target_shim.js: -------------------------------------------------------------------------------- 1 | module.exports = OmegaTarget 2 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/overlay/background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SwitchyOmega Background 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/overlay/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "__MSG_manifest_app_name__", 4 | "version": "2.5.21", 5 | "description": "__MSG_manifest_app_description__", 6 | "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkhwZJT76btQ04EEMOFtZPLESD1TmSVjbLjs0OyesD9Ht8YllFPfJ3qmtbSQGVuvmxH1GK/jUO2QcEWb8bHuOjoRlq20fi5j5Aq90O8FKET+y5D8PxCyi3WmnquiEwaE5cNmaCsw/G2JlO+bZOtdQ/QKOvMxBAegABYimEGfSvCMVUEvpymys0gBhLoch72zPAiJUBkf0z8BtjYTueMRcRXkrSeRPLygUDQnZ1TkQWMYYBp/zqpD5ggxytAklEMQzR9Hn0lqu5s7iuUAgihbysPn/8Wh00Zj5FySpK//KcpG3JS7UWxC28oSt8z5ZR3YimnX+HX3P36V0mC1pgM4o7wIDAQAB", 7 | "icons": { 8 | "16": "img/icons/omega-action-16.png", 9 | "24": "img/icons/omega-action-24.png", 10 | "32": "img/icons/omega-action-32.png", 11 | "48": "img/icons/omega-48.png", 12 | "64": "img/icons/omega-64.png", 13 | "128": "img/icons/omega-128.png" 14 | }, 15 | "default_locale": "en", 16 | "browser_action": { 17 | "browser_style": false, 18 | "default_icon": { 19 | "16": "img/icons/omega-action-16.png", 20 | "19": "img/icons/omega-action-19.png", 21 | "24": "img/icons/omega-action-24.png", 22 | "32": "img/icons/omega-action-32.png" 23 | }, 24 | "default_title": "__MSG_manifest_icon_default_title__", 25 | "default_popup": "popup/index.html" 26 | }, 27 | "background": { 28 | "page": "background.html" 29 | }, 30 | "minimum_chrome_version": "22.0.0", 31 | "options_page": "options.html", 32 | "options_ui": { 33 | "page": "options.html", 34 | "browser_style": false, 35 | "open_in_tab": true 36 | }, 37 | "permissions": [ 38 | "proxy", 39 | "tabs", 40 | "alarms", 41 | "storage", 42 | "webRequest", 43 | "downloads", 44 | "webRequestBlocking", 45 | "contextMenus", 46 | 47 | "http://*/*", 48 | "https://*/*", 49 | "" 50 | ], 51 | "commands": { 52 | "_execute_browser_action": { 53 | "suggested_key": { 54 | "default": "Alt+Shift+O" 55 | } 56 | } 57 | }, 58 | "applications": { 59 | "gecko": { 60 | "id": "switchyomega@feliscatus.addons.mozilla.org", 61 | "strict_min_version": "55.0a1" 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "omega-target-chromium-extension", 3 | "version": "0.0.1", 4 | "private": true, 5 | "main": "./index", 6 | "devDependencies": { 7 | "chai": "~1.9.1", 8 | "coffee-script": "^1.7.1", 9 | "coffeeify": "^0.7.0", 10 | "coffeelint": "^1.16.0", 11 | "grunt": "^0.4.5", 12 | "grunt-browserify": "^3.0.0", 13 | "grunt-coffeelint": "^0.0.13", 14 | "grunt-contrib-coffee": "^0.11.1", 15 | "grunt-contrib-compress": "^0.12.0", 16 | "grunt-contrib-copy": "^0.5.0", 17 | "grunt-contrib-watch": "^0.6.1", 18 | "grunt-mocha-test": "~0.11.0", 19 | "load-grunt-config": "^0.13.1", 20 | "minifyify": "^4.1.1", 21 | "po2json": "^0.3.2" 22 | }, 23 | "dependencies": { 24 | "heap": "^0.2.6", 25 | "omega-target": "../omega-target", 26 | "omega-web": "../omega-web", 27 | "omega-pac": "../omega-pac", 28 | "xhr": "^1.16.0" 29 | }, 30 | "browser": { 31 | "omega-target": "./omega_target_shim.js" 32 | }, 33 | "scripts": { 34 | "dev": "npm link omega-target && npm link omega-web && npm link omega-pac" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/coffee/background_preload.coffee: -------------------------------------------------------------------------------- 1 | window.UglifyJS_NoUnsafeEval = true 2 | localStorage['log'] = '' 3 | localStorage['logLastError'] = '' 4 | 5 | window.OmegaContextMenuQuickSwitchHandler = -> null 6 | 7 | if chrome.contextMenus? 8 | # We don't need this API. However its presence indicates that Chrome >= 35 9 | # which provides info.checked we need in contextMenu callback. 10 | # https://developer.chrome.com/extensions/contextMenus 11 | if chrome.i18n.getUILanguage? 12 | # We must create the menu item here before others to make it first in menu. 13 | chrome.contextMenus.create({ 14 | id: 'enableQuickSwitch' 15 | title: chrome.i18n.getMessage('contextMenu_enableQuickSwitch') 16 | type: 'checkbox' 17 | checked: false 18 | contexts: ["browser_action"] 19 | onclick: (info) -> window.OmegaContextMenuQuickSwitchHandler(info) 20 | }) 21 | 22 | chrome.contextMenus.create({ 23 | title: chrome.i18n.getMessage('popup_reportIssues') 24 | contexts: ["browser_action"] 25 | onclick: OmegaDebug.reportIssue 26 | }) 27 | 28 | chrome.contextMenus.create({ 29 | title: chrome.i18n.getMessage('popup_errorLog') 30 | contexts: ["browser_action"] 31 | onclick: OmegaDebug.downloadLog 32 | }) 33 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/coffee/omega_debug.coffee: -------------------------------------------------------------------------------- 1 | window.OmegaDebug = 2 | getProjectVersion: -> 3 | chrome.runtime.getManifest().version 4 | getExtensionVersion: -> 5 | chrome.runtime.getManifest().version 6 | downloadLog: -> 7 | blob = new Blob [localStorage['log']], {type: "text/plain;charset=utf-8"} 8 | filename = "OmegaLog_#{Date.now()}.txt" 9 | 10 | if browser?.downloads?.download? 11 | url = URL.createObjectURL(blob) 12 | browser.downloads.download({url: url, filename: filename}) 13 | else 14 | saveAs(blob, filename) 15 | resetOptions: -> 16 | localStorage.clear() 17 | # Prevent options loading from sync storage after reload. 18 | localStorage['omega.local.syncOptions'] = '"conflict"' 19 | chrome.storage.local.clear() 20 | chrome.runtime.reload() 21 | reportIssue: -> 22 | url = 'https://github.com/FelisCatus/SwitchyOmega/issues/new?title=&body=' 23 | finalUrl = url 24 | try 25 | projectVersion = OmegaDebug.getProjectVersion() 26 | extensionVersion = OmegaDebug.getExtensionVersion() 27 | env = 28 | extensionVersion: extensionVersion 29 | projectVersion: extensionVersion 30 | userAgent: navigator.userAgent 31 | body = chrome.i18n.getMessage('popup_issueTemplate', [ 32 | env.projectVersion, env.userAgent 33 | ]) 34 | body ||= """ 35 | \n\n 36 | 37 | SwitchyOmega #{env.projectVersion} 38 | #{env.userAgent} 39 | """ 40 | finalUrl = url + encodeURIComponent(body) 41 | err = localStorage['logLastError'] 42 | if err 43 | body += "\n```\n#{err}\n```" 44 | finalUrl = (url + encodeURIComponent(body)).substr(0, 2000) 45 | 46 | chrome.tabs.create(url: finalUrl) 47 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/js/omega_target_popup.js: -------------------------------------------------------------------------------- 1 | function callBackgroundNoReply(method, args, cb) { 2 | chrome.runtime.sendMessage({ 3 | method: method, 4 | args: args, 5 | noReply: true, 6 | refreshActivePage: true, 7 | }); 8 | if (cb) return cb(); 9 | } 10 | 11 | function callBackground(method, args, cb) { 12 | chrome.runtime.sendMessage({ 13 | method: method, 14 | args: args, 15 | }, function(response) { 16 | if (chrome.runtime.lastError != null) 17 | return cb && cb(chrome.runtime.lastError) 18 | if (response.error) return cb && cb(response.error) 19 | return cb && cb(null, response.result) 20 | }); 21 | } 22 | 23 | var requestInfoCallback = null; 24 | 25 | OmegaTargetPopup = { 26 | getState: function (keys, cb) { 27 | if (typeof localStorage === 'undefined' || !localStorage.length) { 28 | callBackground('getState', [keys], cb); 29 | return; 30 | } 31 | var results = {}; 32 | keys.forEach(function(key) { 33 | try { 34 | results[key] = JSON.parse(localStorage['omega.local.' + key]); 35 | } catch (_) { 36 | return null; 37 | } 38 | }); 39 | if (cb) cb(null, results); 40 | }, 41 | applyProfile: function (name, cb) { 42 | callBackgroundNoReply('applyProfile', [name], cb); 43 | }, 44 | openOptions: function (hash, cb) { 45 | var options_url = chrome.extension.getURL('options.html'); 46 | 47 | chrome.tabs.query({ 48 | url: options_url 49 | }, function(tabs) { 50 | if (!chrome.runtime.lastError && tabs && tabs.length > 0) { 51 | var props = { 52 | active: true 53 | }; 54 | if (hash) { 55 | var url = options_url + hash; 56 | props.url = url; 57 | } 58 | chrome.tabs.update(tabs[0].id, props); 59 | } else { 60 | chrome.tabs.create({ 61 | url: options_url 62 | }); 63 | } 64 | if (cb) return cb(); 65 | }); 66 | }, 67 | getActivePageInfo: function(cb) { 68 | chrome.tabs.query({active: true, lastFocusedWindow: true}, function (tabs) { 69 | if (tabs.length === 0 || !tabs[0].url) return cb(); 70 | var args = {tabId: tabs[0].id, url: tabs[0].url}; 71 | callBackground('getPageInfo', [args], cb) 72 | }); 73 | }, 74 | setDefaultProfile: function(profileName, defaultProfileName, cb) { 75 | callBackgroundNoReply('setDefaultProfile', 76 | [profileName, defaultProfileName], cb); 77 | }, 78 | addTempRule: function(domain, profileName, cb) { 79 | callBackgroundNoReply('addTempRule', [domain, profileName], cb); 80 | }, 81 | openManage: function(domain, profileName, cb) { 82 | chrome.tabs.create({ 83 | url: 'chrome://extensions/?id=' + chrome.runtime.id, 84 | }, cb); 85 | }, 86 | getMessage: chrome.i18n.getMessage.bind(chrome.i18n), 87 | }; 88 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/js/omega_webext_proxy_script.js: -------------------------------------------------------------------------------- 1 | FindProxyForURL = (function () { 2 | var OmegaPac = require('omega-pac'); 3 | var options = {}; 4 | var state = {}; 5 | var activeProfile = null; 6 | var fallbackResult = 'DIRECT'; 7 | var pacCache = {}; 8 | 9 | init(); 10 | 11 | return FindProxyForURL; 12 | 13 | function FindProxyForURL(url, host, details) { 14 | if (!activeProfile) { 15 | warn('Warning: Proxy script not initialized on handling: ' + url); 16 | return fallbackResult; 17 | } 18 | // Moz: Neither path or query is included url regardless of scheme for now. 19 | // This is even more strict than Chromium restricting HTTPS URLs. 20 | // Therefore, it leads to different behavior than the icon and badge. 21 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1337001 22 | var request = OmegaPac.Conditions.requestFromUrl(url); 23 | var profile = activeProfile; 24 | var matchResult, next; 25 | while (profile) { 26 | matchResult = OmegaPac.Profiles.match(profile, request) 27 | if (!matchResult) { 28 | if (profile.profileType === 'DirectProfile') { 29 | return 'DIRECT'; 30 | } else { 31 | warn('Warning: Unsupported profile: ' + profile.profileType); 32 | return fallbackResult; 33 | } 34 | } 35 | 36 | if (Array.isArray(matchResult)) { 37 | next = matchResult[0]; 38 | var proxy = matchResult[2]; 39 | var auth = matchResult[3]; 40 | if (proxy && !state.useLegacyStringReturn) { 41 | var proxyInfo = { 42 | type: proxy.scheme, 43 | host: proxy.host, 44 | port: proxy.port, 45 | }; 46 | if (proxyInfo.type === 'socks5') { 47 | // MOZ: SOCKS5 proxies are identified by "type": "socks". 48 | // https://dxr.mozilla.org/mozilla-central/rev/ffe6cc09ccf38cca6f0e727837bbc6cb722d1e71/toolkit/components/extensions/ProxyScriptContext.jsm#51 49 | proxyInfo.type = 'socks'; 50 | // Enable SOCKS5 remote DNS. 51 | // TODO(catus): Maybe allow the users to configure this? 52 | proxyInfo.proxyDNS = true; 53 | } 54 | if (auth) { 55 | proxyInfo.username = auth.username; 56 | proxyInfo.password = auth.password; 57 | } 58 | return [proxyInfo]; 59 | } else if (next.charCodeAt(0) !== 43) { 60 | // MOZ: Legacy proxy support expects PAC-like string return type. 61 | // TODO(catus): Remove support for string return type. 62 | // MOZ: SOCKS5 proxies are supported under the prefix SOCKS. 63 | // https://dxr.mozilla.org/mozilla-central/rev/ffe6cc09ccf38cca6f0e727837bbc6cb722d1e71/toolkit/components/extensions/ProxyScriptContext.jsm#51 64 | // Note: We have to replace this because MOZ won't process the rest of 65 | // the list if the syntax of the first item is not recognized. 66 | return next.replace(/SOCKS5 /g, 'SOCKS '); 67 | } 68 | } else if (matchResult.profileName) { 69 | next = OmegaPac.Profiles.nameAsKey(matchResult.profileName) 70 | } else { 71 | return fallbackResult; 72 | } 73 | profile = OmegaPac.Profiles.byKey(next, options) 74 | } 75 | warn('Warning: Cannot find profile: ' + next); 76 | return fallbackResult; 77 | } 78 | 79 | function warn(message, error) { 80 | // We don't have console here and alert is not implemented. 81 | // Throwing and messaging seems to be the only ways to communicate. 82 | // MOZ: alert(): https://bugzilla.mozilla.org/show_bug.cgi?id=1353510 83 | browser.runtime.sendMessage({ 84 | event: 'proxyScriptLog', 85 | message: message, 86 | error: error, 87 | level: 'warn', 88 | }); 89 | } 90 | 91 | function init() { 92 | browser.runtime.onMessage.addListener(function(message) { 93 | if (message.event === 'proxyScriptStateChanged') { 94 | state = message.state; 95 | options = message.options; 96 | if (!state.currentProfileName) { 97 | activeProfile = state.tempProfile; 98 | } else { 99 | activeProfile = OmegaPac.Profiles.byName(state.currentProfileName, 100 | options); 101 | } 102 | } 103 | }); 104 | browser.runtime.sendMessage({event: 'proxyScriptLoaded'}); 105 | } 106 | })(); 107 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/module/chrome_api.coffee: -------------------------------------------------------------------------------- 1 | OmegaTarget = require('omega-target') 2 | Promise = OmegaTarget.Promise 3 | 4 | exports.chromeApiPromisify = (target, method) -> 5 | return (args...) -> 6 | new Promise (resolve, reject) -> 7 | callback = (callbackArgs...) -> 8 | if chrome.runtime.lastError? 9 | error = new Error(chrome.runtime.lastError.message) 10 | error.original = chrome.runtime.lastError 11 | return reject(error) 12 | if callbackArgs.length <= 1 13 | resolve(callbackArgs[0]) 14 | else 15 | resolve(callbackArgs) 16 | 17 | args.push(callback) 18 | target[method].apply(target, args) 19 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/module/chrome_port.coffee: -------------------------------------------------------------------------------- 1 | # A wrapper around type Port in Chromium Extension API. 2 | # https://developer.chrome.com/extensions/runtime#type-Port 3 | # 4 | # Please wrap any Port object in this class BEFORE adding listeners. Adding 5 | # listeners to events of raw Port objects should be avoided to minimize the risk 6 | # of memory leaks. See the comments of the TrackedEvent class for more details. 7 | module.exports = class ChromePort 8 | constructor: (@port) -> 9 | @name = @port.name 10 | @sender = @port.sender 11 | 12 | @disconnect = @port.disconnect.bind(@port) 13 | @postMessage = (args...) => 14 | try 15 | @port.postMessage(args...) 16 | catch _ 17 | return 18 | 19 | @onMessage = new TrackedEvent(@port.onMessage) 20 | @onDisconnect = new TrackedEvent(@port.onDisconnect) 21 | @onDisconnect.addListener @dispose.bind(this) 22 | 23 | dispose: -> 24 | @onMessage.dispose() 25 | @onDisconnect.dispose() 26 | 27 | # A wrapper around chrome.Event. 28 | # https://developer.chrome.com/extensions/events#type-Event 29 | # 30 | # ALL event listeners MUST be manually removed before disposing any Event or 31 | # object containing Event, such as Port. Otherwise, a memory leak will happen. 32 | # https://code.google.com/p/chromium/issues/detail?id=320723 33 | # 34 | # TrackedEvent helps to solve this problem by keeping track of all listeners 35 | # installed and removes them when the #dispose method is called. 36 | # Don't forget to call #dispose when this TrackedEvent is not needed any more. 37 | class TrackedEvent 38 | constructor: (@event) -> 39 | @callbacks = [] 40 | mes = ['hasListener', 'hasListeners', 'addRules', 'getRules', 'removeRules'] 41 | for methodName in mes 42 | method = @event[methodName] 43 | if method? 44 | this[methodName] = method.bind(@event) 45 | 46 | addListener: (callback) -> 47 | @event.addListener(callback) 48 | @callbacks.push(callback) 49 | return this 50 | 51 | removeListener: (callback) -> 52 | @event.removeListener(callback) 53 | i = @callbacks.indexOf(callback) 54 | @callbacks.splice(i, 1) if i >= 0 55 | return this 56 | 57 | ###* 58 | # Removes all listeners added via this TrackedEvent instance. 59 | # Note: Won't remove listeners added via other TrackedEvent or raw Event. 60 | ### 61 | removeAllListeners: -> 62 | for callback in @callbacks 63 | @event.removeListener(callback) 64 | @callbacks = [] 65 | return this 66 | 67 | ###* 68 | # Removes all listeners added via this TrackedEvent instance and prevent any 69 | # further listeners from being added. It is considered safe to nullify any 70 | # references to this instance and the underlying Event without causing leaks. 71 | # This should be the last method called in the lifetime of TrackedEvent. 72 | # 73 | # Throws if the underlying raw Event object still has listeners. This can 74 | # happen when listeners have been added via other TrackedEvents or raw Event. 75 | ### 76 | dispose: -> 77 | @removeAllListeners() 78 | if @event.hasListeners?() 79 | throw new Error("Underlying Event still has listeners!") 80 | @event = null 81 | @callbacks = null 82 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/module/external_api.coffee: -------------------------------------------------------------------------------- 1 | OmegaTarget = require('omega-target') 2 | OmegaPac = OmegaTarget.OmegaPac 3 | Promise = OmegaTarget.Promise 4 | ChromePort = require('./chrome_port') 5 | 6 | module.exports = class ExternalApi 7 | constructor: (options) -> 8 | @options = options 9 | knownExts: 10 | 'padekgcemlokbadohgkifijomclgjgif': 32 11 | disabled: false 12 | listen: -> 13 | return unless chrome.runtime.onConnectExternal 14 | chrome.runtime.onConnectExternal.addListener (rawPort) => 15 | port = new ChromePort(rawPort) 16 | port.onMessage.addListener (msg) => @onMessage(msg, port) 17 | port.onDisconnect.addListener @reenable.bind(this) 18 | 19 | _previousProfileName: null 20 | 21 | reenable: -> 22 | return unless @disabled 23 | 24 | @options.setProxyNotControllable(null) 25 | chrome.browserAction.setPopup?({popup: 'popup/index.html'}) 26 | @options.reloadQuickSwitch() 27 | @disabled = false 28 | @options.clearBadge() 29 | @options.applyProfile(@_previousProfileName) 30 | 31 | checkPerm: (port, level) -> 32 | perm = @knownExts[port.sender.id] || 0 33 | if perm < level 34 | port.postMessage({action: 'error', error: 'permission'}) 35 | false 36 | else 37 | true 38 | 39 | onMessage: (msg, port) -> 40 | @options.log.log("#{port.sender.id} -> #{msg.action}", msg) 41 | switch msg.action 42 | when 'disable' 43 | return unless @checkPerm(port, 16) 44 | return if @disabled 45 | @disabled = true 46 | @_previousProfileName = @options.currentProfile()?.name || 'system' 47 | @options.applyProfile('system').then => 48 | reason = 'disabled' 49 | if @knownExts[port.sender.id] >= 32 50 | reason = 'upgrade' 51 | @options.setProxyNotControllable reason, {text: 'X', color: '#5ab432'} 52 | chrome.browserAction.setPopup?({popup: 'popup/index.html'}) 53 | port.postMessage({action: 'state', state: 'disabled'}) 54 | when 'enable' 55 | @reenable() 56 | port.postMessage({action: 'state', state: 'enabled'}) 57 | when 'getOptions' 58 | return unless @checkPerm(port, 8) 59 | port.postMessage({action: 'options', options: @options.getAll()}) 60 | else 61 | port.postMessage( 62 | action: 'error' 63 | error: 'noSuchAction' 64 | action_name: msg.action 65 | ) 66 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/module/fetch_url.coffee: -------------------------------------------------------------------------------- 1 | Promise = OmegaTarget.Promise 2 | xhr = Promise.promisify(require('xhr')) 3 | Url = require('url') 4 | ContentTypeRejectedError = OmegaTarget.ContentTypeRejectedError 5 | 6 | xhrWrapper = (args...) -> 7 | xhr(args...).catch (err) -> 8 | throw err unless err.isOperational 9 | if not err.statusCode 10 | throw new OmegaTarget.NetworkError(err) 11 | if err.statusCode == 404 12 | throw new OmegaTarget.HttpNotFoundError(err) 13 | if err.statusCode >= 500 and err.statusCode < 600 14 | throw new OmegaTarget.HttpServerError(err) 15 | throw new OmegaTarget.HttpError(err) 16 | 17 | fetchUrl = (dest_url, opt_bypass_cache, opt_type_hints) -> 18 | getResBody = ([response, body]) -> 19 | return body unless opt_type_hints 20 | contentType = response.headers['content-type']?.toLowerCase() 21 | for hint in opt_type_hints 22 | handler = hintHandlers[hint] ? defaultHintHandler 23 | result = handler(response, body, {contentType, hint}) 24 | return result if result? 25 | throw new ContentTypeRejectedError( 26 | 'Unrecognized Content-Type: ' + contentType) 27 | return body 28 | 29 | if opt_bypass_cache and dest_url.indexOf('?') < 0 30 | parsed = Url.parse(dest_url, true) 31 | parsed.search = undefined 32 | parsed.query['_'] = Date.now() 33 | dest_url_nocache = Url.format(parsed) 34 | # Try first with the dumb parameter to bypass cache. 35 | xhrWrapper(dest_url_nocache).then(getResBody).catch -> 36 | # If failed, try again with the original URL. 37 | xhrWrapper(dest_url).then(getResBody) 38 | else 39 | xhrWrapper(dest_url).then(getResBody) 40 | 41 | defaultHintHandler = (response, body, {contentType, hint}) -> 42 | if '!' + contentType == hint 43 | throw new ContentTypeRejectedError( 44 | 'Response Content-Type blacklisted: ' + contentType) 45 | if contentType == hint 46 | return body 47 | 48 | hintHandlers = 49 | '*': (response, body) -> 50 | # Allow all contents. 51 | return body 52 | 53 | '!text/html': (response, body, {contentType, hint}) -> 54 | if contentType == hint 55 | # Sometimes other content can also be served with the text/html 56 | # Content-Type header. So we check if the body actually looks like HTML. 57 | looksLikeHtml = false 58 | if body.indexOf('= 0 || body.indexOf('= 0 59 | looksLikeHtml = true 60 | else if body.indexOf('') >= 0 61 | looksLikeHtml = true 62 | else if body.indexOf('') >= 0 63 | looksLikeHtml = true 64 | 65 | if looksLikeHtml 66 | throw new ContentTypeRejectedError('Response must not be HTML.') 67 | 68 | '!application/xhtml+xml': (args...) -> hintHandlers['!text/html'](args...) 69 | 70 | 'application/x-ns-proxy-autoconfig': (response, body, {contentType, hint}) -> 71 | if contentType == hint 72 | return body 73 | # Sometimes PAC scripts can also be served using with wrong Content-Type. 74 | if body.indexOf('FindProxyForURL') >= 0 75 | return body 76 | else 77 | # The content is not a PAC script if it does not contain FindProxyForURL. 78 | return undefined 79 | 80 | module.exports = fetchUrl 81 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/module/index.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | Storage: require('./storage') 3 | Options: require('./options') 4 | ChromeTabs: require('./tabs') 5 | SwitchySharp: require('./switchysharp') 6 | ExternalApi: require('./external_api') 7 | WebRequestMonitor: require('./web_request_monitor') 8 | Inspect: require('./inspect') 9 | Url: require('url') 10 | proxy: require('./proxy') 11 | 12 | for name, value of require('omega-target') 13 | module.exports[name] ?= value 14 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/module/inspect.coffee: -------------------------------------------------------------------------------- 1 | OmegaTarget = require('omega-target') 2 | OmegaPac = OmegaTarget.OmegaPac 3 | Promise = OmegaTarget.Promise 4 | 5 | module.exports = class Inspect 6 | _enabled: false 7 | constructor: (onInspect) -> 8 | @onInspect = onInspect 9 | 10 | enable: -> 11 | return unless chrome.contextMenus? 12 | # We don't need this API. However its presence indicates that Chrome >= 35, 13 | # which provides the menuItemId we need in contextMenu callback. 14 | # https://developer.chrome.com/extensions/contextMenus 15 | return unless chrome.i18n.getUILanguage? 16 | 17 | return if @_enabled 18 | 19 | webResource = [ 20 | "http://*/*" 21 | "https://*/*" 22 | "ftp://*/*" 23 | ] 24 | 25 | ### Not so useful... 26 | chrome.contextMenus.create({ 27 | id: 'inspectPage' 28 | title: chrome.i18n.getMessage('contextMenu_inspectPage') 29 | contexts: ['page'] 30 | onclick: @inspect.bind(this) 31 | documentUrlPatterns: webResource 32 | }) 33 | ### 34 | 35 | chrome.contextMenus.create({ 36 | id: 'inspectFrame' 37 | title: chrome.i18n.getMessage('contextMenu_inspectFrame') 38 | contexts: ['frame'] 39 | onclick: @inspect.bind(this) 40 | documentUrlPatterns: webResource 41 | }) 42 | 43 | chrome.contextMenus.create({ 44 | id: 'inspectLink' 45 | title: chrome.i18n.getMessage('contextMenu_inspectLink') 46 | contexts: ['link'] 47 | onclick: @inspect.bind(this) 48 | targetUrlPatterns: webResource 49 | }) 50 | 51 | chrome.contextMenus.create({ 52 | id: 'inspectElement' 53 | title: chrome.i18n.getMessage('contextMenu_inspectElement') 54 | contexts: [ 55 | 'image' 56 | 'video' 57 | 'audio' 58 | ] 59 | onclick: @inspect.bind(this) 60 | targetUrlPatterns: webResource 61 | }) 62 | 63 | @_enabled = true 64 | 65 | disable: -> 66 | return unless @_enabled 67 | for own menuId of @propForMenuItem 68 | try chrome.contextMenus.remove(menuId) 69 | @_enabled = false 70 | 71 | propForMenuItem: 72 | 'inspectPage': 'pageUrl' 73 | 'inspectFrame': 'frameUrl' 74 | 'inspectLink': 'linkUrl' 75 | 'inspectElement': 'srcUrl' 76 | 77 | inspect: (info, tab) -> 78 | return unless info.menuItemId 79 | url = info[@propForMenuItem[info.menuItemId]] 80 | if not url and info.menuItemId == 'inspectPage' 81 | url = tab.url 82 | return unless url 83 | 84 | @onInspect(url, tab) 85 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/module/proxy/index.coffee: -------------------------------------------------------------------------------- 1 | ListenerProxyImpl = require('./proxy_impl_listener') 2 | SettingsProxyImpl = require('./proxy_impl_settings') 3 | ScriptProxyImpl = require('./proxy_impl_script') 4 | 5 | exports.proxyImpls = [ListenerProxyImpl, ScriptProxyImpl, SettingsProxyImpl] 6 | exports.getProxyImpl = (log) -> 7 | for Impl in exports.proxyImpls 8 | if Impl.isSupported() 9 | return new Impl(log) 10 | throw new Error('Your browser does not support proxy settings!') 11 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/module/proxy/proxy_auth.coffee: -------------------------------------------------------------------------------- 1 | OmegaTarget = require('omega-target') 2 | OmegaPac = OmegaTarget.OmegaPac 3 | Promise = OmegaTarget.Promise 4 | 5 | module.exports = class ProxyAuth 6 | constructor: (log) -> 7 | @_requests = {} 8 | @log = log 9 | 10 | listening: false 11 | listen: -> 12 | return if @listening 13 | if not chrome.webRequest 14 | @log.error('Proxy auth disabled! No webRequest permission.') 15 | return 16 | if not chrome.webRequest.onAuthRequired 17 | @log.error('Proxy auth disabled! onAuthRequired not available.') 18 | return 19 | chrome.webRequest.onAuthRequired.addListener( 20 | @authHandler.bind(this) 21 | {urls: ['']} 22 | ['blocking'] 23 | ) 24 | chrome.webRequest.onCompleted.addListener( 25 | @_requestDone.bind(this) 26 | {urls: ['']} 27 | ) 28 | chrome.webRequest.onErrorOccurred.addListener( 29 | @_requestDone.bind(this) 30 | {urls: ['']} 31 | ) 32 | @listening = true 33 | 34 | _keyForProxy: (proxy) -> "#{proxy.host.toLowerCase()}:#{proxy.port}" 35 | setProxies: (profiles) -> 36 | @_proxies = {} 37 | @_fallbacks = [] 38 | for profile in profiles when profile.auth 39 | for scheme in OmegaPac.Profiles.schemes when profile[scheme.prop] 40 | auth = profile.auth?[scheme.prop] 41 | continue unless auth 42 | proxy = profile[scheme.prop] 43 | key = @_keyForProxy(proxy) 44 | list = @_proxies[key] 45 | if not list? 46 | @_proxies[key] = list = [] 47 | list.push({ 48 | config: proxy 49 | auth: auth 50 | name: profile.name + '.' + scheme.prop 51 | }) 52 | 53 | fallback = profile.auth?['all'] 54 | if fallback? 55 | @_fallbacks.push({ 56 | auth: fallback 57 | name: profile.name + '.' + 'all' 58 | }) 59 | 60 | _proxies: {} 61 | _fallbacks: [] 62 | _requests: null 63 | authHandler: (details) -> 64 | return {} unless details.isProxy 65 | req = @_requests[details.requestId] 66 | if not req? 67 | @_requests[details.requestId] = req = {authTries: 0} 68 | 69 | key = @_keyForProxy( 70 | host: details.challenger.host 71 | port: details.challenger.port 72 | ) 73 | 74 | list = @_proxies[key] 75 | listLen = if list? then list.length else 0 76 | if req.authTries < listLen 77 | proxy = list[req.authTries] 78 | else 79 | proxy = @_fallbacks[req.authTries - listLen] 80 | @log.log('ProxyAuth', key, req.authTries, proxy?.name) 81 | 82 | return {} unless proxy? 83 | req.authTries++ 84 | return authCredentials: proxy.auth 85 | 86 | _requestDone: (details) -> 87 | delete @_requests[details.requestId] 88 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/module/proxy/proxy_impl.coffee: -------------------------------------------------------------------------------- 1 | OmegaTarget = require('omega-target') 2 | Promise = OmegaTarget.Promise 3 | ProxyAuth = require('./proxy_auth') 4 | 5 | class ProxyImpl 6 | constructor: (log) -> 7 | @log = log 8 | @isSupported: -> false 9 | applyProfile: (profile, meta) -> Promise.reject() 10 | watchProxyChange: (callback) -> null 11 | parseExternalProfile: (details, options) -> null 12 | _profileNotFound: (name) -> 13 | @log.error("Profile #{name} not found! Things may go very, very wrong.") 14 | return OmegaPac.Profiles.create({ 15 | name: name 16 | profileType: 'VirtualProfile' 17 | defaultProfileName: 'direct' 18 | }) 19 | setProxyAuth: (profile, options) -> 20 | return Promise.try(=> 21 | @_proxyAuth ?= new ProxyAuth(@log) 22 | @_proxyAuth.listen() 23 | referenced_profiles = [] 24 | ref_set = OmegaPac.Profiles.allReferenceSet(profile, 25 | options, profileNotFound: @_profileNotFound.bind(this)) 26 | for own _, name of ref_set 27 | profile = OmegaPac.Profiles.byName(name, options) 28 | if profile 29 | referenced_profiles.push(profile) 30 | @_proxyAuth.setProxies(referenced_profiles) 31 | ) 32 | getProfilePacScript: (profile, meta, options) -> 33 | meta ?= profile 34 | ast = OmegaPac.PacGenerator.script(options, profile, 35 | profileNotFound: @_profileNotFound.bind(this)) 36 | ast = OmegaPac.PacGenerator.compress(ast) 37 | script = OmegaPac.PacGenerator.ascii(ast.print_to_string()) 38 | profileName = OmegaPac.PacGenerator.ascii(JSON.stringify(meta.name)) 39 | profileName = profileName.replace(/\*/g, '\\u002a') 40 | profileName = profileName.replace(/\\/g, '\\u002f') 41 | prefix = "/*OmegaProfile*#{profileName}*#{meta.revision}*/" 42 | return prefix + script 43 | 44 | module.exports = ProxyImpl 45 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/module/proxy/proxy_impl_listener.coffee: -------------------------------------------------------------------------------- 1 | OmegaTarget = require('omega-target') 2 | # The browser only accepts native promises as onRequest return values. 3 | # DO NOT USE Bluebird Promises here! 4 | NativePromise = Promise ? null 5 | ProxyImpl = require('./proxy_impl') 6 | 7 | class ListenerProxyImpl extends ProxyImpl 8 | @isSupported: -> Promise? and browser?.proxy?.onRequest? 9 | features: ['fullUrl', 'socks5Auth'] 10 | constructor: -> 11 | super(arguments...) 12 | @_optionsReady = new NativePromise (resolve) => 13 | @_optionsReadyCallback = resolve 14 | # We want to register listeners early so that it can start blocking requests 15 | # when starting the browser & extension, returning correct results later. 16 | @_initRequestListeners() 17 | _initRequestListeners: -> 18 | browser.proxy.onRequest.addListener(@onRequest.bind(this), 19 | {urls: [""]}) 20 | browser.proxy.onError.addListener(@onError.bind(this)) 21 | watchProxyChange: (callback) -> null 22 | applyProfile: (profile, state, options) -> 23 | @_options = options 24 | @_profile = profile 25 | @_optionsReadyCallback?() 26 | @_optionsReadyCallback = null 27 | return @setProxyAuth(profile, options) 28 | onRequest: (requestDetails) -> 29 | # The browser only recognizes native promises return values, not Bluebird. 30 | return NativePromise.resolve(@_optionsReady.then(=> 31 | request = OmegaPac.Conditions.requestFromUrl(requestDetails.url) 32 | profile = @_profile 33 | while profile 34 | result = OmegaPac.Profiles.match(profile, request) 35 | if not result 36 | switch profile.profileType 37 | when 'DirectProfile' 38 | return {type: 'direct'} 39 | when 'SystemProfile' 40 | # Returning undefined means using the default proxy from previous. 41 | # https://hg.mozilla.org/mozilla-central/rev/9f0ee2f582a2#l1.337 42 | return undefined 43 | else 44 | throw new Error('Unsupported profile: ' + profile.profileType) 45 | if Array.isArray(result) 46 | proxy = result[2] 47 | auth = result[3] 48 | return @proxyInfo(proxy, auth) if proxy 49 | next = result[0] 50 | else if result.profileName 51 | next = OmegaPac.Profiles.nameAsKey(result.profileName) 52 | else 53 | break 54 | profile = OmegaPac.Profiles.byKey(next, @_options) 55 | 56 | throw new Error('Profile not found: ' + next) 57 | )) 58 | onError: (error) -> 59 | @log.error(error) 60 | proxyInfo: (proxy, auth) -> 61 | proxyInfo = 62 | type: proxy.scheme 63 | host: proxy.host 64 | port: proxy.port 65 | if proxyInfo.type == 'socks5' 66 | # MOZ: SOCKS5 proxies should be specified as "type": "socks". 67 | # https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/proxy/ProxyInfo 68 | proxyInfo.type = 'socks' 69 | if auth 70 | # Username & password here are only available for SOCKS5. 71 | # https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/proxy/ProxyInfo 72 | # HTTP proxy auth must be handled via webRequest.onAuthRequired. 73 | proxyInfo.username = auth.username 74 | proxyInfo.password = auth.password 75 | if proxyInfo.type == 'socks' 76 | # Enable SOCKS remote DNS. 77 | # TODO(catus): Maybe allow the users to configure this? 78 | proxyInfo.proxyDNS = true 79 | 80 | # TODO(catus): Maybe allow proxyDNS for socks4? Server may support SOCKS4a. 81 | # It cannot default to true though, since SOCKS4 servers that does not have 82 | # the SOCKS4a extension may simply refuse to work. 83 | 84 | return [proxyInfo] 85 | 86 | module.exports = ListenerProxyImpl 87 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/module/proxy/proxy_impl_script.coffee: -------------------------------------------------------------------------------- 1 | OmegaTarget = require('omega-target') 2 | Promise = OmegaTarget.Promise 3 | ProxyImpl = require('./proxy_impl') 4 | 5 | class ScriptProxyImpl extends ProxyImpl 6 | @isSupported: -> 7 | return browser?.proxy?.register? or browser?.proxy?.registerProxyScript? 8 | features: ['socks5Auth'] 9 | _proxyScriptUrl: 'js/omega_webext_proxy_script.min.js' 10 | _proxyScriptDisabled: false 11 | _proxyScriptInitialized: false 12 | _proxyScriptState: {} 13 | watchProxyChange: (callback) -> null 14 | applyProfile: (profile, state, options) -> 15 | @log.error( 16 | 'Your browser is outdated! Full-URL based matching, etc. unsupported! ' + 17 | "Please update your browser ASAP!") 18 | state = state ? {} 19 | @_options = options 20 | state.currentProfileName = profile.name 21 | if profile.name == '' 22 | state.tempProfile = profile 23 | if profile.profileType == 'SystemProfile' 24 | # MOZ: SystemProfile cannot be done now due to lack of "PASS" support. 25 | # https://bugzilla.mozilla.org/show_bug.cgi?id=1319634 26 | # In the mean time, let's just unregister the script. 27 | if browser.proxy.unregister? 28 | browser.proxy.unregister() 29 | else 30 | # Some older browers may not ship with .unregister API. 31 | # In that case, let's just set an invalid script to unregister it. 32 | browser.proxy.registerProxyScript('js/omega_invalid_proxy_script.js') 33 | @_proxyScriptDisabled = true 34 | else 35 | @_proxyScriptState = state 36 | Promise.all([ 37 | browser.runtime.getBrowserInfo(), 38 | @_initWebextProxyScript(), 39 | ]).then ([info]) => 40 | if info.vendor == 'Mozilla' and info.buildID < '20170918220054' 41 | # MOZ: Legacy proxy support expects PAC-like string return type. 42 | # TODO(catus): Remove support for string return type. 43 | @log.error( 44 | 'Your browser is outdated! SOCKS5 DNS/Auth unsupported! ' + 45 | "Please update your browser ASAP! (Current Build #{info.buildID})") 46 | @_proxyScriptState.useLegacyStringReturn = true 47 | @_proxyScriptStateChanged() 48 | return @setProxyAuth(profile, options) 49 | _initWebextProxyScript: -> 50 | if not @_proxyScriptInitialized 51 | browser.proxy.onProxyError.addListener (err) => 52 | if err?.message? 53 | if err.message.indexOf('Invalid Proxy Rule: DIRECT') >= 0 54 | # DIRECT cannot be parsed in Mozilla earlier due to a bug. Even 55 | # though it throws, it actually falls back to direct connection 56 | # so it works. 57 | # https://bugzilla.mozilla.org/show_bug.cgi?id=1355198 58 | return 59 | if err.message.indexOf('Return type must be a string') >= 0 60 | # MOZ: Legacy proxy support expects PAC-like string return type. 61 | # TODO(catus): Remove support for string return type. 62 | # 63 | @log.error( 64 | 'Your browser is outdated! SOCKS5 DNS/Auth unsupported! ' + 65 | 'Please update your browser ASAP!') 66 | @_proxyScriptState.useLegacyStringReturn = true 67 | @_proxyScriptStateChanged() 68 | return 69 | @log.error(err) 70 | browser.runtime.onMessage.addListener (message) => 71 | return unless message.event == 'proxyScriptLog' 72 | if message.level == 'error' 73 | @log.error(message) 74 | else if message.level == 'warn' 75 | @log.error(message) 76 | else 77 | @log.log(message) 78 | 79 | if not @_proxyScriptInitialized or @_proxyScriptDisabled 80 | promise = new Promise (resolve) -> 81 | onMessage = (message) -> 82 | return unless message.event == 'proxyScriptLoaded' 83 | resolve() 84 | browser.runtime.onMessage.removeListener onMessage 85 | return 86 | browser.runtime.onMessage.addListener onMessage 87 | # The API has been renamed to .register but for some old browsers' sake: 88 | if browser.proxy.register? 89 | browser.proxy.register(@_proxyScriptUrl) 90 | else 91 | browser.proxy.registerProxyScript(@_proxyScriptUrl) 92 | @_proxyScriptDisabled = false 93 | else 94 | promise = Promise.resolve() 95 | @_proxyScriptInitialized = true 96 | return promise 97 | 98 | _proxyScriptStateChanged: -> 99 | browser.runtime.sendMessage({ 100 | event: 'proxyScriptStateChanged' 101 | state: @_proxyScriptState 102 | options: @_options 103 | }, { 104 | toProxyScript: true 105 | }) 106 | 107 | module.exports = ScriptProxyImpl 108 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/module/storage.coffee: -------------------------------------------------------------------------------- 1 | chromeApiPromisify = require('./chrome_api').chromeApiPromisify 2 | OmegaTarget = require('omega-target') 3 | Promise = OmegaTarget.Promise 4 | 5 | class ChromeStorage extends OmegaTarget.Storage 6 | @parseStorageErrors: (err) -> 7 | if err?.message 8 | sustainedPerMinute = 'MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE' 9 | if err.message.indexOf('QUOTA_BYTES_PER_ITEM') >= 0 10 | err = new OmegaTarget.Storage.QuotaExceededError() 11 | err.perItem = true 12 | else if err.message.indexOf('QUOTA_BYTES') >= 0 13 | err = new OmegaTarget.Storage.QuotaExceededError() 14 | else if err.message.indexOf('MAX_ITEMS') >= 0 15 | err = new OmegaTarget.Storage.QuotaExceededError() 16 | err.maxItems = true 17 | else if err.message.indexOf('MAX_WRITE_OPERATIONS_') >= 0 18 | err = new OmegaTarget.Storage.RateLimitExceededError() 19 | if err.message.indexOf('MAX_WRITE_OPERATIONS_PER_HOUR') >= 0 20 | err.perHour = true 21 | else if err.message.indexOf('MAX_WRITE_OPERATIONS_PER_MINUTE') >= 0 22 | err.perMinute = true 23 | else if err.message.indexOf(sustainedPerMinute) >= 0 24 | err = new OmegaTarget.Storage.RateLimitExceededError() 25 | err.perMinute = true 26 | err.sustained = 10 27 | else if err.message.indexOf('is not available') >= 0 28 | # This could happen if the storage area is not available. For example, 29 | # some Chromium-based browsers disable access to the sync storage. 30 | err = new OmegaTarget.Storage.StorageUnavailableError() 31 | else if err.message.indexOf( 32 | 'Please set webextensions.storage.sync.enabled to true') >= 0 33 | # This happens when sync storage is disabled in flags. 34 | err = new OmegaTarget.Storage.StorageUnavailableError() 35 | 36 | return Promise.reject(err) 37 | 38 | constructor: (@areaName) -> 39 | if browser?.storage?[@areaName] 40 | @storage = browser.storage[@areaName] 41 | else 42 | @storage = 43 | get: chromeApiPromisify(chrome.storage[@areaName], 'get') 44 | set: chromeApiPromisify(chrome.storage[@areaName], 'set') 45 | remove: chromeApiPromisify(chrome.storage[@areaName], 'remove') 46 | clear: chromeApiPromisify(chrome.storage[@areaName], 'clear') 47 | 48 | get: (keys) -> 49 | keys ?= null 50 | Promise.resolve(@storage.get(keys)).catch(ChromeStorage.parseStorageErrors) 51 | 52 | set: (items) -> 53 | if Object.keys(items).length == 0 54 | return Promise.resolve({}) 55 | Promise.resolve(@storage.set(items)).catch(ChromeStorage.parseStorageErrors) 56 | 57 | remove: (keys) -> 58 | if not keys? 59 | return Promise.resolve(@storage.clear()) 60 | if Array.isArray(keys) and keys.length == 0 61 | return Promise.resolve({}) 62 | Promise.resolve(@storage.remove(keys)) 63 | .catch(ChromeStorage.parseStorageErrors) 64 | 65 | watch: (keys, callback) -> 66 | ChromeStorage.watchers[@areaName] ?= {} 67 | area = ChromeStorage.watchers[@areaName] 68 | watcher = {keys: keys, callback: callback} 69 | id = Date.now().toString() 70 | while area[id] 71 | id = Date.now().toString() 72 | 73 | if Array.isArray(keys) 74 | keyMap = {} 75 | for key in keys 76 | keyMap[key] = true 77 | keys = keyMap 78 | area[id] = {keys: keys, callback: callback} 79 | if not ChromeStorage.onChangedListenerInstalled 80 | chrome.storage.onChanged.addListener(ChromeStorage.onChangedListener) 81 | ChromeStorage.onChangedListenerInstalled = true 82 | return -> delete area[id] 83 | 84 | @onChangedListener: (changes, areaName) -> 85 | map = null 86 | for _, watcher of ChromeStorage.watchers[areaName] 87 | match = watcher.keys == null 88 | if not match 89 | for own key of changes 90 | if watcher.keys[key] 91 | match = true 92 | break 93 | if match 94 | if not map? 95 | map = {} 96 | for own key, change of changes 97 | map[key] = change.newValue 98 | watcher.callback(map) 99 | 100 | @onChangedListenerInstalled: false 101 | @watchers: {} 102 | 103 | module.exports = ChromeStorage 104 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/module/switchysharp.coffee: -------------------------------------------------------------------------------- 1 | OmegaTarget = require('omega-target') 2 | OmegaPac = OmegaTarget.OmegaPac 3 | Promise = OmegaTarget.Promise 4 | ChromePort = require('./chrome_port') 5 | 6 | module.exports = class SwitchySharp 7 | @extId: 'dpplabbmogkhghncfbfdeeokoefdjegm' 8 | port: null 9 | 10 | monitor: (action) -> 11 | return if location.href.substr(0, 4) == 'moz-' 12 | if not port? and not @_monitorTimerId? 13 | @_monitorTimerId = setInterval @_connect.bind(this), 5000 14 | if action != 'reconnect' 15 | @_connect() 16 | 17 | getOptions: -> 18 | if not @_getOptions 19 | @_getOptions = new Promise (resolve) => 20 | @_getOptionsResolver = resolve 21 | @monitor() 22 | @_getOptions 23 | 24 | _getOptions: null 25 | _getOptionsResolver: null 26 | _monitorTimerId: null 27 | 28 | _onMessage: (msg) -> 29 | if @_monitorTimerId 30 | clearInterval @_monitorTimerId 31 | @_monitorTimerId = null 32 | switch msg?.action 33 | when 'state' 34 | # State changed. 35 | OmegaTarget.Log.log(msg) 36 | if @_getOptionsResolver 37 | @port.postMessage({action: 'getOptions'}) 38 | when 'options' 39 | @_getOptionsResolver?(msg.options) 40 | @_getOptionsResolver = null 41 | 42 | _onDisconnect: (msg) -> 43 | @port = null 44 | @_getOptions = null 45 | @_getOptionsResolver = null 46 | @monitor('reconnect') 47 | 48 | _connect: -> 49 | if not @port 50 | @port = new ChromePort(chrome.runtime.connect(SwitchySharp.extId)) 51 | @port.onDisconnect.addListener(@_onDisconnect.bind(this)) 52 | @port?.onMessage.addListener(@_onMessage.bind(this)) 53 | try 54 | @port.postMessage({action: 'disable'}) 55 | catch _ 56 | @port = null 57 | return @port? 58 | -------------------------------------------------------------------------------- /omega-target-chromium-extension/src/module/tabs.coffee: -------------------------------------------------------------------------------- 1 | class ChromeTabs 2 | _defaultAction: null 3 | _badgeTab: null 4 | 5 | constructor: (@actionForUrl) -> 6 | @_dirtyTabs = {} 7 | return 8 | 9 | ignoreError: -> 10 | chrome.runtime.lastError 11 | return 12 | 13 | watch: -> 14 | chrome.tabs.onUpdated.addListener @onUpdated.bind(this) 15 | chrome.tabs.onActivated.addListener (info) => 16 | chrome.tabs.get info.tabId, (tab) => 17 | return if chrome.runtime.lastError 18 | if @_dirtyTabs.hasOwnProperty(info.tabId) 19 | @onUpdated tab.id, {}, tab 20 | 21 | resetAll: (action) -> 22 | @_defaultAction = action 23 | chrome.tabs.query {}, (tabs) => 24 | @_dirtyTabs = {} 25 | tabs.forEach (tab) => 26 | @_dirtyTabs[tab.id] = tab.id 27 | @onUpdated tab.id, {}, tab if tab.active 28 | if chrome.browserAction.setPopup? 29 | chrome.browserAction.setTitle({title: action.title}) 30 | else 31 | chrome.browserAction.setTitle({title: action.shortTitle}) 32 | @setIcon(action.icon) 33 | 34 | onUpdated: (tabId, changeInfo, tab) -> 35 | if @_dirtyTabs.hasOwnProperty(tab.id) 36 | delete @_dirtyTabs[tab.id] 37 | else if not changeInfo.url? 38 | if changeInfo.status? and changeInfo.status != 'loading' 39 | return 40 | @processTab(tab, changeInfo) 41 | 42 | processTab: (tab, changeInfo) -> 43 | if @_badgeTab 44 | for own id of @_badgeTab 45 | try chrome.browserAction.setBadgeText?(text: '', tabId: id) 46 | @_badgeTab = null 47 | 48 | if not tab.url? or tab.url.indexOf("chrome") == 0 49 | if @_defaultAction 50 | chrome.browserAction.setTitle({ 51 | title: @_defaultAction.title 52 | tabId: tab.id 53 | }) 54 | @clearIcon tab.id 55 | return 56 | @actionForUrl(tab.url).then (action) => 57 | if not action 58 | @clearIcon tab.id 59 | return 60 | @setIcon(action.icon, tab.id) 61 | if chrome.browserAction.setPopup? 62 | chrome.browserAction.setTitle({title: action.title, tabId: tab.id}) 63 | else 64 | chrome.browserAction.setTitle({title: action.shortTitle, tabId: tab.id}) 65 | 66 | setTabBadge: (tab, badge) -> 67 | @_badgeTab ?= {} 68 | @_badgeTab[tab.id] = true 69 | chrome.browserAction.setBadgeText?(text: badge.text, tabId: tab.id) 70 | chrome.browserAction.setBadgeBackgroundColor?( 71 | color: badge.color 72 | tabId: tab.id 73 | ) 74 | 75 | setIcon: (icon, tabId) -> 76 | return unless icon? 77 | if tabId? 78 | params = { 79 | imageData: icon 80 | tabId: tabId 81 | } 82 | else 83 | params = { 84 | imageData: icon 85 | } 86 | @_chromeSetIcon(params) 87 | 88 | _chromeSetIcon: (params) -> 89 | try 90 | chrome.browserAction.setIcon?(params, @ignoreError) 91 | catch _ 92 | # Some legacy Chrome versions will panic if there are other icon sizes. 93 | params.imageData = {19: params.imageData[19], 38: params.imageData[38]} 94 | chrome.browserAction.setIcon?(params, @ignoreError) 95 | 96 | clearIcon: (tabId) -> 97 | return unless @_defaultAction?.icon? 98 | @_chromeSetIcon({ 99 | imageData: @_defaultAction.icon 100 | tabId: tabId 101 | }, @ignoreError) 102 | 103 | module.exports = ChromeTabs 104 | -------------------------------------------------------------------------------- /omega-target/.gitignore: -------------------------------------------------------------------------------- 1 | /index.js 2 | /omega_target.min.js 3 | -------------------------------------------------------------------------------- /omega-target/Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = require('load-grunt-config') 2 | -------------------------------------------------------------------------------- /omega-target/grunt/aliases.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | default: [ 3 | 'coffeelint' 4 | 'browserify' 5 | ] 6 | test: ['mochaTest'] 7 | -------------------------------------------------------------------------------- /omega-target/grunt/browserify.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | index: 3 | files: 4 | 'index.js': 'index.coffee' 5 | options: 6 | transform: ['coffeeify'] 7 | exclude: ['bluebird', 'jsondiffpatch', 'omega-pac'] 8 | browserifyOptions: 9 | extensions: '.coffee' 10 | builtins: [] 11 | standalone: 'index.coffee' 12 | debug: true 13 | browser: 14 | files: 15 | 'omega_target.min.js': 'index.coffee' 16 | options: 17 | alias: [ 18 | './index.coffee:OmegaTarget' 19 | ] 20 | transform: ['coffeeify'] 21 | plugin: 22 | if process.env.BUILD == 'release' 23 | [['minifyify', {map: false}]] 24 | else 25 | [] 26 | browserifyOptions: 27 | extensions: '.coffee' 28 | standalone: 'OmegaTarget' 29 | -------------------------------------------------------------------------------- /omega-target/grunt/coffeelint.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | options: 3 | arrow_spacing: level: 'error' 4 | colon_assignment_spacing: 5 | level: 'error' 6 | spacing: 7 | left: 0 8 | right: 1 9 | missing_fat_arrows: level: 'warn' 10 | no_empty_functions: level: 'error' 11 | no_empty_param_list: level: 'error' 12 | no_interpolation_in_single_quotes: level: 'error' 13 | no_stand_alone_at: level: 'error' 14 | space_operators: level: 'error' 15 | # https://github.com/clutchski/coffeelint/issues/525 16 | indentation: level: 'ignore' 17 | 18 | gruntfile: ['Gruntfile.coffee'] 19 | tasks: ['grunt/**/*.coffee'] 20 | src: ['src/**/*.coffee', 'test/**/*.coffee'] 21 | -------------------------------------------------------------------------------- /omega-target/grunt/mochaTest.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | test: 3 | options: 4 | reporter: 'spec' 5 | require: 'coffee-script/register' 6 | src: ['test/**/*.coffee'] 7 | -------------------------------------------------------------------------------- /omega-target/grunt/watch.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | grunt: 3 | options: 4 | reload: true 5 | files: 6 | 'grunt/*' 7 | tasks: ['coffeelint:tasks', 'default'] 8 | src: 9 | files: ['src/**/*.coffee', 'test/**/*.coffee'] 10 | tasks: ['default'] 11 | -------------------------------------------------------------------------------- /omega-target/index.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | Log: require('./src/log') 3 | Storage: require('./src/storage') 4 | BrowserStorage: require('./src/browser_storage') 5 | Options: require('./src/options') 6 | OptionsSync: require('./src/options_sync') 7 | OmegaPac: require('omega-pac') 8 | 9 | for name, value of require('./src/utils.coffee') 10 | module.exports[name] = value 11 | 12 | for name, value of require('./src/errors.coffee') 13 | module.exports[name] = value 14 | -------------------------------------------------------------------------------- /omega-target/omega_pac_shim.js: -------------------------------------------------------------------------------- 1 | module.exports = OmegaPac; 2 | -------------------------------------------------------------------------------- /omega-target/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "omega-target", 3 | "version": "0.0.1", 4 | "private": true, 5 | "main": "./index.js", 6 | "devDependencies": { 7 | "chai": "^1.10.0", 8 | "coffee-script": "^1.8.0", 9 | "coffeeify": "^0.7.0", 10 | "coffeelint": "^1.16.0", 11 | "grunt": "^0.4.5", 12 | "grunt-browserify": "^3.0.0", 13 | "grunt-coffeelint": "^0.0.13", 14 | "grunt-contrib-coffee": "^0.11.1", 15 | "grunt-contrib-watch": "^0.6.1", 16 | "grunt-mocha-test": "~0.11.0", 17 | "load-grunt-config": "^0.13.1", 18 | "minifyify": "^4.1.1", 19 | "sinon": "^1.12.2", 20 | "sinon-chai": "^2.6.0" 21 | }, 22 | "dependencies": { 23 | "bluebird": "^2.3.2", 24 | "jsondiffpatch": "^0.1.8", 25 | "limiter": "^1.0.5", 26 | "omega-pac": "../omega-pac" 27 | }, 28 | "browser": { 29 | "omega-pac": "./omega_pac_shim.js" 30 | }, 31 | "scripts": { 32 | "dev": "npm link omega-pac && npm link", 33 | "test": "grunt test" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /omega-target/src/browser_storage.coffee: -------------------------------------------------------------------------------- 1 | Storage = require('./storage') 2 | Promise = require('bluebird') 3 | 4 | class BrowserStorage extends Storage 5 | constructor: (@storage, @prefix = '') -> 6 | @proto = Object.getPrototypeOf(@storage) 7 | 8 | get: (keys) -> 9 | map = {} 10 | if typeof keys == 'string' 11 | map[keys] = undefined 12 | else if Array.isArray(keys) 13 | for key in keys 14 | map[key] = undefined 15 | else if typeof keys == 'object' 16 | map = keys 17 | for own key of map 18 | try 19 | value = JSON.parse(@proto.getItem.call(@storage, @prefix + key)) 20 | map[key] = value if value? 21 | if typeof map[key] == 'undefined' 22 | delete map[key] 23 | Promise.resolve map 24 | 25 | set: (items) -> 26 | for own key, value of items 27 | value = JSON.stringify(value) 28 | @proto.setItem.call(@storage, @prefix + key, value) 29 | Promise.resolve items 30 | 31 | remove: (keys) -> 32 | if not keys? 33 | if not @prefix 34 | @proto.clear.call(@storage) 35 | else 36 | index = 0 37 | while true 38 | key = @proto.key.call(index) 39 | break if key == null 40 | if @key.substr(0, @prefix.length) == @prefix 41 | @proto.removeItem.call(@storage, @prefix + keys) 42 | else 43 | index++ 44 | if typeof keys == 'string' 45 | @proto.removeItem.call(@storage, @prefix + keys) 46 | for key in keys 47 | @proto.removeItem.call(@storage, @prefix + key) 48 | 49 | Promise.resolve() 50 | 51 | module.exports = BrowserStorage 52 | -------------------------------------------------------------------------------- /omega-target/src/default_options.coffee: -------------------------------------------------------------------------------- 1 | module.exports = -> 2 | schemaVersion: 2 3 | "-enableQuickSwitch": false 4 | "-refreshOnProfileChange": true 5 | "-startupProfileName": "" 6 | "-quickSwitchProfiles": [] 7 | "-revertProxyChanges": true 8 | "-confirmDeletion": true 9 | "-showInspectMenu": true 10 | "-addConditionsToBottom": false 11 | "-showExternalProfile": true 12 | "-downloadInterval": 1440 13 | "+proxy": 14 | bypassList: [ 15 | { 16 | pattern: "127.0.0.1" 17 | conditionType: "BypassCondition" 18 | } 19 | { 20 | pattern: "::1" 21 | conditionType: "BypassCondition" 22 | } 23 | { 24 | pattern: "localhost" 25 | conditionType: "BypassCondition" 26 | } 27 | ] 28 | profileType: "FixedProfile" 29 | name: "proxy" 30 | color: "#99ccee" 31 | fallbackProxy: 32 | port: 8080 33 | scheme: "http" 34 | host: "proxy.example.com" 35 | 36 | "+auto switch": 37 | profileType: "SwitchProfile" 38 | rules: [ 39 | { 40 | condition: 41 | pattern: "internal.example.com" 42 | conditionType: "HostWildcardCondition" 43 | 44 | profileName: "direct" 45 | } 46 | { 47 | condition: 48 | pattern: "*.example.com" 49 | conditionType: "HostWildcardCondition" 50 | 51 | profileName: "proxy" 52 | } 53 | ] 54 | name: "auto switch" 55 | color: "#99dd99" 56 | defaultProfileName: "direct" 57 | -------------------------------------------------------------------------------- /omega-target/src/errors.coffee: -------------------------------------------------------------------------------- 1 | class NetworkError extends Error 2 | constructor: (err) -> 3 | super 4 | this.cause = err 5 | this.name = 'NetworkError' 6 | 7 | class HttpError extends NetworkError 8 | constructor: -> 9 | super 10 | this.statusCode = this.cause?.statusCode 11 | this.name = 'HttpError' 12 | 13 | class HttpNotFoundError extends HttpError 14 | constructor: -> 15 | super 16 | this.name = 'HttpNotFoundError' 17 | 18 | class HttpServerError extends HttpError 19 | constructor: -> 20 | super 21 | this.name = 'HttpServerError' 22 | 23 | class ContentTypeRejectedError extends Error 24 | constructor: -> 25 | super 26 | this.name = 'ContentTypeRejectedError' 27 | 28 | module.exports = 29 | NetworkError: NetworkError 30 | HttpError: HttpError 31 | HttpNotFoundError: HttpNotFoundError 32 | HttpServerError: HttpServerError 33 | ContentTypeRejectedError: ContentTypeRejectedError 34 | -------------------------------------------------------------------------------- /omega-target/src/log.coffee: -------------------------------------------------------------------------------- 1 | ### @module omega-target/log ### 2 | Log = require './log' 3 | 4 | replacer = (key, value) -> 5 | switch key 6 | # Hide values for a few keys with privacy concerns. 7 | when "username", "password", "host", "port" 8 | return "" 9 | else 10 | value 11 | 12 | # Log is used as singleton. 13 | # coffeelint: disable=missing_fat_arrows 14 | module.exports = Log = 15 | ###* 16 | # Pretty-print an object and return the result string. 17 | # @param {{}} obj The object to format 18 | # @returns {String} the formatted object in string 19 | ### 20 | str: (obj) -> 21 | # TODO(catus): This can be improved to print things more friendly. 22 | if typeof obj == 'object' and obj != null 23 | if obj.debugStr? 24 | if typeof obj.debugStr == 'function' 25 | obj.debugStr() 26 | else 27 | obj.debugStr 28 | else if obj instanceof Error 29 | obj.stack || obj.message 30 | else 31 | JSON.stringify(obj, replacer, 4) 32 | else if typeof obj == 'function' 33 | if obj.name 34 | "" 35 | else 36 | obj.toString() 37 | else 38 | '' + obj 39 | 40 | ###* 41 | # Print something to the log. 42 | # @param {...{}} args The objects to log 43 | ### 44 | log: console.log.bind(console) 45 | 46 | ###* 47 | # Print something to the error log. 48 | # @param {...{}} args The objects to log 49 | ### 50 | error: console.error.bind(console) 51 | 52 | ###* 53 | # Log a function call with target and arguments 54 | # @param {string} name The name of the method 55 | # @param {Array} args The arguments to the method call 56 | ### 57 | func: (name, args) -> 58 | this.log(name, '(', [].slice.call(args), ')') 59 | 60 | ###* 61 | # Log a method call with target and arguments 62 | # @param {string} name The name of the method 63 | # @param {{}} self The target of the method call 64 | # @param {Array} args The arguments to the method call 65 | ### 66 | method: (name, self, args) -> 67 | this.log(this.str(self), '<<', name, [].slice.call(args)) 68 | 69 | # coffeelint: enable=missing_fat_arrows 70 | -------------------------------------------------------------------------------- /omega-target/src/storage.coffee: -------------------------------------------------------------------------------- 1 | ### @module omega-target/storage ### 2 | Promise = require 'bluebird' 3 | Log = require './log' 4 | 5 | class Storage 6 | ###* 7 | # Any operation that fails due to rate limiting should reject with an instance 8 | # of RateLimitExceededError, when implemented in derived classes of Storage. 9 | ### 10 | @RateLimitExceededError: 11 | class RateLimitExceededError extends Error 12 | constructor: -> super 13 | 14 | ###* 15 | # Any operation that fails due to storage quota should reject with an instance 16 | # of QuotaExceededError, when implemented in derived classes of Storage. 17 | ### 18 | @QuotaExceededError: 19 | class QuotaExceededError extends Error 20 | constructor: -> super 21 | 22 | ###* 23 | # If this storage is not available for some reason, all operations should 24 | # reject with an instance of StorageUnavailableError, when implemented in 25 | # derived classes of Storage. 26 | # This error is considered fatal and unrecoverable in the current environment. 27 | # Further access to this storage should be avoided until restart. 28 | ### 29 | @StorageUnavailableError: 30 | class StorageUnavailableError extends Error 31 | constructor: -> super 32 | 33 | ###* 34 | # A set of operations to be performed on a Storage. 35 | # @typedef WriteOperations 36 | # @type {object} 37 | # @property {Object.} set - A map from keys to new values of the 38 | # items to set 39 | # @property {{}[]} remove - An array of keys to remove 40 | ### 41 | 42 | ###* 43 | # Calculate the actual operations against storage that should be performed to 44 | # replay the changes on a storage. 45 | # @param {Object.} changes The changes to apply 46 | # @param {?{}} args Extra arguments 47 | # @param {Object.?} args.base The original items in the storage. 48 | # @param {function(key, newVal, oldVal)} args.merge A function that merges 49 | # the newVal and oldVal. oldVal is provided only if args.base is present. 50 | # Otherwise it will be equal to newVal (i.e. merge(key, newVal, newVal)). 51 | # @returns {WriteOperations} The operations that should be performed. 52 | ### 53 | @operationsForChanges: (changes, {base, merge} = {}) -> 54 | set = {} 55 | remove = [] 56 | for key, newVal of changes 57 | oldVal = if base? then base[key] else newVal 58 | if merge 59 | newVal = merge(key, newVal, oldVal) 60 | continue if base? and newVal == oldVal 61 | if typeof newVal == 'undefined' 62 | if typeof oldVal != 'undefined' or not base? 63 | remove.push(key) 64 | else 65 | set[key] = newVal 66 | return {set: set, remove: remove} 67 | 68 | ###* 69 | # Get the requested values by keys from the storage. 70 | # @param {(string|string[]|null|Object.)} keys The keys to retrive, 71 | # or null for all. 72 | # @returns {Promise<(Object.)>} A map from keys to values 73 | ### 74 | get: (keys) -> 75 | Log.method('Storage#get', this, arguments) 76 | return Promise.resolve({}) unless @_items 77 | if not keys? 78 | keys = @_items 79 | map = {} 80 | if typeof keys == 'string' 81 | map[keys] = @_items[keys] 82 | else if Array.isArray(keys) 83 | for key in keys 84 | map[key] = @_items[key] 85 | else if typeof keys == 'object' 86 | for key, value of keys 87 | map[key] = @_items[key] ? value 88 | Promise.resolve(map) 89 | 90 | ###* 91 | # Set multiple values by keys in the storage. 92 | # @param {(string|Object.)} items A map from key to value to set. 93 | # @returns {Promise<(Object.)>} A map of key-value pairs just set. 94 | ### 95 | set: (items) -> 96 | Log.method('Storage#set', this, arguments) 97 | @_items ?= {} 98 | for key, value of items 99 | @_items[key] = value 100 | Promise.resolve(items) 101 | 102 | ###* 103 | # Remove items by keys from the storage. 104 | # @param {(string|string[]|null)} keys The keys to remove, or null for all. 105 | # @returns {Promise} A promise that fulfills on successful removal. 106 | ### 107 | remove: (keys) -> 108 | Log.method('Storage#remove', this, arguments) 109 | if @_items? 110 | if not keys? 111 | @_items = {} 112 | else if Array.isArray(keys) 113 | for key in keys 114 | delete @_items[key] 115 | else 116 | delete @_items[keys] 117 | Promise.resolve() 118 | 119 | ###* 120 | # @callback watchCallback 121 | # @param {Object.} map A map of key-value pairs just changed. 122 | ### 123 | 124 | ###* 125 | # Watch for any changes to the storage. 126 | # @param {(string|string[]|null)} keys The keys to watch, or null for all. 127 | # @param {watchCallback} callback Called everytime something changes. 128 | # @returns {function} Calling the returned function will stop watching. 129 | ### 130 | watch: (keys, callback) -> 131 | Log.method('Storage#watch', this, arguments) 132 | return (-> null) 133 | 134 | ###* 135 | # Apply WriteOperations to the storage. 136 | # @param {WriteOperations|{changes: Object.}} operations The 137 | # operations to apply, or the changes to be applied. If changes is provided, 138 | # the operations are calculated by Storage.operationsForChanges, with extra 139 | # fields passed through as the second argument. 140 | # @returns {Promise} A promise that fulfills on operation success. 141 | ### 142 | apply: (operations) -> 143 | if 'changes' of operations 144 | operations = Storage.operationsForChanges(operations.changes, operations) 145 | @set(operations.set).then(=> @remove(operations.remove)).return(operations) 146 | 147 | module.exports = Storage 148 | -------------------------------------------------------------------------------- /omega-target/src/utils.coffee: -------------------------------------------------------------------------------- 1 | exports.Promise = require('bluebird') 2 | -------------------------------------------------------------------------------- /omega-web/.gitattributes: -------------------------------------------------------------------------------- 1 | lib/* linguist-vendored 2 | -------------------------------------------------------------------------------- /omega-web/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /tmp 3 | -------------------------------------------------------------------------------- /omega-web/Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = require('load-grunt-config') 2 | -------------------------------------------------------------------------------- /omega-web/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "switchyomega", 3 | "version": "0.0.1", 4 | "authors": [ 5 | "FelisCatus " 6 | ], 7 | "description": "The ultimate proxy switcher.", 8 | "keywords": [ 9 | "proxy", 10 | "pac", 11 | "extension" 12 | ], 13 | "license": "GPL", 14 | "homepage": "https://github.com/FelisCatus/SwitchyOmega", 15 | "private": true, 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "bower_components", 20 | "test", 21 | "tests" 22 | ], 23 | "dependencies": { 24 | "angular": "~1.7.2", 25 | "angular-animate": "~1.7.2", 26 | "angular-loader": "~1.7.2", 27 | "angular-i18n": "~1.7.2", 28 | "angular-sanitize": "~1.7.2", 29 | 30 | "angular-bootstrap": "~0.12.0", 31 | "angular-ui-router": "~0.2.18", 32 | "bootstrap": "~3.3.2", 33 | "script.js": "~2.5.3", 34 | "ngprogress": "~1.0.4", 35 | "angular-ui-sortable": "~0.13.3", 36 | "jsondiffpatch": "~0.1.7", 37 | "angular-spectrum-colorpicker": "~1.3.5", 38 | "blob": "*", 39 | "FileSaver": "=1.3.3", 40 | "angular-ui-utils": "bower-validate", 41 | "angular-ladda": "~0.2.1", 42 | "shepherd.js": "~0.5.1", 43 | "jqueryui-touch-punch": "*" 44 | }, 45 | "exportsOverride": { 46 | "script.js": { 47 | "": "dist/*.min.js" 48 | }, 49 | "jquery": { 50 | "": "dist/jquery.min.js" 51 | }, 52 | "jquery-ui": { 53 | "": [] 54 | }, 55 | "jqueryui-touch-punch": { 56 | "": "jquery.ui.touch-punch.min.js" 57 | }, 58 | "angular": { 59 | "": "angular.min.js" 60 | }, 61 | "angular-animate": { 62 | "": "*.min.js" 63 | }, 64 | "angular-bootstrap": { 65 | "": "ui-bootstrap-tpls.min.js" 66 | }, 67 | "angular-i18n": { 68 | "": [ 69 | "angular-locale_en-us.js", 70 | "angular-locale_zh-cn.js", 71 | "angular-locale_zh-hk.js", 72 | "angular-locale_zh-tw.js" 73 | ] 74 | }, 75 | "angular-loader": { 76 | "": "*.min.js" 77 | }, 78 | "angular-sanitize": { 79 | "": "*.min.js" 80 | }, 81 | "angular-spectrum-colorpicker": { 82 | "": "dist/*.min.js" 83 | }, 84 | "angular-ui-router": { 85 | "": "release/*.min.js" 86 | }, 87 | "angular-ui-sortable": { 88 | "": "*.min.js" 89 | }, 90 | "angular-ui-utils": { 91 | "": "*.min.js" 92 | }, 93 | "bootstrap": { 94 | "css": "dist/css/bootstrap.min.css", 95 | "fonts": "dist/fonts/*", 96 | "js": "js/dropdown.*" 97 | }, 98 | "ngprogress": { 99 | "": "build/*.min.js" 100 | }, 101 | "jsondiffpatch": { 102 | "": "public/build/jsondiffpatch.min.js" 103 | }, 104 | "shepherd.js": { 105 | "": [ 106 | "*.min.js", 107 | "css/shepherd-theme-arrows.css" 108 | ] 109 | }, 110 | "spectrum": { 111 | "": [ 112 | "*.js", 113 | "*.css" 114 | ] 115 | }, 116 | "FileSaver": { 117 | "": "*.min.js" 118 | }, 119 | "blob": { 120 | "": "*.js" 121 | }, 122 | "ladda": { 123 | "": [ 124 | "dist/ladda-themeless.min.css", 125 | "dist/ladda.min.js" 126 | ] 127 | } 128 | }, 129 | "resolutions": { 130 | "angular": "~1.7.2" 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /omega-web/grunt/aliases.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | default: [ 3 | 'copy' 4 | 'jade' 5 | 'less' 6 | 'autoprefixer' 7 | 'coffeelint' 8 | 'coffee' 9 | 'bower' 10 | ] 11 | test: ['mochaTest'] 12 | -------------------------------------------------------------------------------- /omega-web/grunt/autoprefixer.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | options: 3 | map: false 4 | unprefixed: 5 | expand: true 6 | cwd: 'tmp/css' 7 | src: '**/*.css' 8 | dest: 'build/css/' 9 | -------------------------------------------------------------------------------- /omega-web/grunt/bower.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | install: 3 | options: 4 | copy: true 5 | targetDir: 'build/lib/' 6 | layout: 'byComponent' 7 | -------------------------------------------------------------------------------- /omega-web/grunt/coffee.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | web: 3 | expand: true 4 | cwd: 'src/coffee' 5 | src: ['**/*.coffee'] 6 | dest: 'build/js/' 7 | ext: '.js' 8 | web_omega: 9 | files: 10 | 'build/js/omega.js': 'src/omega/**/*.coffee' 11 | -------------------------------------------------------------------------------- /omega-web/grunt/coffeelint.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | options: 3 | arrow_spacing: level: 'error' 4 | colon_assignment_spacing: 5 | level: 'error' 6 | spacing: 7 | left: 0 8 | right: 1 9 | missing_fat_arrows: level: 'warn' 10 | no_empty_functions: level: 'error' 11 | no_empty_param_list: level: 'error' 12 | no_interpolation_in_single_quotes: level: 'error' 13 | no_stand_alone_at: level: 'error' 14 | space_operators: level: 'error' 15 | # https://github.com/clutchski/coffeelint/issues/525 16 | indentation: level: 'ignore' 17 | 18 | gruntfile: ['Gruntfile.coffee'] 19 | tasks: ['grunt/**/*.coffee'] 20 | src: ['src/**/*.coffee'] 21 | -------------------------------------------------------------------------------- /omega-web/grunt/copy.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | pac: 3 | files: 4 | 'build/js/omega_pac.min.js': 'node_modules/omega-pac/omega_pac.min.js' 5 | lib: 6 | expand: true 7 | cwd: 'lib' 8 | src: ['**/*'] 9 | dest: 'build/lib/' 10 | img: 11 | expand: true 12 | cwd: 'img' 13 | src: ['**/*'] 14 | dest: 'build/img/' 15 | popup: 16 | expand: true 17 | cwd: 'src/popup' 18 | src: ['**/*'] 19 | dest: 'build/popup/' 20 | -------------------------------------------------------------------------------- /omega-web/grunt/jade.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | options: 3 | pretty: true 4 | web: 5 | files: [ 6 | { 7 | expand: true 8 | dest: 'build/' 9 | cwd: 'src/' 10 | ext: '.html' 11 | src: '*.jade' 12 | } 13 | { 14 | expand: true 15 | dest: 'build/partials/' 16 | cwd: 'src/partials' 17 | ext: '.html' 18 | src: ['*.jade'] 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /omega-web/grunt/less.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | web_options: 3 | files: 4 | 'tmp/css/options.css': 'src/less/options.less' 5 | web_popup: 6 | files: 7 | 'tmp/css/popup.css': 'src/less/popup.less' 8 | -------------------------------------------------------------------------------- /omega-web/grunt/mochaTest.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | test: 3 | options: 4 | reporter: 'spec' 5 | require: 'coffee-script/register' 6 | src: ['test/**/*.coffee'] 7 | -------------------------------------------------------------------------------- /omega-web/grunt/ngAnnotate.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | options: 3 | singleQuotes: true 4 | app: 5 | files: 6 | 'build/js/omega.ngmin.js': 'build/js/omega.js' 7 | -------------------------------------------------------------------------------- /omega-web/grunt/watch.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | grunt: 3 | options: 4 | reload: true 5 | files: 6 | 'grunt/*' 7 | tasks: ['coffeelint:tasks', 'default'] 8 | copy_pac: 9 | files: 10 | 'node_modules/omega-pac/omega_pac.min.js' 11 | tasks: 'copy:pac' 12 | copy_lib: 13 | files: 14 | 'lib/**/*' 15 | tasks: 'copy:lib' 16 | copy_img: 17 | files: 18 | 'img/**/*' 19 | tasks: 'copy:img' 20 | copy_popup: 21 | files: 22 | 'src/popup/**/*' 23 | tasks: 'copy:popup' 24 | jade: 25 | files: ['src/**/*.jade'] 26 | tasks: 'jade' 27 | less: 28 | files: 29 | 'src/less/**/*.less' 30 | tasks: ['less', 'autoprefixer'] 31 | coffeelint: 32 | files: 'src/**/*.coffee' 33 | tasks: ['coffeelint'] 34 | coffee: 35 | files: [ 36 | 'src/coffee/**/*.coffee' 37 | 'src/omega/**/*.coffee' 38 | ] 39 | tasks: ['coffee'] 40 | -------------------------------------------------------------------------------- /omega-web/img/icons/draw_omega.js: -------------------------------------------------------------------------------- 1 | var drawOmega = function (ctx, outerCircleColor, innerCircleColor) { 2 | ctx.globalCompositeOperation = "source-over"; 3 | ctx.fillStyle = outerCircleColor; 4 | ctx.beginPath(); 5 | ctx.arc(0.5, 0.5, 0.5, 0, Math.PI * 2, true); 6 | ctx.closePath(); 7 | ctx.fill(); 8 | 9 | if (innerCircleColor != null) { 10 | ctx.fillStyle = innerCircleColor; 11 | } else { 12 | ctx.globalCompositeOperation = "destination-out"; 13 | } 14 | 15 | ctx.beginPath(); 16 | ctx.arc(0.5, 0.5, 0.25, 0, Math.PI * 2, true); 17 | ctx.closePath(); 18 | ctx.fill(); 19 | }; 20 | -------------------------------------------------------------------------------- /omega-web/img/icons/omega-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FelisCatus/SwitchyOmega/19f9d73f20125b6164fb95d17cd4cc6659df147c/omega-web/img/icons/omega-128.png -------------------------------------------------------------------------------- /omega-web/img/icons/omega-128.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /omega-web/img/icons/omega-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FelisCatus/SwitchyOmega/19f9d73f20125b6164fb95d17cd4cc6659df147c/omega-web/img/icons/omega-48.png -------------------------------------------------------------------------------- /omega-web/img/icons/omega-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FelisCatus/SwitchyOmega/19f9d73f20125b6164fb95d17cd4cc6659df147c/omega-web/img/icons/omega-64.png -------------------------------------------------------------------------------- /omega-web/img/icons/omega-action-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FelisCatus/SwitchyOmega/19f9d73f20125b6164fb95d17cd4cc6659df147c/omega-web/img/icons/omega-action-16.png -------------------------------------------------------------------------------- /omega-web/img/icons/omega-action-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FelisCatus/SwitchyOmega/19f9d73f20125b6164fb95d17cd4cc6659df147c/omega-web/img/icons/omega-action-19.png -------------------------------------------------------------------------------- /omega-web/img/icons/omega-action-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FelisCatus/SwitchyOmega/19f9d73f20125b6164fb95d17cd4cc6659df147c/omega-web/img/icons/omega-action-24.png -------------------------------------------------------------------------------- /omega-web/img/icons/omega-action-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FelisCatus/SwitchyOmega/19f9d73f20125b6164fb95d17cd4cc6659df147c/omega-web/img/icons/omega-action-32.png -------------------------------------------------------------------------------- /omega-web/img/icons/omega-action.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /omega-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "omega-web", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "chai": "~1.9.1", 7 | "coffeelint": "^1.16.0", 8 | "grunt": "^0.4.5", 9 | "grunt-autoprefixer": "^1.0.1", 10 | "grunt-bower-task": "^0.5.0", 11 | "grunt-coffeelint": "^0.0.13", 12 | "grunt-contrib-coffee": "^0.11.1", 13 | "grunt-contrib-concat": "^0.5.0", 14 | "grunt-contrib-copy": "^0.5.0", 15 | "grunt-contrib-jade": "^0.12.0", 16 | "grunt-contrib-less": "^0.11.4", 17 | "grunt-contrib-watch": "^0.6.1", 18 | "grunt-mocha-test": "~0.11.0", 19 | "grunt-ng-annotate": "^0.3.2", 20 | "load-grunt-config": "^0.13.1", 21 | "omega-pac": "../omega-pac" 22 | }, 23 | "scripts": { 24 | "dev": "npm link omega-pac && npm link" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /omega-web/src/coffee/log_error.coffee: -------------------------------------------------------------------------------- 1 | window.onerror = (message, url, line, col, err) -> 2 | log = localStorage['log'] || '' 3 | if err?.stack 4 | log += err.stack + '\n\n' 5 | else 6 | log += "#{url}:#{line}:#{col}:\t#{message}\n\n" 7 | localStorage['log'] = log 8 | return 9 | -------------------------------------------------------------------------------- /omega-web/src/coffee/omega_decoration.coffee: -------------------------------------------------------------------------------- 1 | orderForType = 2 | 'FixedProfile': -2000 3 | 'PacProfile': -1000 4 | 'VirtualProfile': 1000 5 | 'SwitchProfile': 2000 6 | 'RuleListProfile': 3000 7 | 8 | angular.module('omegaDecoration', []).value('profileIcons', { 9 | 'DirectProfile': 'glyphicon-transfer' 10 | 'SystemProfile': 'glyphicon-off' 11 | 'AutoDetectProfile': 'glyphicon-file' 12 | 'FixedProfile': 'glyphicon-globe' 13 | 'PacProfile': 'glyphicon-file' 14 | 'VirtualProfile': 'glyphicon-question-sign' 15 | 'RuleListProfile': 'glyphicon-list' 16 | 'SwitchProfile': 'glyphicon-retweet' 17 | }).constant('profileOrder', (a, b) -> 18 | diff = (orderForType[a.profileType] | 0) - (orderForType[b.profileType] | 0) 19 | return diff if diff != 0 20 | if a.name == b.name 21 | 0 22 | else if a.name < b.name 23 | -1 24 | else 25 | 1 26 | ).constant('getVirtualTarget', (profile, options) -> 27 | if profile?.profileType == 'VirtualProfile' 28 | options?['+' + profile.defaultProfileName] 29 | ).directive('omegaProfileIcon', (profileIcons, getVirtualTarget) -> 30 | restrict: 'A' 31 | template: ''' 32 | 35 | 36 | ''' 37 | scope: 38 | 'profile': '=?omegaProfileIcon' 39 | 'icon': '=?icon' 40 | 'color': '=?color' 41 | 'options': '=options' 42 | link: (scope, element, attrs, ngModel) -> 43 | scope.profileIcons = profileIcons 44 | scope.isVirtual = (profile) -> 45 | profile?.profileType == 'VirtualProfile' 46 | scope.getIcon = (profile) -> 47 | type = profile?.profileType 48 | type = getVirtualTarget(profile, scope.options)?.profileType ? type 49 | profileIcons[type] 50 | scope.getColor = (profile) -> 51 | color = undefined 52 | while profile 53 | color = profile.color 54 | profile = getVirtualTarget(profile, scope.options) 55 | color 56 | ).directive('omegaProfileInline', -> 57 | restrict: 'A' 58 | template: ''' 59 | 60 | {{dispName ? dispName(profile) : profile.name}} 61 | ''' 62 | scope: 63 | 'profile': '=omegaProfileInline' 64 | 'dispName': '=?dispName' 65 | 'options': '=options' 66 | ).directive('omegaHtml', ($compile) -> 67 | restrict: 'A' 68 | link: (scope, element, attrs, ngModel) -> 69 | locals = 70 | $profile: (profile = 'profile', dispName = 'dispNameFilter', 71 | options = 'options') -> 72 | """ 73 | 75 | """ 76 | getHtml = -> scope.$eval(attrs.omegaHtml, locals) 77 | scope.$watch getHtml, (html) -> 78 | element.html(html) 79 | $compile(element.contents())(scope) 80 | ).directive('omegaProfileSelect', ($timeout, profileIcons) -> 81 | restrict: 'A' 82 | templateUrl: 'partials/omega_profile_select.html' 83 | require: '?ngModel' 84 | scope: 85 | 'profiles': '&omegaProfileSelect' 86 | 'defaultText': '@?defaultText' 87 | 'dispName': '=?dispName' 88 | 'options': '=options' 89 | link: (scope, element, attrs, ngModel) -> 90 | scope.profileIcons = profileIcons 91 | scope.currentProfiles = [] 92 | scope.dispProfiles = undefined 93 | updateView = -> 94 | scope.profileIcon = '' 95 | for profile in scope.currentProfiles 96 | if profile.name == scope.profileName 97 | scope.selectedProfile = profile 98 | scope.profileIcon = profileIcons[profile.profileType] 99 | break 100 | scope.$watch(scope.profiles, ((profiles) -> 101 | scope.currentProfiles = profiles || [] 102 | if scope.dispProfiles? 103 | scope.dispProfiles = currentProfiles 104 | updateView() 105 | ), true) 106 | 107 | scope.toggled = (open) -> 108 | if open and not scope.dispProfiles? 109 | scope.dispProfiles = scope.currentProfiles 110 | scope.toggled = undefined 111 | 112 | if ngModel 113 | ngModel.$render = -> 114 | scope.profileName = ngModel.$viewValue 115 | updateView() 116 | 117 | scope.setProfileName = (name) -> 118 | if ngModel 119 | ngModel.$setViewValue(name) 120 | ngModel.$render() 121 | 122 | scope.getName = (profile) -> 123 | if profile 124 | scope.dispName(profile) || profile.name 125 | ) 126 | -------------------------------------------------------------------------------- /omega-web/src/coffee/options.coffee: -------------------------------------------------------------------------------- 1 | this.UglifyJS_NoUnsafeEval = true 2 | $script 'lib/angular-loader/angular-loader.min.js', 3 | 'angular-loader' 4 | $script 'lib/jquery/jquery.min.js', 'jquery' 5 | $script 'js/omega_pac.min.js', 'omega-pac' 6 | $script 'lib/FileSaver/FileSaver.min.js', 'filesaver' 7 | $script 'lib/blob/Blob.js', 'blob' 8 | $script 'lib/spin.js/spin.js', -> 9 | $script 'lib/ladda/ladda.min.js', -> 10 | $script.ready ['angular-loader'], -> 11 | $script 'lib/angular-ladda/angular-ladda.min.js', 'angular-ladda' 12 | 13 | $script.ready ['angular-loader'], -> 14 | angular.module 'omega', ['ngLocale', 'ngAnimate', 'ngSanitize', 15 | 'ui.bootstrap', 'ui.router', 'ngProgress', 'ui.sortable', 16 | 'angularSpectrumColorpicker', 'ui.validate', 'angular-ladda', 'omegaTarget', 17 | 'omegaDecoration'] 18 | $script.ready ['omega-pac'], -> 19 | $script 'js/omega.js', 'omega' 20 | 21 | $script([ 22 | 'js/omega_target_web.js' 23 | 'js/omega_decoration.js' 24 | 'lib/angular-animate/angular-animate.min.js' 25 | 'lib/angular-bootstrap/ui-bootstrap-tpls.min.js' 26 | 'lib/ngprogress/ngProgress.min.js' 27 | 'lib/angular-ui-sortable/sortable.min.js' 28 | 'lib/angular-ui-utils/validate.min.js' 29 | 'lib/jsondiffpatch/jsondiffpatch.min.js' 30 | 'lib/angular-spectrum-colorpicker/angular-spectrum-colorpicker.min.js' 31 | ], 'omega-deps') 32 | $script.ready ['jquery'], -> 33 | $script 'lib/jquery-ui-1.10.4.custom.min.js', 'jquery-ui-base' 34 | $script 'lib/spectrum/spectrum.js', 'spectrum' 35 | $script.ready ['jquery-ui-base'], -> 36 | $script 'lib/jqueryui-touch-punch/jquery.ui.touch-punch.min.js', 'jquery-ui' 37 | 38 | $script.ready ['angular-loader', 'jquery'], -> 39 | $script 'lib/angular/angular.min.js', 'angular' 40 | 41 | $script.ready ['angular'], -> 42 | $script 'lib/angular-ui-router/angular-ui-router.min.js', 'angular-ui-router' 43 | $script 'lib/angular-sanitize/angular-sanitize.min.js', 'angular-sanitize' 44 | 45 | locales = 46 | '': 'en-us' 47 | 48 | 'en': 'en-us' 49 | 50 | 'zh': 'zh-cn' 51 | 'zh-hans': 'zh-cn' 52 | 'zh-hant': 'zh-tw' 53 | 'zh-cn': 'zh-cn' 54 | 'zh-hk': 'zh-hk' 55 | 'zh-tw': 'zh-tw' 56 | 57 | lang = navigator.language 58 | lang1 = navigator.language?.split('-')[0] || '' 59 | locale = locales[lang] || locales[lang1] || locales[''] 60 | $script 'lib/angular-i18n/angular-locale_' + locale + '.js', 'angular-i18n' 61 | 62 | $script.ready ['angular', 'omega', 'omega-deps', 'angular-ui-router', 63 | 'jquery-ui', 'spectrum', 'filesaver', 'blob', 'angular-ladda', 64 | 'angular-sanitize', 'angular-i18n'], -> 65 | angular.bootstrap document, ['omega'] 66 | -------------------------------------------------------------------------------- /omega-web/src/coffee/options_guide.coffee: -------------------------------------------------------------------------------- 1 | $script 'lib/tether/tether.js', -> 2 | $script 'lib/shepherd.js/shepherd.min.js', -> 3 | tr = chrome.i18n.getMessage.bind(chrome.i18n) 4 | tour = new Shepherd.Tour 5 | defaults: 6 | classes: 'shepherd-theme-arrows' 7 | scrollTo: true 8 | 9 | targetAnchorClick = 10 | selector: '.shepherd-target a' 11 | event: 'click' 12 | 13 | tour.addStep('fixed-profile-step', 14 | text: tr('options_guide_fixedProfileStep') 15 | attachTo: '.nav-profile[data-profile-type="FixedProfile"] right' 16 | scrollTo: false 17 | advanceOn: targetAnchorClick 18 | buttons: [ 19 | text: tr('options_guideNext') 20 | action: tour.next 21 | ] 22 | ) 23 | 24 | tour.addStep 'fixed-servers-step', 25 | text: tr('options_guide_fixedServersStep') 26 | attachTo: '.fixed-servers top' 27 | scrollTo: false 28 | buttons: [ 29 | text: tr('options_guideNext') 30 | action: tour.next 31 | ] 32 | 33 | tour.addStep 'auto-switch-profile-step', 34 | text: tr('options_guide_autoSwitchProfileStep') 35 | attachTo: '.nav-profile[data-profile-type="SwitchProfile"] right' 36 | scrollTo: false 37 | advanceOn: targetAnchorClick 38 | buttons: [ 39 | text: tr('options_guideNext') 40 | action: tour.next 41 | ] 42 | 43 | tour.addStep 'add-more-profiles-step', 44 | text: tr('options_guide_addMoreProfilesStep') 45 | attachTo: '.nav-new-profile right' 46 | scrollTo: false 47 | advanceOn: targetAnchorClick 48 | buttons: [ 49 | text: tr('options_guideDone') 50 | action: tour.next 51 | ] 52 | 53 | tour.start() 54 | -------------------------------------------------------------------------------- /omega-web/src/coffee/switch_profile_guide.coffee: -------------------------------------------------------------------------------- 1 | $script 'lib/tether/tether.js', -> 2 | $script 'lib/shepherd.js/shepherd.min.js', -> 3 | return if jQuery('.switch-rule-row').length == 0 4 | jQuery('html, body').scrollTop(0) 5 | tr = chrome.i18n.getMessage.bind(chrome.i18n) 6 | tour = new Shepherd.Tour 7 | defaults: 8 | classes: 'shepherd-theme-arrows' 9 | 10 | tour.addStep 'condition-step', 11 | text: tr('options_guide_conditionStep') 12 | attachTo: '.switch-rule-row bottom' 13 | buttons: [ 14 | { 15 | text: tr('options_guideSkip') 16 | action: tour.cancel 17 | classes: 'shepherd-button-secondary' 18 | } 19 | { 20 | text: tr('options_guideNext') 21 | action: tour.next 22 | } 23 | ] 24 | 25 | conditionTypeStep = tour.addStep('condition-type-step', 26 | text: tr('options_guide_conditionTypeStep') 27 | attachTo: '.condition-type-th bottom' 28 | advanceOn: 29 | selector: '.close-condition-help' 30 | event: 'click' 31 | buttons: [ 32 | text: tr('options_guideNext') 33 | action: tour.next 34 | ] 35 | ) 36 | 37 | conditionTypeStep.on 'show', -> 38 | jQuery('.toggle-condition-help').one 'click', -> 39 | return unless conditionTypeStep.isOpen() 40 | jQuery('.shepherd-step.shepherd-enabled').hide() 41 | jQuery('.toggle-condition-help, .close-condition-help').one 'click', -> 42 | tour.next() 43 | 44 | tour.addStep 'condition-profile-step', 45 | text: tr('options_guide_conditionProfileStep') 46 | attachTo: '.switch-rule-row-target bottom' 47 | buttons: [ 48 | text: tr('options_guideNext') 49 | action: tour.next 50 | ] 51 | 52 | defaultStep = tour.addStep 'switch-default-step', 53 | text: tr('options_guide_switchDefaultStep') 54 | attachTo: '.switch-default-row top' 55 | buttons: [ 56 | text: tr('options_guideNext') 57 | action: tour.next 58 | ] 59 | 60 | defaultStep.on 'show', -> 61 | row = jQuery('.switch-default-row') 62 | scrollTop = row.offset().top + row.height() - $(window).height() 63 | scrollTop = 0 if scrollTop < 0 64 | jQuery('html, body').animate({scrollTop: scrollTop}) 65 | 66 | tour.addStep 'apply-switch-profile-step', 67 | text: tr('options_guide_applySwitchProfileStep') 68 | attachTo: 'body top' 69 | scrollTo: false 70 | classes: 'shepherd-theme-arrows fixed-top-right' 71 | buttons: [ 72 | text: tr('options_guideDone') 73 | action: tour.next 74 | ] 75 | 76 | Shepherd.activeTour?.cancel() 77 | tour.start() 78 | -------------------------------------------------------------------------------- /omega-web/src/less/common.less: -------------------------------------------------------------------------------- 1 | /* angular */ 2 | 3 | .ng-hide { 4 | display: none !important; 5 | } 6 | 7 | /* helpers */ 8 | 9 | .clear-padding { 10 | padding: 0 !important; 11 | } 12 | 13 | /* basic */ 14 | 15 | a[role="button"] { 16 | cursor: pointer; 17 | } 18 | 19 | .form-control.ng-invalid { 20 | border-color: #a94442; 21 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 22 | 23 | &:hover { 24 | border-color: #843534; 25 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #CE8483; 26 | } 27 | } 28 | 29 | .disabled .btn { 30 | background-color: #ddd !important; 31 | border-color: #ccc !important; 32 | cursor: not-allowed !important; 33 | pointer-events: none !important; 34 | opacity: .65 !important; 35 | box-shadow: none !important; 36 | } 37 | 38 | /* profile */ 39 | 40 | .virtual-profile-icon { 41 | border: dotted 1px; 42 | margin: -1px; 43 | } 44 | 45 | .profile-inline { 46 | background-color: #eee; 47 | color: #333; 48 | padding: 0 5px; 49 | } 50 | 51 | /* omega-profile-select */ 52 | 53 | .omega-profile-select { 54 | width: 100%; 55 | .dropdown-menu>li>a { 56 | max-width: none !important; 57 | } 58 | 59 | .btn { 60 | width: 100%; 61 | display: block; 62 | text-align: left; 63 | text-align: initial; 64 | position: relative; 65 | padding-right: 25px; 66 | 67 | .caret { 68 | position: absolute; 69 | top: 50%; 70 | right: 12px; 71 | margin-top: -2px; 72 | vertical-align: middle; 73 | } 74 | } 75 | 76 | .dropdown-menu { 77 | width: 100%; 78 | cursor: pointer; 79 | } 80 | } 81 | 82 | .monospace { 83 | font-family: Menlo,Monaco,Consolas,"Courier New",monospace !important; 84 | } 85 | -------------------------------------------------------------------------------- /omega-web/src/less/popup.less: -------------------------------------------------------------------------------- 1 | @import 'common.less'; 2 | 3 | /* popup */ 4 | 5 | body { 6 | margin: 0; 7 | padding: 0; 8 | min-width: 180px; 9 | } 10 | 11 | body.with-condition-form { 12 | } 13 | 14 | .condition-form { 15 | min-width: 360px; 16 | } 17 | 18 | .nav { 19 | margin-bottom: 0; 20 | } 21 | 22 | li > a { 23 | text-overflow: ellipsis; 24 | overflow: hidden; 25 | max-width: 20em; 26 | } 27 | 28 | .shortcut-help { 29 | .monospace(); 30 | border: solid 1px #000; 31 | border-radius: 2px; 32 | display: inline-block; 33 | color: #000; 34 | box-shadow: 1px 1px; 35 | width: 1em; 36 | height: 1em; 37 | line-height: 1em; 38 | text-align: center; 39 | margin-top: -3px; 40 | } 41 | 42 | .nav-pills.nav-stacked { 43 | > li > a { 44 | padding: 5px 25px 5px 8px; 45 | white-space: nowrap; 46 | cursor: pointer; 47 | 48 | .glyphicon { 49 | margin-right: 6px; 50 | } 51 | 52 | &.profile-with-default-edit { 53 | padding-right: 32px; 54 | position: relative; 55 | .dropdown-toggle { 56 | margin: -5px 0; 57 | color: inherit; 58 | padding: 5px 8px 3px; 59 | min-width: 20px; 60 | position: absolute; 61 | right: 0; 62 | 63 | .glyphicon { 64 | margin-right: 0; 65 | } 66 | } 67 | } 68 | } 69 | 70 | > li.active .dropdown-toggle { 71 | border-color: transparent !important; 72 | border-left: solid 1px !important; 73 | background: none !important; 74 | border-radius: 0 !important; 75 | } 76 | } 77 | 78 | .divider { 79 | height: 1px; 80 | overflow: hidden; 81 | background-color: #E5E5E5; 82 | } 83 | 84 | .temp-rule a { 85 | padding-left: 8px !important; 86 | box-shadow: none !important; 87 | } 88 | 89 | li .dropdown-menu { 90 | position: static; 91 | top: initial; 92 | top: -moz-initial; 93 | margin: 0 5px !important; 94 | float: none; 95 | 96 | a { 97 | cursor: pointer; 98 | } 99 | } 100 | 101 | .current-domain { 102 | color: #08C; 103 | } 104 | 105 | select, textarea, input { 106 | margin-bottom: 0px !important; 107 | } 108 | 109 | form { 110 | margin: 10px; 111 | } 112 | 113 | legend, 114 | .well { 115 | margin-bottom: 10px; 116 | } 117 | 118 | .well { 119 | padding: 10px; 120 | } 121 | 122 | .condition-controls, .proxy-not-controllable-controls { 123 | .btn-primary { 124 | float: right; 125 | } 126 | } 127 | 128 | .external-profile { 129 | a { 130 | padding-right: 10px !important; 131 | } 132 | 133 | form { 134 | margin: 0; 135 | padding: 0; 136 | display: inline-block; 137 | } 138 | 139 | .form-control { 140 | display: inline-block; 141 | padding: 0; 142 | height: 2em; 143 | width: ~"calc(100% - 1em - 8px)"; 144 | } 145 | } 146 | 147 | .proxy-not-controllable { 148 | min-width: 400px; 149 | padding: 10px 10px; 150 | 151 | .proxy-not-controllable-controls { 152 | margin-bottom: 0; 153 | } 154 | } 155 | 156 | .request-info-details { 157 | min-width: 360px; 158 | } 159 | -------------------------------------------------------------------------------- /omega-web/src/omega/app.coffee: -------------------------------------------------------------------------------- 1 | angular.module('omega').constant('builtinProfiles', 2 | OmegaPac.Profiles.builtinProfiles) 3 | 4 | profileColors = [ 5 | '#9ce', '#9d9', '#fa8', '#fe9', '#d497ee', '#47b', '#5b5', '#d63', '#ca0' 6 | ] 7 | colors = [].concat(profileColors) 8 | profileColorPalette = (colors.splice(0, 3) while colors.length) 9 | 10 | angular.module('omega').constant('profileColors', profileColors) 11 | angular.module('omega').constant('profileColorPalette', profileColorPalette) 12 | 13 | attachedPrefix = '__ruleListOf_' 14 | angular.module('omega').constant 'getAttachedName', (name) -> 15 | attachedPrefix + name 16 | angular.module('omega').constant 'getParentName', (name) -> 17 | if name.indexOf(attachedPrefix) == 0 18 | name.substr(attachedPrefix.length) 19 | else 20 | undefined 21 | 22 | charCodeUnderscore = '_'.charCodeAt(0) 23 | angular.module('omega').constant 'charCodeUnderscore', charCodeUnderscore 24 | angular.module('omega').constant 'isProfileNameHidden', (name) -> 25 | # Hide profiles beginning with underscore. 26 | name.charCodeAt(0) == charCodeUnderscore 27 | angular.module('omega').constant 'isProfileNameReserved', (name) -> 28 | # Reserve profile names beginning with double-underscore. 29 | (name.charCodeAt(0) == charCodeUnderscore and 30 | name.charCodeAt(1) == charCodeUnderscore) 31 | 32 | angular.module('omega').config ($stateProvider, $urlRouterProvider, 33 | $httpProvider, $animateProvider, $compileProvider) -> 34 | $compileProvider.aHrefSanitizationWhitelist( 35 | /^\s*(https?|ftp|mailto|chrome-extension|moz-extension):/) 36 | $compileProvider.imgSrcSanitizationWhitelist( 37 | /^\s*(https?|local|data|chrome-extension|moz-extension):/) 38 | $animateProvider.classNameFilter(/angular-animate/) 39 | 40 | $urlRouterProvider.otherwise '/about' 41 | 42 | $urlRouterProvider.otherwise ($injector, $location) -> 43 | if $location.path() == '' 44 | $injector.get('omegaTarget').lastUrl() || '/about' 45 | else 46 | '/about' 47 | 48 | $stateProvider 49 | .state('ui', 50 | url: '/ui' 51 | templateUrl: 'partials/ui.html' 52 | #controller: 'UiCtrl' 53 | ).state('general', 54 | url: '/general' 55 | templateUrl: 'partials/general.html' 56 | #controller: 'GeneralCtrl' 57 | ).state('io', 58 | url: '/io' 59 | templateUrl: 'partials/io.html' 60 | controller: 'IoCtrl' 61 | ).state('profile', 62 | url: '/profile/*name' 63 | templateUrl: 'partials/profile.html' 64 | controller: 'ProfileCtrl' 65 | ).state('about', 66 | url: '/about' 67 | templateUrl: 'partials/about.html' 68 | controller: 'AboutCtrl' 69 | ) 70 | 71 | angular.module('omega').factory '$exceptionHandler', ($log) -> 72 | return (exception, cause) -> 73 | return if exception.message == 'transition aborted' 74 | return if exception.message == 'transition superseded' 75 | return if exception.message == 'transition prevented' 76 | return if exception.message == 'transition failed' 77 | $log.error(exception, cause) 78 | 79 | angular.module('omega').factory 'omegaDebug', ($window, $rootScope, 80 | $injector) -> 81 | omegaDebug = $window.OmegaDebug ? {} 82 | 83 | omegaDebug.downloadLog ?= -> 84 | downloadFile = $injector.get('downloadFile') ? saveAs 85 | blob = new Blob [localStorage['log']], {type: "text/plain;charset=utf-8"} 86 | downloadFile(blob, "OmegaLog_#{Date.now()}.txt") 87 | 88 | omegaDebug.reportIssue ?= -> 89 | $window.open( 90 | 'https://github.com/FelisCatus/SwitchyOmega/issues/new?title=&body=') 91 | return 92 | 93 | omegaDebug.resetOptions ?= -> 94 | $rootScope.resetOptions() 95 | 96 | omegaDebug 97 | 98 | angular.module('omega').factory 'downloadFile', -> 99 | if browser?.downloads?.download? 100 | return (blob, filename) -> 101 | url = URL.createObjectURL(blob) 102 | if filename 103 | browser.downloads.download({url: url, filename: filename}) 104 | else 105 | browser.downloads.download({url: url}) 106 | else 107 | return (blob, filename) -> 108 | noAutoBom = true 109 | saveAs(blob, filename, noAutoBom) 110 | -------------------------------------------------------------------------------- /omega-web/src/omega/controllers/about.coffee: -------------------------------------------------------------------------------- 1 | angular.module('omega').controller 'AboutCtrl', ($scope, $rootScope, 2 | $modal, omegaDebug) -> 3 | 4 | $scope.downloadLog = omegaDebug.downloadLog 5 | $scope.reportIssue = omegaDebug.reportIssue 6 | 7 | $scope.showResetOptionsModal = -> 8 | $modal.open(templateUrl: 'partials/reset_options_confirm.html').result 9 | .then -> omegaDebug.resetOptions() 10 | 11 | try 12 | $scope.version = omegaDebug.getProjectVersion() 13 | catch _ 14 | $scope.version = '?.?.?' 15 | -------------------------------------------------------------------------------- /omega-web/src/omega/controllers/fixed_profile.coffee: -------------------------------------------------------------------------------- 1 | angular.module('omega').controller 'FixedProfileCtrl', ($scope, $modal, 2 | trFilter) -> 3 | $scope.urlSchemes = ['', 'http', 'https', 'ftp'] 4 | $scope.urlSchemeDefault = 'fallbackProxy' 5 | proxyProperties = 6 | '': 'fallbackProxy' 7 | 'http': 'proxyForHttp' 8 | 'https': 'proxyForHttps' 9 | 'ftp': 'proxyForFtp' 10 | $scope.schemeDisp = 11 | '': null 12 | 'http': 'http://' 13 | 'https': 'https://' 14 | 'ftp': 'ftp://' 15 | 16 | defaultPort = 17 | 'http': 80 18 | 'https': 443 19 | 'socks4': 1080 20 | 'socks5': 1080 21 | 22 | $scope.showAdvanced = false 23 | 24 | $scope.optionsForScheme = {} 25 | for scheme in $scope.urlSchemes 26 | defaultLabel = 27 | if scheme 28 | trFilter('options_protocol_useDefault') 29 | else 30 | trFilter('options_protocol_direct') 31 | $scope.optionsForScheme[scheme] = [ 32 | {label: defaultLabel, value: undefined}, 33 | {label: 'HTTP', value: 'http'}, 34 | {label: 'HTTPS', value: 'https'}, 35 | {label: 'SOCKS4', value: 'socks4'}, 36 | {label: 'SOCKS5', value: 'socks5'}, 37 | ] 38 | 39 | $scope.proxyEditors = {} 40 | 41 | socks5AuthSupported = (browser?.proxy?.register?) 42 | $scope.authSupported = { 43 | "http": true, 44 | "https": true, 45 | "socks5": socks5AuthSupported, 46 | } 47 | $scope.isProxyAuthActive = (scheme) -> 48 | return $scope.profile.auth?[proxyProperties[scheme]]? 49 | $scope.editProxyAuth = (scheme) -> 50 | prop = proxyProperties[scheme] 51 | proxy = $scope.profile[prop] 52 | scope = $scope.$new('isolate') 53 | scope.proxy = proxy 54 | auth = $scope.profile.auth?[prop] 55 | scope.auth = auth && angular.copy(auth) 56 | scope.authSupported = $scope.authSupported[proxy.scheme] 57 | scope.protocolDisp = proxy.scheme 58 | $modal.open( 59 | templateUrl: 'partials/fixed_auth_edit.html' 60 | scope: scope 61 | size: if scope.authSupported then 'sm' else 'lg' 62 | ).result.then (auth) -> 63 | if not auth?.username 64 | if $scope.profile.auth 65 | $scope.profile.auth[prop] = undefined 66 | else 67 | $scope.profile.auth ?= {} 68 | $scope.profile.auth[prop] = auth 69 | 70 | onProxyChange = (proxyEditors, oldProxyEditors) -> 71 | return unless proxyEditors 72 | for scheme in $scope.urlSchemes 73 | proxy = proxyEditors[scheme] 74 | if $scope.profile.auth and not $scope.authSupported[proxy.scheme] 75 | delete $scope.profile.auth[proxyProperties[scheme]] 76 | if not proxy.scheme 77 | if not scheme 78 | proxyEditors[scheme] = {} 79 | delete $scope.profile[proxyProperties[scheme]] 80 | continue 81 | else if not oldProxyEditors[scheme].scheme 82 | if proxy.scheme == proxyEditors[''].scheme 83 | proxy.port ?= proxyEditors[''].port 84 | proxy.port ?= defaultPort[proxy.scheme] 85 | proxy.host ?= proxyEditors[''].host ? 'example.com' 86 | $scope.profile[proxyProperties[scheme]] ?= proxy 87 | for scheme in $scope.urlSchemes 88 | do (scheme) -> 89 | $scope.$watch (-> $scope.profile[proxyProperties[scheme]]), (proxy) -> 90 | if scheme and proxy 91 | $scope.showAdvanced = true 92 | $scope.proxyEditors[scheme] = proxy ? {} 93 | $scope.$watch 'proxyEditors', onProxyChange, true 94 | 95 | onBypassListChange = (list) -> 96 | $scope.bypassList = (item.pattern for item in list).join('\n') 97 | 98 | $scope.$watch 'profile.bypassList', onBypassListChange, true 99 | 100 | $scope.$watch 'bypassList', (bypassList, oldList) -> 101 | return if not bypassList? or bypassList == oldList 102 | $scope.profile.bypassList = 103 | for entry in bypassList.split(/\r?\n/) when entry 104 | conditionType: "BypassCondition" 105 | pattern: entry 106 | -------------------------------------------------------------------------------- /omega-web/src/omega/controllers/io.coffee: -------------------------------------------------------------------------------- 1 | angular.module('omega').controller 'IoCtrl', ($scope, $rootScope, 2 | $window, $http, omegaTarget, downloadFile) -> 3 | 4 | omegaTarget.state('web.restoreOnlineUrl').then (url) -> 5 | if url 6 | $scope.restoreOnlineUrl = url 7 | 8 | $scope.exportOptions = -> 9 | $rootScope.applyOptionsConfirm().then -> 10 | plainOptions = angular.fromJson(angular.toJson($rootScope.options)) 11 | content = JSON.stringify(plainOptions) 12 | blob = new Blob [content], {type: "text/plain;charset=utf-8"} 13 | downloadFile(blob, "OmegaOptions.bak") 14 | 15 | $scope.importSuccess = -> 16 | $rootScope.showAlert( 17 | type: 'success' 18 | i18n: 'options_importSuccess' 19 | message: 'Options imported.' 20 | ) 21 | 22 | $scope.restoreLocal = (content) -> 23 | $scope.restoringLocal = true 24 | $rootScope.resetOptions(content).then(( -> 25 | $scope.importSuccess() 26 | ), -> $scope.restoreLocalError()).finally -> 27 | $scope.restoringLocal = false 28 | 29 | $scope.restoreLocalError = -> 30 | $rootScope.showAlert( 31 | type: 'error' 32 | i18n: 'options_importFormatError' 33 | message: 'Invalid backup file!' 34 | ) 35 | $scope.downloadError = -> 36 | $rootScope.showAlert( 37 | type: 'error' 38 | i18n: 'options_importDownloadError' 39 | message: 'Error downloading backup file!' 40 | ) 41 | $scope.triggerFileInput = -> 42 | angular.element('#restore-local-file').click() 43 | return 44 | $scope.restoreOnline = -> 45 | omegaTarget.state('web.restoreOnlineUrl', $scope.restoreOnlineUrl) 46 | $scope.restoringOnline = true 47 | $http( 48 | method: 'GET' 49 | url: $scope.restoreOnlineUrl 50 | cache: false 51 | timeout: 10000 52 | responseType: "text" 53 | ).then(((result) -> 54 | $rootScope.resetOptions(result.data).then (-> 55 | $scope.importSuccess() 56 | ), -> $scope.restoreLocalError() 57 | ), $scope.downloadError).finally -> 58 | $scope.restoringOnline = false 59 | 60 | $scope.enableOptionsSync = (args) -> 61 | enable = -> 62 | omegaTarget.setOptionsSync(true, args).finally -> 63 | $window.location.reload() 64 | if args?.force 65 | enable() 66 | else 67 | $rootScope.applyOptionsConfirm().then enable 68 | 69 | $scope.disableOptionsSync = -> 70 | omegaTarget.setOptionsSync(false).then -> 71 | $rootScope.applyOptionsConfirm().then -> 72 | $window.location.reload() 73 | 74 | $scope.resetOptionsSync = -> 75 | omegaTarget.resetOptionsSync().then -> 76 | $rootScope.applyOptionsConfirm().then -> 77 | $window.location.reload() 78 | -------------------------------------------------------------------------------- /omega-web/src/omega/controllers/pac_profile.coffee: -------------------------------------------------------------------------------- 1 | angular.module('omega').controller 'PacProfileCtrl', ($scope, $modal) -> 2 | # coffeelint: disable=max_line_length 3 | 4 | # https://github.com/angular/angular.js/blob/master/src/ng/directive/input.js#L13 5 | $scope.urlRegex = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/ 6 | # With the file: scheme added to the pattern: 7 | $scope.urlWithFile = /^(ftp|http|https|file):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/ 8 | 9 | # coffeelint: enable=max_line_length 10 | 11 | $scope.isFileUrl = OmegaPac.Profiles.isFileUrl 12 | $scope.pacUrlCtrl = {ctrl: null} 13 | 14 | set = OmegaPac.Profiles.referencedBySet($scope.profile, $scope.options) 15 | $scope.referenced = Object.keys(set).length > 0 16 | 17 | oldPacUrl = null 18 | oldLastUpdate = null 19 | oldPacScript = null 20 | onProfileChange = (profile, oldProfile) -> 21 | return unless profile and oldProfile 22 | if profile.pacUrl != oldProfile.pacUrl 23 | if profile.lastUpdate 24 | oldPacUrl = oldProfile.pacUrl 25 | oldLastUpdate = profile.lastUpdate 26 | oldPacScript = oldProfile.pacScript 27 | profile.lastUpdate = null 28 | else if oldPacUrl and profile.pacUrl == oldPacUrl 29 | profile.lastUpdate = oldLastUpdate 30 | profile.pacScript = oldPacScript 31 | $scope.pacUrlIsFile = $scope.isFileUrl(profile.pacUrl) 32 | $scope.$watch 'profile', onProfileChange, true 33 | 34 | $scope.editProxyAuth = (scheme) -> 35 | prop = 'all' 36 | auth = $scope.profile.auth?[prop] 37 | scope = $scope.$new('isolate') 38 | scope.auth = auth && angular.copy(auth) 39 | $modal.open( 40 | templateUrl: 'partials/fixed_auth_edit.html' 41 | scope: scope 42 | size: 'sm' 43 | ).result.then (auth) -> 44 | if not auth?.username 45 | if $scope.profile.auth 46 | $scope.profile.auth[prop] = undefined 47 | else 48 | $scope.profile.auth ?= {} 49 | $scope.profile.auth[prop] = auth 50 | -------------------------------------------------------------------------------- /omega-web/src/omega/controllers/profile.coffee: -------------------------------------------------------------------------------- 1 | angular.module('omega').controller 'ProfileCtrl', ($scope, $stateParams, 2 | $location, $rootScope, $timeout, $state, $modal, profileColorPalette, 3 | getAttachedName, getParentName, getVirtualTarget) -> 4 | name = $stateParams.name 5 | profileTemplates = 6 | 'FixedProfile': 'profile_fixed.html' 7 | 'PacProfile': 'profile_pac.html' 8 | 'VirtualProfile': 'profile_virtual.html' 9 | 'SwitchProfile': 'profile_switch.html' 10 | 'RuleListProfile': 'profile_rule_list.html' 11 | $scope.spectrumOptions = 12 | localStorageKey: 'spectrum.profileColor' 13 | palette: profileColorPalette 14 | preferredFormat: 'hex' 15 | showButtons: false 16 | showInitial: true 17 | showInput: true 18 | showPalette: true 19 | showSelectionPalette: true 20 | maxSelectionSize: 5 21 | 22 | $scope.getProfileColor = -> 23 | color = undefined 24 | profile = $scope.profile 25 | while profile 26 | color = profile.color 27 | profile = getVirtualTarget(profile, $scope.options) 28 | color 29 | 30 | $scope.deleteProfile = -> 31 | profileName = $scope.profile.name 32 | refs = OmegaPac.Profiles.referencedBySet(profileName, $rootScope.options) 33 | 34 | scope = $rootScope.$new('isolate') 35 | scope.profile = $scope.profile 36 | scope.dispNameFilter = $scope.dispNameFilter 37 | scope.options = $scope.options 38 | 39 | if Object.keys(refs).length > 0 40 | refSet = {} 41 | for own key, pname of refs 42 | parent = getParentName(pname) 43 | if parent 44 | key = OmegaPac.Profiles.nameAsKey(parent) 45 | pname = parent 46 | refSet[key] = pname 47 | 48 | refProfiles = [] 49 | for own key of refSet 50 | refProfiles.push(OmegaPac.Profiles.byKey(key, $rootScope.options)) 51 | scope.refs = refProfiles 52 | $modal.open( 53 | templateUrl: 'partials/cannot_delete_profile.html' 54 | scope: scope 55 | ) 56 | return 57 | else 58 | $modal.open( 59 | templateUrl: 'partials/delete_profile.html' 60 | scope: scope 61 | ).result.then -> 62 | attachedName = getAttachedName(profileName) 63 | delete $rootScope.options[OmegaPac.Profiles.nameAsKey(attachedName)] 64 | delete $rootScope.options[OmegaPac.Profiles.nameAsKey(profileName)] 65 | if $rootScope.options['-startupProfileName'] == profileName 66 | $rootScope.options['-startupProfileName'] = "" 67 | quickSwitch = $rootScope.options['-quickSwitchProfiles'] 68 | for i in [0...quickSwitch.length] 69 | if profileName == quickSwitch[i] 70 | quickSwitch.splice i, 1 71 | break 72 | $state.go('ui') 73 | 74 | # The watcher should be applied on the calling scope. 75 | # coffeelint: disable=missing_fat_arrows 76 | $scope.watchAndUpdateRevision = (expression) -> 77 | revisionChanged = false 78 | onChange = (profile, oldProfile) -> 79 | return profile if profile == oldProfile or not profile or not oldProfile 80 | if revisionChanged and profile.revision != oldProfile.revision 81 | revisionChanged = false 82 | else 83 | OmegaPac.Profiles.updateRevision(profile) 84 | revisionChanged = true 85 | this.$watch expression, onChange, true 86 | 87 | $scope.exportRuleList = null 88 | $scope.exportRuleListOptions = null 89 | $scope.setExportRuleListHandler = (exportRuleList, options) -> 90 | $scope.exportRuleList = exportRuleList 91 | $scope.exportRuleListOptions = options 92 | 93 | unwatch = $scope.$watch (-> $scope.options?['+' + name]), (profile) -> 94 | if not profile 95 | if $scope.options 96 | unwatch() 97 | $location.path '/' 98 | else 99 | unwatch2 = $scope.$watch 'options', -> 100 | if $scope.options 101 | unwatch2() 102 | if not $scope.options['+' + name] 103 | unwatch() 104 | $location.path '/' 105 | return 106 | if OmegaPac.Profiles.formatByType[profile.profileType] 107 | profile.format = OmegaPac.Profiles.formatByType[profile.profileType] 108 | profile.profileType = 'RuleListProfile' 109 | $scope.profile = profile 110 | type = $scope.profile.profileType 111 | templ = profileTemplates[type] ? 'profile_unsupported.html' 112 | $scope.profileTemplate = 'partials/' + templ 113 | $scope.scriptable = true 114 | 115 | $scope.watchAndUpdateRevision 'profile' 116 | -------------------------------------------------------------------------------- /omega-web/src/omega/controllers/quick_switch.coffee: -------------------------------------------------------------------------------- 1 | angular.module('omega').controller 'QuickSwitchCtrl', ($scope, $filter) -> 2 | $scope.sortableOptions = 3 | tolerance: 'pointer' 4 | axis: 'y' 5 | forceHelperSize: true 6 | forcePlaceholderSize: true 7 | connectWith: '.cycle-profile-container' 8 | containment: '#quick-switch-settings' 9 | 10 | $scope.$watchCollection 'options', (options) -> 11 | return unless options? 12 | $scope.notCycledProfiles = 13 | for profile in $filter('profiles')(options, 'all') when ( 14 | options["-quickSwitchProfiles"].indexOf(profile.name) < 0) 15 | profile.name 16 | -------------------------------------------------------------------------------- /omega-web/src/omega/controllers/rule_list_profile.coffee: -------------------------------------------------------------------------------- 1 | angular.module('omega').controller 'RuleListProfileCtrl', ($scope) -> 2 | $scope.ruleListFormats = OmegaPac.Profiles.ruleListFormats 3 | -------------------------------------------------------------------------------- /omega-web/src/omega/directives.coffee: -------------------------------------------------------------------------------- 1 | angular.module('omega').directive 'inputGroupClear', ($timeout) -> 2 | restrict: 'A' 3 | templateUrl: 'partials/input_group_clear.html' 4 | scope: 5 | 'model': '=model' 6 | 'type': '@type' 7 | 'ngPattern': '=?ngPattern' 8 | 'placeholder': '@placeholder' 9 | 'controller': '=?controller' 10 | link: (scope, element, attrs) -> 11 | scope.catchAll = new RegExp('') 12 | $timeout -> 13 | scope.controller = element.find('input').controller('ngModel') 14 | 15 | scope.oldModel = '' 16 | scope.controller = scope.input 17 | scope.modelChange = -> 18 | if scope.model 19 | scope.oldModel = '' 20 | scope.toggleClear = -> 21 | [scope.model, scope.oldModel] = [scope.oldModel, scope.model] 22 | angular.module('omega').directive 'omegaUpload', -> 23 | restrict: 'A' 24 | scope: 25 | success: '&omegaUpload' 26 | error: '&omegaError' 27 | link: (scope, element, attrs) -> 28 | input = element[0] 29 | element.on 'change', -> 30 | if input.files.length > 0 and input.files[0].name.length > 0 31 | reader = new FileReader() 32 | reader.addEventListener 'load', (e) -> 33 | scope.$apply -> 34 | scope.success({'$content': e.target.result}) 35 | reader.addEventListener 'error', (e) -> 36 | scope.$apply -> 37 | scope.error({'$error': e.target.error}) 38 | reader.readAsText(input.files[0]) 39 | input.value = '' 40 | angular.module('omega').directive 'omegaIp2str', -> 41 | restrict: 'A' 42 | priority: 2 # Run post-link after input directive (0) and ngModel (1). 43 | require: 'ngModel' 44 | link: (scope, element, attr, ngModel) -> 45 | ngModel.$parsers.push (value) -> 46 | if value 47 | OmegaPac.Conditions.fromStr('Ip: ' + value) 48 | else 49 | ({conditionType: 'IpCondition', ip: '0.0.0.0', prefixLength: 0}) 50 | ngModel.$formatters.push (value) -> 51 | if value?.ip 52 | OmegaPac.Conditions.str(value).split(' ', 2)[1] 53 | else 54 | '' 55 | -------------------------------------------------------------------------------- /omega-web/src/omega/filters.coffee: -------------------------------------------------------------------------------- 1 | angular.module('omega').filter 'profiles', (builtinProfiles, profileOrder, 2 | isProfileNameHidden, isProfileNameReserved) -> 3 | 4 | charCodePlus = '+'.charCodeAt(0) 5 | builtinProfileList = (profile for _, profile of builtinProfiles) 6 | (options, filter) -> 7 | result = [] 8 | for name, value of options when name.charCodeAt(0) == charCodePlus 9 | result.push value 10 | if (typeof filter == 'object' or ( 11 | typeof filter == 'string' and filter.charCodeAt(0) == charCodePlus)) 12 | if typeof filter == 'string' 13 | filter = filter.substr(1) 14 | result = OmegaPac.Profiles.validResultProfilesFor(filter, options) 15 | if filter == 'all' 16 | result = result.filter (profile) -> !isProfileNameHidden(profile.name) 17 | result = result.concat builtinProfileList 18 | else 19 | result = result.filter (profile) -> !isProfileNameReserved(profile.name) 20 | if filter == 'sorted' 21 | result.sort profileOrder 22 | result 23 | 24 | angular.module('omega').filter 'tr', (omegaTarget) -> omegaTarget.getMessage 25 | angular.module('omega').filter 'dispName', (omegaTarget) -> 26 | (name) -> 27 | if typeof name == 'object' 28 | name = name.name 29 | omegaTarget.getMessage('profile_' + name) || name 30 | -------------------------------------------------------------------------------- /omega-web/src/options.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang='en' ng-controller='MasterCtrl' ng-csp) 3 | head 4 | meta(charset='utf-8') 5 | title(ng-bind="'options_title' | tr") SwitchyOmega Options 6 | meta(name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no') 7 | link(rel='stylesheet' href='lib/bootstrap/css/bootstrap.min.css') 8 | link(rel='stylesheet' href='lib/spectrum/spectrum.css') 9 | link(rel='stylesheet' href='lib/ladda/ladda-themeless.min.css') 10 | link(rel='stylesheet' href='lib/shepherd.js/shepherd-theme-arrows.css') 11 | link(rel='stylesheet' href='css/options.css') 12 | body(style='display: none;' ng-style='{display: options ? "block" : "none"}') 13 | .container-fluid 14 | header.col-lg-2.col-sm-3.side-nav 15 | h1 16 | a(ui-sref='about' title='{{"about_title" | tr}}') {{'appNameShort' | tr}} 17 | sup.om-experimental.text-danger(ng-show='isExperimental') 18 | | {{'options_experimental_badge' | tr}} 19 | nav.nav.nav-pills.nav-stacked 20 | li.nav-header {{'options_navHeader_setting' | tr}} 21 | li(ui-sref-active='active'): a(ui-sref='ui') 22 | span.glyphicon.glyphicon-wrench 23 | = ' ' 24 | | {{'options_tab_ui' | tr}} 25 | li(ui-sref-active='active'): a(ui-sref='general') 26 | span.glyphicon.glyphicon-cog 27 | = ' ' 28 | | {{'options_tab_general' | tr}} 29 | li(ui-sref-active='active'): a(ui-sref='io') 30 | span.glyphicon.glyphicon-floppy-save 31 | = ' ' 32 | | {{'options_tab_importExport' | tr}} 33 | li.divider 34 | li.nav-header {{'options_navHeader_profiles' | tr}} 35 | li.nav-profile(ng-repeat='profile in options | profiles:"sorted"' ui-sref-active='active' 36 | data-profile-type="{{profile.profileType}}") 37 | a(ui-sref='profile({name: profile.name})') 38 | span(omega-profile-inline='profile' options='options') 39 | li.nav-new-profile 40 | a(role='button' ng-click='newProfile()') 41 | span.glyphicon.glyphicon-plus 42 | = ' ' 43 | span {{'options_newProfile' | tr}} 44 | li.divider 45 | li.nav-header Actions 46 | li 47 | a.btn-default.btn.align-initial(role='button' ng-click='applyOptions()' 48 | ng-class='{"btn-success": optionsDirty}') 49 | span.glyphicon.glyphicon-ok-circle 50 | = ' ' 51 | | {{'options_apply' | tr}} 52 | li(ng-class='{disabled: !optionsDirty}') 53 | a.text-danger(role='button' ng-click='revertOptions()') 54 | span.glyphicon.glyphicon-remove-circle 55 | = ' ' 56 | | {{'options_discard' | tr}} 57 | main.col-lg-10.col-sm-9.col-lg-offset-2.col-sm-offset-3.angular-animate(ui-view) 58 | 59 | //- 60 | Note: Alert type classes cannot be changed in angular-bootstrap. 61 | This is already fixed but they don't have time for a new release: 62 | https://github.com/angular-ui/bootstrap/issues/2641 63 | .alert-top-wrapper(ng-show='alertShown') 64 | div(alert type='workaround' close='hideAlert()' 65 | class='{{alertClassForType(alert.type)}}') 66 | span.glyphicon(class="{{alertIcons[alert.type]}}") 67 | = ' ' 68 | | {{alert.i18n ? (alert.i18n | tr) : alert.message}} 69 | script(src='js/omega_debug.js') 70 | script(src='js/log_error.js') 71 | script(src='lib/script.js/script.min.js') 72 | script(src='js/options.js') 73 | -------------------------------------------------------------------------------- /omega-web/src/partials/about.jade: -------------------------------------------------------------------------------- 1 | .page-header 2 | h2 {{'about_title' | tr}} 3 | section.omega-experimental(ng-show='isExperimental') 4 | p.alert.alert-warning 5 | span.glyphicon.glyphicon-warning-sign 6 | = ' ' 7 | span {{'about_experimental_warning_moz' | tr}} 8 | section 9 | .media(style='margin: 1em 0') 10 | .media-left 11 | img.media-object(src='img/icons/omega-action-32.png') 12 | .media-body 13 | h4.media-heading {{'appNameShort' | tr}} 14 | p {{'about_app_description' | tr}} 15 | section 16 | p 17 | button.btn.btn-info(ng-click='reportIssue()') 18 | span.glyphicon.glyphicon-comment 19 | = ' ' 20 | | {{'popup_reportIssues' | tr}} 21 | = ' ' 22 | button.btn.btn-default(ng-click='downloadLog()') 23 | span.glyphicon.glyphicon-download 24 | = ' ' 25 | | {{'popup_errorLog' | tr}} 26 | = ' ' 27 | button.btn.btn-danger(ng-click='showResetOptionsModal()') 28 | span.glyphicon.glyphicon-alert 29 | = ' ' 30 | | {{'options_reset' | tr}} 31 | section 32 | p 33 | | {{'about_version' | tr:[version]}} 34 | p.text-warning 35 | span.glyphicon.glyphicon-info-sign 36 | = ' ' 37 | span(ng-bind-html='"about_disclaimer_networkService" | tr') 38 | p.text-success 39 | span.glyphicon.glyphicon-eye-close 40 | = ' ' 41 | span(ng-bind-html='"about_disclaimer_privacy" | tr') 42 | p.text-info 43 | span.glyphicon.glyphicon-question-sign 44 | = ' ' 45 | span(ng-bind-html='"about_help" | tr') 46 | 47 | section(style='margin-top: 7em') 48 | p 49 | | {{'appNameShort' | tr}} 50 | br 51 | span(ng-bind-html='"about_copyright" | tr') 52 | br 53 | span(ng-bind-html='"about_license" | tr') 54 | br 55 | span(ng-bind-html='"about_credits" | tr') 56 | -------------------------------------------------------------------------------- /omega-web/src/partials/apply_options_confirm.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(type='button' ng-click='$dismiss()') 3 | span(aria-hidden='true') × 4 | span.sr-only {{'dialog_close' | tr}} 5 | h4.modal-title {{'options_modalHeader_applyOptions' | tr}} 6 | .modal-body 7 | p {{'options_applyOptionsRequired' | tr}} 8 | p {{'options_applyOptionsConfirm' | tr}} 9 | .modal-footer 10 | button.btn.btn-default(ng-click='$dismiss()') {{'dialog_cancel' | tr}} 11 | button.btn.btn-primary(type='button' ng-click='$close("ok")') {{'options_apply' | tr}} 12 | -------------------------------------------------------------------------------- /omega-web/src/partials/cannot_delete_profile.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(type='button' ng-click='$dismiss()') 3 | span(aria-hidden='true') × 4 | span.sr-only {{'dialog_close' | tr}} 5 | h4.modal-title {{'options_modalHeader_cannotDeleteProfile' | tr}} 6 | .modal-body 7 | p {{'options_profileReferredBy' | tr}} 8 | .well 9 | ul.list-style-none 10 | li(ng-repeat='p in refs') 11 | span(omega-profile-inline='p' options='options' disp-name='dispNameFilter') 12 | p {{'options_modifyReferringProfiles' | tr}} 13 | .modal-footer 14 | button.btn.btn-default(ng-click='$dismiss()') {{'dialog_cancel' | tr}} 15 | -------------------------------------------------------------------------------- /omega-web/src/partials/delete_attached.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(type='button' ng-click='$dismiss()') 3 | span(aria-hidden='true') × 4 | span.sr-only {{'dialog_close' | tr}} 5 | h4.modal-title {{'options_modalHeader_deleteAttached' | tr}} 6 | .modal-body 7 | p {{'options_deleteAttachedConfirm' | tr}} 8 | .well 9 | span(omega-profile-icon='attached' options='options') 10 | = ' ' 11 | | {{attached.sourceUrl || ('options_ruleListLineCount' | tr:[attached.ruleList.split('\n').length])}} 12 | .modal-footer 13 | button.btn.btn-default(ng-click='$dismiss()') {{'dialog_cancel' | tr}} 14 | button.btn.btn-danger(type='button' ng-click='$close("ok")') {{'options_deleteAttached' | tr}} 15 | -------------------------------------------------------------------------------- /omega-web/src/partials/delete_profile.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(type='button' ng-click='$dismiss()') 3 | span(aria-hidden='true') × 4 | span.sr-only {{'dialog_close' | tr}} 5 | h4.modal-title {{'options_modalHeader_deleteProfile' | tr}} 6 | .modal-body 7 | p {{'options_deleteProfileConfirm' | tr}} 8 | .well 9 | span(omega-profile-inline='profile' options='options' disp-name='dispNameFilter') 10 | .modal-footer 11 | button.btn.btn-default(ng-click='$dismiss()') {{'dialog_cancel' | tr}} 12 | button.btn.btn-danger(type='button' ng-click='$close("ok")') {{'options_deleteProfile' | tr}} 13 | -------------------------------------------------------------------------------- /omega-web/src/partials/fixed_auth_edit.jade: -------------------------------------------------------------------------------- 1 | form(ng-submit='authForm.$valid && $close(auth)' name='authForm') 2 | .modal-header 3 | button.close(type='button' ng-click='$dismiss()') 4 | span(aria-hidden='true') × 5 | span.sr-only Close 6 | h4.modal-title {{'options_modalHeader_proxyAuth' | tr}} 7 | .modal-body(style='padding-bottom: 0;') 8 | .form-group(ng-show='!authSupported') 9 | .alert.alert-danger 10 | span.glyphicon.glyphicon-warning-sign 11 | = ' ' 12 | | {{"options_proxy_authNotSupported" | tr:[protocolDisp]}} 13 | .form-group 14 | label.sr-only {{'options_proxyAuthUsername' | tr}} 15 | div(input-group-clear type='text' model='auth.username' autofocus 16 | placeholder='{{"options_proxyAuthUsername" | tr}}') 17 | .form-group(ng-class='{"has-error": !authForm.password.$valid}') 18 | label.sr-only {{'options_proxyAuthPassword' | tr}} 19 | .input-group 20 | input.form-control(type='text' name='password' ng-model='auth.password' 21 | ng-attr-type='{{showPassword ? "text" : "password"}}' 22 | placeholder='{{"options_proxyAuthPassword" | tr}}' ng-show='!!auth.username') 23 | input.form-control(type='text' value='' placeholder='{{"options_proxyAuthNone" | tr}}' 24 | disabled ng-show='!auth.username') 25 | span.input-group-btn 26 | button.btn.btn-default(type='button' ng-click='showPassword = !showPassword' 27 | title="{{(showPassword ? 'options_proxyAuthHidePassword' : 'options_proxyAuthShowPassword') | tr}}" ng-disabled='!auth.username') 28 | span.glyphicon(ng-class='{"glyphicon-eye-close": !showPassword, "glyphicon-eye-open": !!showPassword}') 29 | .modal-footer 30 | button.btn.btn-default(type='button' ng-click='$dismiss()') {{'dialog_cancel' | tr}} 31 | button.btn.btn-primary(type='submit' ng-disabled='!authForm.$valid') {{'dialog_save' | tr}} 32 | -------------------------------------------------------------------------------- /omega-web/src/partials/general.jade: -------------------------------------------------------------------------------- 1 | .page-header 2 | h2 {{'options_tab_general' | tr}} 3 | section.settings-group 4 | h3 {{'options_group_networkRequests' | tr}} 5 | div.checkbox 6 | label 7 | input#revert-proxy-changes(type='checkbox' ng-model='options["-monitorWebRequests"]') 8 | span {{'options_monitorWebRequests' | tr}} 9 | p.help-block(omega-html="'options_monitorWebRequestsHelp' | tr") 10 | section.settings-group.width-limit 11 | h3 {{'options_downloadOptions' | tr}} 12 | p.help-block {{'options_downloadOptionsHelp' | tr}} 13 | .form-group 14 | label(for='download-interval') {{'options_downloadInterval' | tr}} 15 | select#download-interval.form-control.inline-form-control(ng-model='options["-downloadInterval"]' 16 | ng-options='interval as (downloadIntervalI18n(interval) | tr) for interval in downloadIntervals') 17 | section.settings-group.width-limit 18 | h3 {{'options_group_conflicts' | tr}} 19 | p {{'options_conflicts_introduction' | tr}} 20 | p.help-text.text-danger 21 | span(style='padding: 1px 4px; background: #da4f49; color: #fff; box-shadow: #ccc 1px 1px 1px 1px;') = 22 | = ' ' 23 | | {{'options_conflicts_lowerPriority' | tr}} 24 | p.help-text.text-info 25 | span.glyphicon.glyphicon-info-sign 26 | = ' ' 27 | span(omega-html="'options_conflicts_higherPriority' | tr:[$profile('systemProfile')]") 28 | 29 | div.checkbox 30 | label 31 | input#revert-proxy-changes(type='checkbox' ng-model='options["-showExternalProfile"]') 32 | span {{'options_showExternalProfile' | tr}} 33 | p.help-block(omega-html="'options_showExternalProfileHelp' | tr:[$profile('systemProfile'), $profile('externalProfile')]") 34 | -------------------------------------------------------------------------------- /omega-web/src/partials/input_group_clear.jade: -------------------------------------------------------------------------------- 1 | .input-group 2 | input.form-control(ng-model='model' ng-attr-type='{{type}}' ng-pattern='ngPattern || catchAll' 3 | placeholder='{{placeholder}}' ng-change='modelChange()') 4 | span.input-group-btn 5 | button.btn.btn-default.input-group-clear-btn(type='button' ng-click='toggleClear()' 6 | ng-disabled='!model && !oldModel' title="{{'inputClear_' + (oldModel ? 'restore' : 'clear') | tr}}") 7 | span.glyphicon(ng-class='{"glyphicon-remove": !oldModel, "glyphicon-repeat": !!oldModel}') 8 | -------------------------------------------------------------------------------- /omega-web/src/partials/io.jade: -------------------------------------------------------------------------------- 1 | .page-header 2 | h2 {{'options_tab_importExport' | tr}} 3 | section.settings-group 4 | h3 {{'options_group_importExportProfile' | tr}} 5 | .help-block 6 | .text-info 7 | span.glyphicon.glyphicon-info-sign 8 | = ' ' 9 | | {{'options_exportProfileHelp' | tr}} 10 | div.checkbox(ng-show='!(options["-showConditionTypes"] > 0)') 11 | label 12 | input(type='checkbox' ng-model='options["-exportLegacyRuleList"]') 13 | span {{'options_exportLegacyRuleList' | tr}} 14 | p.help-block(omega-html="'options_exportLegacyRuleListHelp' | tr") 15 | section.settings-group 16 | h3 {{'options_group_importExportSettings' | tr}} 17 | p 18 | button.btn.btn-default(ng-click='exportOptions()') 19 | span.glyphicon.glyphicon-floppy-save 20 | = ' ' 21 | | {{'options_makeBackup' | tr}} 22 | span.help-inline {{'options_makeBackupHelp' | tr}} 23 | p 24 | input#restore-local-file(type='file' omega-upload='restoreLocal($content)' 25 | omega-error='restoreLocalError($error)') 26 | button.btn.btn-default(ng-click='triggerFileInput()' 27 | ladda='restoringLocal' data-spinner-color="#000000") 28 | span.glyphicon.glyphicon-folder-open 29 | = ' ' 30 | | {{'options_restoreLocal' | tr}} 31 | span.help-inline {{'options_restoreLocalHelp' | tr}} 32 | div 33 | label {{'options_restoreOnline' | tr}} 34 | .input-group.width-limit 35 | input.form-control(type='url' ng-model='restoreOnlineUrl' placeholder="{{'options_restoreOnlinePlaceholder' | tr}}") 36 | span.input-group-btn 37 | button.btn.btn-default(ng-click='restoreOnline()' ladda='restoringOnline' data-spinner-color="#000000") 38 | | {{'options_restoreOnlineSubmit' | tr}} 39 | section.settings-group 40 | h3 {{'options_group_syncing' | tr}} 41 | div(ng-show='syncOptions == "pristine" || syncOptions == "disabled"') 42 | p.help-block(omega-html='"options_syncPristineHelp" | tr') 43 | p 44 | button.btn.btn-default(ng-click='enableOptionsSync()') 45 | span.glyphicon.glyphicon-cloud-upload 46 | = ' ' 47 | | {{'options_syncEnable' | tr}} 48 | div(ng-show='syncOptions == "sync"') 49 | p.alert.alert-success.width-limit 50 | span.glyphicon.glyphicon-ok 51 | = ' ' 52 | | {{"options_syncSyncAlert" | tr}} 53 | p.help-block(omega-html='"options_syncSyncHelp" | tr') 54 | p 55 | button.btn.btn-warning(ng-click='disableOptionsSync()') 56 | span.glyphicon.glyphicon-remove-sign 57 | = ' ' 58 | | {{'options_syncDisable' | tr}} 59 | div(ng-show='syncOptions == "conflict"') 60 | p.alert.alert-info.width-limit 61 | span.glyphicon.glyphicon-info-sign 62 | = ' ' 63 | | {{"options_syncConflictAlert" | tr}} 64 | p.help-block(omega-html='"options_syncConflictHelp" | tr') 65 | p 66 | button.btn.btn-danger(ng-click='enableOptionsSync({force: true})') 67 | span.glyphicon.glyphicon-cloud-download 68 | = ' ' 69 | | {{'options_syncEnableForce' | tr}} 70 | = ' ' 71 | button.btn.btn-link(ng-click='resetOptionsSync()') 72 | span.glyphicon.glyphicon-erase 73 | = ' ' 74 | | {{'options_syncReset' | tr}} 75 | div(ng-show='syncOptions == "unsupported"') 76 | p.help-block(omega-html='"options_syncUnsupportedHelp" | tr') 77 | -------------------------------------------------------------------------------- /omega-web/src/partials/new_profile.jade: -------------------------------------------------------------------------------- 1 | form(ng-submit='newProfile.$valid && $close(profile)' name='newProfile') 2 | .modal-header 3 | button.close(type='button' ng-click='$dismiss()') 4 | span(aria-hidden='true') × 5 | span.sr-only Close 6 | h4.modal-title {{'options_modalHeader_newProfile' | tr}} 7 | .modal-body 8 | .form-group(ng-class='{"has-error": !newProfile.profileNewName.$valid}') 9 | label(for='profile-new-name') {{'options_newProfileName' | tr}} 10 | input#profile-new-name.form-control(type='text' name='profileNewName' required ng-model='profile.name' 11 | ui-validate='validateProfileName' autofocus) 12 | .help-block(ng-show='newProfile.profileNewName.$error.required') {{'options_profileNameEmpty' | tr}} 13 | .help-block(ng-show='newProfile.profileNewName.$error.reserved') {{'options_profileNameReserved' | tr}} 14 | .help-block(ng-show='!newProfile.profileNewName.$error.reserved && newProfile.profileNewName.$error.conflict') 15 | | {{'options_profileNameConflict' | tr}} 16 | .help-block(ng-show='newProfile.profileNewName.$valid && profile.name && isProfileNameHidden(profile.name)') 17 | .text-info 18 | span.glyphicon.glyphicon-info-sign 19 | = ' ' 20 | | {{'options_profileNameHidden' | tr}} 21 | label {{'options_profileType' | tr}} 22 | .radio 23 | label 24 | input(type='radio' name='profile-new-type' value='FixedProfile' ng-model='profile.profileType' ng-init='profile.profileType = "FixedProfile"') 25 | span.profile-type 26 | span.glyphicon(ng-class='profileIcons["FixedProfile"]') 27 | = ' ' 28 | span {{'options_profileTypeFixedProfile' | tr}} 29 | .help-block {{'options_profileDescFixedProfile' | tr}} 30 | .radio 31 | label 32 | input(type='radio', name='profile-new-type', value='SwitchProfile' ng-model='profile.profileType') 33 | span.profile-type 34 | span.glyphicon(ng-class='profileIcons["SwitchProfile"]') 35 | = ' ' 36 | span {{'options_profileTypeSwitchProfile' | tr}} 37 | .help-block {{'options_profileDescSwitchProfile' | tr}} 38 | .radio 39 | label 40 | input(type='radio', name='profile-new-type', value='PacProfile' ng-model='profile.profileType' ng-disabled='pacProfilesUnsupported') 41 | span.profile-type 42 | span.glyphicon(ng-class='profileIcons["PacProfile"]') 43 | = ' ' 44 | span {{'options_profileTypePacProfile' | tr}} 45 | .help-block {{'options_profileDescPacProfile' | tr}} 46 | .help-block(ng-show='!pacProfilesUnsupported') {{'options_profileDescMorePacProfile' | tr}} 47 | .has-error(ng-show='pacProfilesUnsupported') 48 | .help-block 49 | span.glyphicon.glyphicon-warning-sign 50 | = ' ' 51 | | {{'options_pac_profile_unsupported_moz' | tr}} 52 | .radio 53 | label 54 | input(type='radio', name='profile-new-type', value='VirtualProfile' ng-model='profile.profileType') 55 | span.profile-type 56 | span.glyphicon.virtual-profile-icon(ng-class='profileIcons["VirtualProfile"]') 57 | = ' ' 58 | span {{'options_profileTypeVirtualProfile' | tr}} 59 | .help-block {{'options_profileDescVirtualProfile' | tr}} 60 | .modal-footer 61 | button.btn.btn-default(type='button' ng-click='$dismiss()') {{'dialog_cancel' | tr}} 62 | button.btn.btn-primary(type='submit' ng-disabled='!newProfile.$valid') {{'options_createProfile' | tr}} 63 | -------------------------------------------------------------------------------- /omega-web/src/partials/omega_profile_select.jade: -------------------------------------------------------------------------------- 1 | .btn-group.omega-profile-select(dropdown on-toggle="toggled(open)") 2 | button.btn.btn-default.dropdown-toggle(dropdown-toggle type='button' aria-expanded='false' 3 | role='listbox' aria-haspopup='true') 4 | span(omega-profile-icon='selectedProfile' options='options' icon='selectedProfile ? undefined : "glyphicon-time"') 5 | = ' ' 6 | span(ng-show='!!profileName') {{getName(selectedProfile)}} 7 | span(ng-show='!profileName') {{defaultText}} 8 | = ' ' 9 | span.caret 10 | ul.dropdown-menu(role='listbox') 11 | li(role='option' ng-if='!!defaultText' ng-class='{active: profileName == ""}') 12 | a(ng-click='setProfileName("")') 13 | span.glyphicon.glyphicon-time 14 | = ' {{defaultText}}' 15 | li(role='option' ng-repeat='profile in dispProfiles' ng-class='{active: profileName == profile.name}') 16 | a(ng-click='setProfileName(profile.name)') 17 | span(omega-profile-icon='profile' options='options') 18 | = ' {{getName(profile)}}' 19 | -------------------------------------------------------------------------------- /omega-web/src/partials/options_welcome.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(type='button' ng-click='$dismiss()') 3 | span(aria-hidden='true') × 4 | span.sr-only {{'dialog_close' | tr}} 5 | h4.modal-title {{'options_modalHeader_welcome' | tr}} 6 | .modal-body 7 | p(ng-show="upgrade") {{'options_welcomeUpgrade' | tr}} 8 | p(ng-show="upgrade") {{'options_welcomeUpgradeGuide' | tr}} 9 | p(ng-show="!upgrade") {{'options_welcomeNormal' | tr}} 10 | p(ng-show="!upgrade") {{'options_welcomeNormalGuide' | tr}} 11 | .modal-footer 12 | button.btn.btn-default(ng-click='$close("skip")') {{'options_guideSkip' | tr}} 13 | button.btn.btn-primary(type='button' ng-click='$close("show")') {{'options_guideNext' | tr}} 14 | -------------------------------------------------------------------------------- /omega-web/src/partials/profile.jade: -------------------------------------------------------------------------------- 1 | .page-header 2 | .profile-actions 3 | button.btn(ng-show='exportRuleList' ng-click='exportRuleList(profile.name)' 4 | title="{{'options_profileExportRuleListHelp' | tr}}" 5 | ng-class="exportRuleListOptions.warning ? 'btn-warning' : 'btn-default'") 6 | span.glyphicon.glyphicon-list 7 | = ' ' 8 | | {{'options_profileExportRuleList' | tr}} 9 | = ' ' 10 | button.btn.btn-default(ng-show='scriptable' ng-click='exportScript(profile.name)' title="{{'options_exportPacFileHelp' | tr}}") 11 | span.glyphicon.glyphicon-download 12 | = ' ' 13 | | {{'options_profileExportPac' | tr}} 14 | = ' ' 15 | button.btn.btn-default(ng-click='renameProfile(profile.name)') 16 | span.glyphicon.glyphicon-edit 17 | = ' ' 18 | | {{'options_renameProfile' | tr}} 19 | = ' ' 20 | button.btn.btn-danger(ng-click='deleteProfile(profile.name)') 21 | span.glyphicon.glyphicon-trash 22 | = ' ' 23 | | {{'options_deleteProfile' | tr}} 24 | span.profile-color-editor 25 | .profile-color-editor-fake(ng-if='profile.profileType == "VirtualProfile"' 26 | ng-style="{'background-color': getProfileColor()}") 27 | x-spectrum-colorpicker(ng-model='profile.color' options='spectrumOptions' 28 | ng-if='profile.profileType != "VirtualProfile"') 29 | h2.profile-name {{'options_profileTabPrefix' | tr}}{{profile.name}} 30 | section.settings-group(ng-show='profile.syncOptions == "disabled"') 31 | p.alert.alert-info.width-limit(ng-show='!profile.syncError') 32 | span.glyphicon.glyphicon-info-sign 33 | = ' ' 34 | | {{'Syncing is disabled for this profile.'}} 35 | p.alert.alert-danger.width-limit(ng-show='!!profile.syncError') 36 | span.glyphicon.glyphicon-remove 37 | = ' ' 38 | | {{('options_profileSyncDisabled_' + profile.syncError.reason) | tr}} 39 | div(ng-include='profileTemplate') 40 | -------------------------------------------------------------------------------- /omega-web/src/partials/profile_fixed.jade: -------------------------------------------------------------------------------- 1 | div(ng-controller='FixedProfileCtrl') 2 | section.settings-group.settings-group-fixed-servers 3 | h3 {{'options_group_proxyServers' | tr}} 4 | .table-responsive 5 | table.fixed-servers.table.table-bordered.table-striped.width-limit-lg 6 | thead 7 | tr 8 | th {{'options_proxy_scheme' | tr}} 9 | th {{'options_proxy_protocol' | tr}} 10 | th {{'options_proxy_server' | tr}} 11 | th {{'options_proxy_port' | tr}} 12 | th 13 | tbody 14 | tr(ng-repeat='scheme in urlSchemes' ng-show='scheme == "" || showAdvanced') 15 | td {{schemeDisp[scheme] || ('options_scheme_default' | tr)}} 16 | td 17 | select.form-control(ng-model='proxyEditors[scheme].scheme' 18 | ng-options='opt.value as opt.label for opt in optionsForScheme[scheme]') 19 | td(ng-if='proxyEditors[scheme].scheme') 20 | input.form-control(type='text' ng-model='proxyEditors[scheme].host' required) 21 | td(ng-if='!proxyEditors[scheme].scheme') 22 | input.form-control(type='text' value='' placeholder='{{proxyEditors[""].host}}' disabled) 23 | 24 | td(ng-if='proxyEditors[scheme].scheme') 25 | input.form-control(type='number' min='1' ng-model='proxyEditors[scheme].port' required) 26 | td(ng-if='!proxyEditors[scheme].scheme') 27 | input.form-control(type='number' value='' placeholder='{{proxyEditors[""].port}}' disabled) 28 | td.proxy-actions 29 | button.btn.btn-xs.proxy-auth-toggle( 30 | ng-class='isProxyAuthActive(scheme) ? "btn-success" : "btn-default"' 31 | type='button' role='button' ng-click='editProxyAuth(scheme)' title='{{"options_proxy_auth" | tr}}') 32 | span.glyphicon.glyphicon-lock 33 | tbody(ng-show='!showAdvanced') 34 | tr.fixed-show-advanced 35 | td(colspan='7') 36 | button.btn.btn-link(ng-click='showAdvanced = true') 37 | | #[span.glyphicon.glyphicon-chevron-down] {{'options_proxy_expand' | tr}} 38 | section.settings-group 39 | h3 {{'options_group_bypassList' | tr}} 40 | p.help-block {{'options_bypassListHelp' | tr}} 41 | p.help-block 42 | a(href='https://developer.chrome.com/extensions/proxy#bypass_list' target='_blank') 43 | | {{'options_bypassListHelpLinkText' | tr}} 44 | textarea.monospace.form-control.width-limit(rows='10' ng-model='bypassList' ng-model-options="{updateOn:'blur'}") 45 | -------------------------------------------------------------------------------- /omega-web/src/partials/profile_pac.jade: -------------------------------------------------------------------------------- 1 | div(ng-controller='PacProfileCtrl') 2 | p.alert.alert-danger.width-limit(ng-show='pacProfilesUnsupported') 3 | span.glyphicon.glyphicon-remove 4 | = ' ' 5 | | {{'options_pac_profile_unsupported_moz' | tr}} 6 | section.settings-group 7 | h3 {{'options_group_pacUrl' | tr}} 8 | .width-limit(input-group-clear type='text' model='profile.pacUrl' 9 | ng-pattern='referenced ? urlRegex : urlWithFile' controller='pacUrlCtrl.ctrl') 10 | p.help-block {{'options_pacUrlHelp' | tr}} 11 | .has-warning(ng-show='pacUrlIsFile && !referenced') 12 | p.help-block #[span.glyphicon.glyphicon-warning-sign] {{'options_pacUrlFile' | tr}} 13 | .has-error(ng-show='isFileUrl(pacUrlCtrl.ctrl.$viewValue) && referenced') 14 | p.help-block #[span.glyphicon.glyphicon-remove-sign] {{'options_pacUrlFile' | tr}} 15 | p.help-block {{'options_pacUrlFileDisabled' | tr}} 16 | p(ng-show='profile.pacUrl && !pacUrlIsFile') 17 | button.btn(ng-click='updateProfile(profile.name)' 18 | ladda='updatingProfile[profile.name]' data-spinner-color="#000000" 19 | ng-class='profile.pacUrl && !profile.lastUpdate ? "btn-primary" : "btn-default"') 20 | | #[span.glyphicon.glyphicon-download-alt] {{'options_downloadProfileNow' | tr}} 21 | section.settings-group 22 | h3 23 | | {{'options_group_pacScript' | tr}} 24 | = ' ' 25 | button.btn.btn-xs.proxy-auth-toggle(ng-class='profile.auth["all"] ? "btn-success" : "btn-default"' 26 | type='button' role='button' ng-click='editProxyAuth()' title='{{"options_proxy_auth" | tr}}') 27 | span.glyphicon.glyphicon-lock 28 | div.alert.alert-warning.width-limit(ng-show='profile.auth["all"]') 29 | p {{'options_proxy_authAllWarningPac' | tr}} 30 | p(ng-show='!!profile.pacUrl') {{'options_proxy_authAllWarningPacUrl' | tr}} 31 | p(ng-show='!profile.pacUrl') {{'options_proxy_authAllWarningPacScript' | tr}} 32 | p(ng-show='!!referenced') 33 | | #[span.glyphicon.glyphicon-warning-sign] {{'options_proxy_authReferencedWarning' | tr}} 34 | div(ng-hide='pacUrlIsFile') 35 | p.alert.alert-success.width-limit(ng-show='profile.pacUrl && profile.lastUpdate') 36 | | {{'options_pacScriptLastUpdate' | tr:[(profile.lastUpdate | date:'medium')]}} 37 | p.alert.alert-danger.width-limit(ng-show='profile.pacUrl && !profile.lastUpdate') 38 | | {{'options_pacScriptObsolete' | tr}} 39 | textarea.monospace.form-control.width-limit(ng-model='profile.pacScript' rows=20 40 | ng-disabled='pacUrlCtrl.ctrl.$invalid || !!profile.pacUrl') 41 | -------------------------------------------------------------------------------- /omega-web/src/partials/profile_rule_list.jade: -------------------------------------------------------------------------------- 1 | div(ng-controller='RuleListProfileCtrl') 2 | section.settings-group 3 | h3 {{'options_group_ruleListConfig' | tr}} 4 | .form-group 5 | label {{'options_ruleListMatchProfile' | tr}} 6 | = ' ' 7 | div(omega-profile-select='options | profiles:profile' ng-model='profile.matchProfileName' 8 | disp-name='dispNameFilter' options='options' style='display: inline-block;') 9 | .form-group 10 | label {{'options_ruleListDefaultProfile' | tr}} 11 | = ' ' 12 | div(omega-profile-select='options | profiles:profile' ng-model='profile.defaultProfileName' 13 | disp-name='dispNameFilter' options='options' style='display: inline-block;') 14 | form.form-group 15 | label {{'options_ruleListFormat' | tr}} 16 | .radio.inline-form-control.no-min-width(ng-repeat='format in ruleListFormats') 17 | label 18 | input(type='radio' name='formatInput' value='{{format}}' ng-model='profile.format') 19 | | {{'ruleListFormat_' + format | tr}} 20 | section.settings-group 21 | h3 {{'options_group_ruleListUrl' | tr}} 22 | .width-limit(input-group-clear type='url' model='profile.sourceUrl' ng-if='profile') 23 | p.help-block {{'options_ruleListUrlHelp' | tr}} 24 | section.settings-group 25 | h3 {{'options_group_ruleListText' | tr}} 26 | p 27 | button.btn.btn-default(ng-disabled='!profile.sourceUrl' ng-click='updateProfile(profile.name)' 28 | ladda='updatingProfile[profile.name]' data-spinner-color="#000000") 29 | | #[span.glyphicon.glyphicon-download-alt] {{'options_downloadProfileNow' | tr}} 30 | textarea.monospace.form-control.width-limit(ng-model='profile.ruleList' rows=20 31 | ng-disabled='!!profile.sourceUrl') 32 | 33 | -------------------------------------------------------------------------------- /omega-web/src/partials/profile_unsupported.jade: -------------------------------------------------------------------------------- 1 | .lead 2 | | {{'options_profileUnsupported' | tr:profile.profileType}} 3 | p {{'options_profileUnsupportedHelp' | tr}} 4 | -------------------------------------------------------------------------------- /omega-web/src/partials/profile_virtual.jade: -------------------------------------------------------------------------------- 1 | div 2 | section.settings-group 3 | h3 {{'options_group_virtualProfile' | tr}} 4 | p.help-block 5 | | {{'options_virtualProfileTargetHelp' | tr}} 6 | .form-group 7 | label {{'options_virtualProfileTarget' | tr}} 8 | = ' ' 9 | div(omega-profile-select='options | profiles:profile' ng-model='profile.defaultProfileName' 10 | disp-name='dispNameFilter' style='display: inline-block;' options='options') 11 | section.settings-group 12 | h3 {{'options_group_virtualProfileReplace' | tr}} 13 | p.help-block(omega-html='"options_virtualProfileReplaceHelp" | tr:[$profile("profileByName(profile.defaultProfileName)")]') 14 | .form-group 15 | button.btn.btn-default(ng-click='replaceProfile(profile.defaultProfileName, profile.name)') 16 | span.glyphicon.glyphicon-search 17 | = ' ' 18 | | {{'options_virtualProfileReplace' | tr}} 19 | -------------------------------------------------------------------------------- /omega-web/src/partials/rename_profile.jade: -------------------------------------------------------------------------------- 1 | form(ng-submit='renameProfile.$valid && $close(newName)' name='renameProfile') 2 | .modal-header 3 | button.close(type='button' ng-click='$dismiss()') 4 | span(aria-hidden='true') × 5 | span.sr-only {{'dialog_close' | tr}} 6 | h4.modal-title {{'options_modalHeader_renameProfile' | tr}} 7 | .modal-body 8 | .form-group(ng-class='{"has-error": !renameProfile.profileNewName.$valid}') 9 | label(for='profile-new-name') {{'options_renameProfileName' | tr}} 10 | input#profile-new-name.form-control(type='text' name='profileNewName' required ng-model='newName' 11 | ui-validate='validateProfileName' ng-init='newName = fromName') 12 | .help-block(ng-show='renameProfile.profileNewName.$error.required') {{'options_profileNameEmpty' | tr}} 13 | .help-block(ng-show='renameProfile.profileNewName.$error.reserved') {{'options_profileNameReserved' | tr}} 14 | .help-block(ng-show='!renameProfile.profileNewName.$error.reserved && renameProfile.profileNewName.$error.conflict') 15 | | {{'options_profileNameConflict' | tr}} 16 | .help-block(ng-show='renameProfile.profileNewName.$valid && newName && isProfileNameHidden(newName)') 17 | .text-info 18 | span.glyphicon.glyphicon-info-sign 19 | = ' ' 20 | | {{'options_profileNameHidden' | tr}} 21 | .modal-footer 22 | button.btn.btn-default(type='button' ng-click='$dismiss()') {{'dialog_cancel' | tr}} 23 | button.btn.btn-primary(type='submit' ng-disabled='!renameProfile.$valid') {{'options_renameProfile' | tr}} 24 | -------------------------------------------------------------------------------- /omega-web/src/partials/replace_profile.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(type='button' ng-click='$dismiss()') 3 | span(aria-hidden='true') × 4 | span.sr-only {{'dialog_close' | tr}} 5 | h4.modal-title {{'options_modalHeader_replaceProfile' | tr}} 6 | .modal-body 7 | p(omega-html='"options_replaceProfileConfirm" | tr:[profileSelect("fromName"), profileSelect("toName")]') 8 | .well 9 | span(omega-profile-inline='profileByName(fromName)' options='options' disp-name='dispNameFilter') 10 | = ' ' 11 | span.glyphicon.glyphicon-chevron-right 12 | = ' ' 13 | span(omega-profile-inline='profileByName(toName)' options='options' disp-name='dispNameFilter') 14 | .help-block(omega-html="'options_replaceProfileHelp' | tr:[$profile('profileByName(fromName)'), $profile('profileByName(toName)')]") 15 | .modal-footer 16 | button.btn.btn-default(ng-click='$dismiss()') {{'dialog_cancel' | tr}} 17 | button.btn.btn-warning(type='button' ng-click='$close({fromName: fromName, toName: toName})') {{'options_replaceProfile' | tr}} 18 | -------------------------------------------------------------------------------- /omega-web/src/partials/reset_options_confirm.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(type='button' ng-click='$dismiss()') 3 | span(aria-hidden='true') × 4 | span.sr-only {{'dialog_close' | tr}} 5 | h4.modal-title {{'options_modalHeader_resetOptions' | tr}} 6 | .modal-body 7 | p.text-danger {{'options_resetOptionsConfirm' | tr}} 8 | .modal-footer 9 | button.btn.btn-default(ng-click='$dismiss()') {{'dialog_cancel' | tr}} 10 | button.btn.btn-danger(type='button' ng-click='$close("ok")') {{'options_reset' | tr}} 11 | -------------------------------------------------------------------------------- /omega-web/src/partials/rule_remove_confirm.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(type='button' ng-click='$dismiss()') 3 | span(aria-hidden='true') × 4 | span.sr-only {{'dialog_close' | tr}} 5 | h4.modal-title {{'options_modalHeader_deleteRule' | tr}} 6 | .modal-body 7 | p {{'options_deleteRuleConfirm' | tr}} 8 | div.well 9 | span.label.label-info {{'condition_' + rule.condition.conditionType | tr}} 10 | = ' ' 11 | | {{rule.condition.pattern}} 12 | span.pull-right 13 | span(omega-profile-inline='ruleProfile' disp-name='dispNameFilter' options='options') 14 | .modal-footer 15 | button.btn.btn-default(ng-click='$dismiss()') {{'dialog_cancel' | tr}} 16 | button.btn.btn-danger(type='button' ng-click='$close("ok")') {{'options_deleteRule' | tr}} 17 | -------------------------------------------------------------------------------- /omega-web/src/partials/rule_reset_confirm.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(type='button' ng-click='$dismiss()') 3 | span(aria-hidden='true') × 4 | span.sr-only {{'dialog_close' | tr}} 5 | h4.modal-title {{'options_modalHeader_resetRules' | tr}} 6 | .modal-body 7 | p {{'options_resetRulesConfirm' | tr}} 8 | .well 9 | span(omega-profile-inline='ruleProfile' disp-name='dispNameFilter' options='options') 10 | .modal-footer 11 | button.btn.btn-default(ng-click='$dismiss()') {{'dialog_cancel' | tr}} 12 | button.btn.btn-warning(type='button' ng-click='$close("ok")') {{'options_resetRules' | tr}} 13 | -------------------------------------------------------------------------------- /omega-web/src/partials/ui.jade: -------------------------------------------------------------------------------- 1 | .page-header 2 | h2 {{'options_tab_ui' | tr}} 3 | section.settings-group 4 | h3 {{'options_group_miscOptions' | tr}} 5 | div.checkbox 6 | label 7 | input(type='checkbox' ng-model='options["-confirmDeletion"]') 8 | span {{'options_confirmDeletion' | tr}} 9 | div.checkbox 10 | label 11 | input#refresh-on-profile-change(type='checkbox' ng-model='options["-refreshOnProfileChange"]') 12 | span {{'options_refreshOnProfileChange' | tr}} 13 | div.checkbox 14 | label 15 | input(type='checkbox' ng-model='options["-showInspectMenu"]') 16 | span {{'options_showInspectMenu' | tr}} 17 | div.checkbox 18 | label 19 | input(type='checkbox' ng-model='options["-addConditionsToBottom"]') 20 | span {{'options_addConditionsToBottom' | tr}} 21 | section.settings-group 22 | h3 {{'options_group_keyboardShortcut' | tr}} 23 | p 24 | button.btn.btn-default(type='button' role='button' ng-click='openShortcutConfig()') 25 | span.glyphicon.glyphicon-share-alt 26 | = ' ' 27 | | {{'options_menuShortcutConfigure' | tr}} 28 | = ' ' 29 | | {{'options_menuShortcutHelp' | tr}} 30 | p.help-block 31 | | {{'options_menuShortcutMore' | tr}} 32 | section.settings-group 33 | h3 {{'options_group_switchOptions' | tr}} 34 | div.form-group 35 | label {{'options_startupProfile' | tr}} 36 | = ' ' 37 | div(omega-profile-select='options | profiles:"all"' ng-model='options["-startupProfileName"]' 38 | default-text="{{'options_startupProfile_none' | tr}}" disp-name='dispNameFilter' 39 | style='display: inline-block;' options='options') 40 | div.checkbox 41 | label 42 | input(type='checkbox' ng-model='options["-showConditionTypes"]' ng-true-value='1' ng-false-value='0') 43 | span {{'options_showConditionTypesAdvanced' | tr}} 44 | p.help-block 45 | | {{'options_showConditionTypesAdvancedHelp' | tr}} 46 | div.checkbox 47 | label 48 | input(type='checkbox' ng-model='options["-enableQuickSwitch"]') 49 | span {{'options_quickSwitch' | tr}} 50 | #quick-switch-settings.settings-group(ng-show='options["-enableQuickSwitch"]' ng-controller='QuickSwitchCtrl') 51 | h4 {{'options_cycledProfiles' | tr}} 52 | p.help-block {{'options_cycledProfilesHelp' | tr}} 53 | div.has-error(ng-show='options["-quickSwitchProfiles"].length < 2') 54 | p.help-block {{'options_cycledProfilesTooFew' | tr}} 55 | ul.cycle-profile-container.cycle-enabled(ui-sortable="sortableOptions" 56 | ng-model='options["-quickSwitchProfiles"]') 57 | li(ng-repeat='name in options["-quickSwitchProfiles"]') 58 | span(omega-profile-inline='profileByName(name)' options='options' disp-name='dispNameFilter') 59 | h4 {{'options_notCycledProfiles' | tr}} 60 | ul.cycle-profile-container(ui-sortable="sortableOptions" ng-model='notCycledProfiles') 61 | li.bg-success(ng-repeat='name in notCycledProfiles') 62 | span(omega-profile-inline='profileByName(name)' options='options' disp-name='dispNameFilter') 63 | -------------------------------------------------------------------------------- /omega-web/src/popup/css/dialog.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2017 The SwitchyOmega Authors. Please see the AUTHORS file 3 | * for details. 4 | * Based on Bootstrap v3.3.2 (http://getbootstrap.com) 5 | * Copyright 2011-2015 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | */ 8 | 9 | /* Dialog */ 10 | 11 | body, html { 12 | margin: 0; 13 | padding: 0; 14 | } 15 | 16 | p { 17 | margin: 0 0 1em 0; 18 | } 19 | 20 | .om-dialog { 21 | min-width: 400px; 22 | padding: 10px 10px; 23 | font-size: 14px; 24 | } 25 | 26 | .om-text-danger { 27 | color: #a94442; 28 | } 29 | 30 | .om-dialog-help { 31 | display: block; 32 | margin-top: 5px; 33 | margin-bottom: 10px; 34 | color: #737373; 35 | } 36 | 37 | .om-dialog-controls { 38 | margin-bottom: 0; 39 | } 40 | 41 | .om-dialog-controls .om-btn-primary { 42 | float: right; 43 | } 44 | 45 | /* Button */ 46 | 47 | .om-btn { 48 | display: inline-block; 49 | padding: 6px 12px; 50 | margin-bottom: 0; 51 | font-size: 14px; 52 | font-weight: 400; 53 | line-height: 1.42857143; 54 | text-align: center; 55 | white-space: nowrap; 56 | vertical-align: middle; 57 | -ms-touch-action: manipulation; 58 | touch-action: manipulation; 59 | cursor: pointer; 60 | -webkit-user-select: none; 61 | -moz-user-select: none; 62 | -ms-user-select: none; 63 | user-select: none; 64 | background-image: none; 65 | border: 1px solid transparent; 66 | border-radius: 4px 67 | } 68 | 69 | .om-btn.active, .om-btn:active { 70 | background-image: none; 71 | outline: 0; 72 | -webkit-box-shadow: inset 0 3px 5px rgba(0,0,0,.125); 73 | box-shadow: inset 0 3px 5px rgba(0,0,0,.125); 74 | } 75 | 76 | .om-btn-default { 77 | color: #333; 78 | background-color: #fff; 79 | border-color: #ccc; 80 | } 81 | 82 | .om-btn-default:hover { 83 | background-color: #e6e6e6; 84 | border-color: #adadad; 85 | } 86 | 87 | .om-btn-link { 88 | font-weight: 400; 89 | color: #337ab7; 90 | border-radius: 0; 91 | background-color: rgba(0, 0, 0, 0); 92 | -webkit-box-shadow: none; 93 | box-shadow: none; 94 | border-color: rgba(0, 0, 0, 0); 95 | } 96 | 97 | .om-btn-link:hover { 98 | color: #23527c; 99 | text-decoration: underline; 100 | background-color: rgba(0, 0, 0, 0); 101 | } 102 | 103 | .om-btn-link:active { 104 | background-color: rgba(0, 0, 0, 0); 105 | -webkit-box-shadow: none; 106 | box-shadow: none; 107 | } 108 | 109 | .om-btn-primary { 110 | color: #fff; 111 | background-color: #337ab7; 112 | border-color: #2e6da4; 113 | } 114 | 115 | .om-btn-primary:hover { 116 | color: #fff; 117 | background-color: #286090; 118 | border-color: #204d74; 119 | } 120 | -------------------------------------------------------------------------------- /omega-web/src/popup/css/index.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2017 The SwitchyOmega Authors. Please see the AUTHORS file 3 | * for details. 4 | * Based on Bootstrap v3.3.2 (http://getbootstrap.com) 5 | * Copyright 2011-2015 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | */ 8 | 9 | /* Layout */ 10 | 11 | html, body { 12 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; 13 | font-size: 14px; 14 | padding: 0; 15 | margin: 0; 16 | } 17 | 18 | body { 19 | min-width: 12em; 20 | } 21 | 22 | .om-hidden { 23 | display: none !important; 24 | } 25 | 26 | /* Menu */ 27 | 28 | .om-nav { 29 | list-style: none; 30 | padding: 0; 31 | margin: 0; 32 | } 33 | 34 | .om-nav-item { 35 | display: block; 36 | margin: 2px 0; 37 | } 38 | 39 | .om-nav-item > a > .glyphicon { 40 | margin-right: 8px; 41 | } 42 | 43 | .om-nav-item > a { 44 | display: block; 45 | padding: 5px 25px 5px 8px; 46 | border-radius: 4px; 47 | line-height: 1.5em; 48 | font-size: 1em; 49 | color: #337ab7; 50 | text-decoration: none; 51 | white-space: nowrap; 52 | cursor: pointer; 53 | } 54 | .om-nav-item > a:hover { 55 | text-decoration: none; 56 | background-color: #eee; 57 | } 58 | .om-nav-item > a:hover { 59 | color: #23527c; 60 | } 61 | .om-nav-item.om-effective > a { 62 | background-color: #d9edf7; 63 | } 64 | .om-nav-item.om-active > a { 65 | color: #fff; 66 | background-color: #337ab7; 67 | } 68 | 69 | .om-divider { 70 | height: 1px; 71 | overflow: hidden; 72 | background-color: #E5E5E5; 73 | } 74 | 75 | .om-reqinfo { 76 | background-color: #fcf8e3; 77 | } 78 | 79 | .glyphicon-warning-sign { 80 | color: #8a6d3b; 81 | } 82 | 83 | /* Dropdown */ 84 | 85 | .om-dropdown { 86 | display: none; 87 | list-style: none; 88 | padding: 5px 0; 89 | margin: 0 5px !important; 90 | border: 1px solid rgba(0,0,0,.15); 91 | border-radius: 4px; 92 | box-shadow: 0 6px 12px rgba(0,0,0,.175); 93 | } 94 | 95 | .om-open .om-dropdown { 96 | display: block; 97 | } 98 | 99 | .om-dropdown .om-nav-item { 100 | margin: 0; 101 | } 102 | 103 | .om-dropdown .om-nav-item > a { 104 | padding: 3px 20px; 105 | line-height: 1.3em; 106 | margin: 0; 107 | } 108 | 109 | .om-caret { 110 | display: inline-block; 111 | width: 0; 112 | height: 0; 113 | margin-left: 2px; 114 | vertical-align: middle; 115 | border-top: 4px solid; 116 | border-right: 4px solid transparent; 117 | border-left: 4px solid transparent; 118 | } 119 | 120 | .om-virtual-profile-icon { 121 | border: dotted 1px; 122 | margin: -1px; 123 | } 124 | 125 | /* Default Edit */ 126 | 127 | .om-has-edit { 128 | padding-right: 32px; 129 | position: relative; 130 | } 131 | 132 | .om-edit-toggle { 133 | background-color: #fff; 134 | border: 1px solid #ccc; 135 | color: #337ab7; 136 | display: inline-block; 137 | margin: -5px 0; 138 | text-align: center; 139 | padding: 5px 8px 3px; 140 | position: absolute; 141 | right: 0; 142 | } 143 | 144 | .om-edit-toggle:hover { 145 | background-color: #e6e6e6; 146 | border-color: #adadad; 147 | } 148 | 149 | /* Keyboard */ 150 | 151 | .om-keyboard-help { 152 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace; 153 | border: solid 1px #000; 154 | border-radius: 2px; 155 | display: inline-block; 156 | color: #000; 157 | box-shadow: 1px 1px; 158 | width: 1em; 159 | height: 1em; 160 | line-height: 1em; 161 | text-align: center; 162 | margin-top: -3px; 163 | } 164 | 165 | /* Glyphicons */ 166 | 167 | @font-face { 168 | font-family: 'Glyphicons Halflings'; 169 | src: url(../../../lib/bootstrap/fonts/glyphicons-halflings-regular.eot); 170 | src: url(../../../lib/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../../../lib/bootstrap/fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../../../lib/bootstrap/fonts/glyphicons-halflings-regular.woff) format('woff'),url(../../../lib/bootstrap/fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../../../lib/bootstrap/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg') 171 | } 172 | 173 | .glyphicon { 174 | position: relative; 175 | top: 1px; 176 | display: inline-block; 177 | font-family: 'Glyphicons Halflings'; 178 | font-style: normal; 179 | font-weight: 400; 180 | line-height: 1; 181 | -webkit-font-smoothing: antialiased; 182 | -moz-osx-font-smoothing: grayscale; 183 | } 184 | 185 | .glyphicon-transfer:before { 186 | content: "\e178"; 187 | } 188 | .glyphicon-off:before { 189 | content: "\e017"; 190 | } 191 | .glyphicon-warning-sign:before { 192 | content: "\e107"; 193 | } 194 | .glyphicon-file:before { 195 | content: "\e022" 196 | } 197 | .glyphicon-globe:before { 198 | content: "\e135" 199 | } 200 | .glyphicon-question-sign:before { 201 | content: "\e085" 202 | } 203 | .glyphicon-list:before { 204 | content: "\e056" 205 | } 206 | .glyphicon-retweet:before { 207 | content: "\e115" 208 | } 209 | .glyphicon-plus:before { 210 | content: "\2b" 211 | } 212 | .glyphicon-filter:before { 213 | content: "\e138" 214 | } 215 | .glyphicon-wrench:before { 216 | content: "\e136" 217 | } 218 | .glyphicon-chevron-down:before { 219 | content: "\e114"; 220 | } 221 | -------------------------------------------------------------------------------- /omega-web/src/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SwitchyOmega Popup 6 | 7 | 8 | 9 | 10 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /omega-web/src/popup/js/i18n.js: -------------------------------------------------------------------------------- 1 | $script.ready('om-page-info', function() { 2 | document.querySelector('#js-direct .om-profile-name').textContent = 3 | OmegaTargetPopup.getMessage('profile_direct'); 4 | document.querySelector('#js-system .om-profile-name').textContent = 5 | OmegaTargetPopup.getMessage('profile_system'); 6 | document.querySelector('#js-addrule-label').textContent = 7 | OmegaTargetPopup.getMessage('popup_addCondition'); 8 | document.querySelector('#js-option-label').textContent = 9 | OmegaTargetPopup.getMessage('popup_showOptions'); 10 | }); 11 | -------------------------------------------------------------------------------- /omega-web/src/popup/js/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | handleClick('js-option', showOptions); 3 | handleClick('js-temprule', showTempRuleDropdown); 4 | handleClick('js-direct', applyProfile.bind(this, 'direct')); 5 | handleClick('js-system', applyProfile.bind(this, 'system')); 6 | OmegaPopup.addTempRule = addTempRule; 7 | OmegaPopup.setDefaultProfile = setDefaultProfile; 8 | OmegaPopup.applyProfile = applyProfile; 9 | return; 10 | 11 | function handleClick(id, handler) { 12 | document.getElementById(id).addEventListener('click', handler, false); 13 | } 14 | 15 | function closePopup() { 16 | window.close(); 17 | // If the popup is opened as a tab, the above won't work. Let's reload then. 18 | document.body.style.opacity = 0; 19 | setTimeout(function() { history.go(0); }, 300); 20 | } 21 | 22 | function showOptions(e) { 23 | if (typeof OmegaTargetPopup !== 'undefined') { 24 | try { 25 | OmegaTargetPopup.openOptions(null, closePopup); 26 | e.preventDefault(); 27 | } catch (_) { 28 | } 29 | } 30 | } 31 | 32 | function applyProfile(profileName) { 33 | $script.ready('om-target', function() { 34 | OmegaTargetPopup.applyProfile(profileName, closePopup); 35 | }); 36 | } 37 | 38 | function setDefaultProfile(profileName, defaultProfileName) { 39 | $script.ready('om-target', function() { 40 | OmegaTargetPopup.setDefaultProfile(profileName, defaultProfileName, 41 | closePopup); 42 | }); 43 | } 44 | 45 | function addTempRule(domain, profileName) { 46 | $script.ready('om-target', function() { 47 | OmegaTargetPopup.addTempRule(domain, profileName, closePopup); 48 | }); 49 | } 50 | 51 | function showTempRuleDropdown() { 52 | $script.ready('om-dropdowns', function() { 53 | OmegaPopup.showTempRuleDropdown(); 54 | }); 55 | } 56 | })(); 57 | -------------------------------------------------------------------------------- /omega-web/src/popup/js/keyboard.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var keyHandler = { 3 | 38: moveUp, // Up 4 | 40: moveDown, // Down 5 | 37: closeDropdown, // Left 6 | 39: openDropdown, // Right 7 | 8 | 72: closeDropdown, // h 9 | 74: moveDown, // j 10 | 75: moveUp, // k 11 | 76: openDropdown, // l 12 | 13 | 191: showKeyboardHelp, // / 14 | 63: showKeyboardHelp, // ? 15 | 16 | 48: 'js-direct', // 0 17 | 83: 'js-system', // s 18 | 69: 'js-external', // e 19 | 65: 'js-addrule', // a 20 | 187: 'js-addrule', // +, = 21 | 84: 'js-temprule', // t 22 | 79: 'js-option', // o 23 | 82: 'js-reqinfo', // r 24 | }; 25 | 26 | for (i = 1; i <= 9; i++) { 27 | keyHandler[48 + i] = 'js-profile-' + i; 28 | } 29 | 30 | var walker; 31 | return init(); 32 | 33 | function init() { 34 | walker = document.createTreeWalker( 35 | document.querySelector('.om-nav'), 36 | NodeFilter.SHOW_ELEMENT, 37 | {acceptNode: tabbableElementsOnly} 38 | ); 39 | 40 | window.addEventListener('keydown', function(e) { 41 | var handler = keyHandler[e.keyCode]; 42 | if (!handler) console.log(e.keyCode); 43 | if (handler == null) return; 44 | if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; 45 | if (typeof handler === 'string') { 46 | clickById(handler); 47 | } else { 48 | handler(); 49 | } 50 | }); 51 | 52 | $script.ready('om-profile-items', function() { 53 | var activeNavLink = document.querySelector('.om-nav-item.om-active > a'); 54 | if (activeNavLink) activeNavLink.focus(); 55 | }); 56 | } 57 | 58 | function tabbableElementsOnly(node) { 59 | if (node.classList.contains('om-hidden')) { 60 | return NodeFilter.FILTER_REJECT; 61 | } else if (node.classList.contains('om-dropdown') && 62 | !node.parentElement.classList.contains('om-open')) { 63 | return NodeFilter.FILTER_REJECT; 64 | } else if (node.tabIndex >= 0) { 65 | return NodeFilter.FILTER_ACCEPT; 66 | } else { 67 | return NodeFilter.FILTER_SKIP; 68 | } 69 | } 70 | 71 | function moveUp() { 72 | walker.currentNode = document.activeElement; 73 | var result = null; 74 | if (walker.currentNode) { 75 | result = walker.previousNode(); 76 | } 77 | if (!result) { 78 | walker.currentNode = walker.root.lastElementChild; 79 | walker.previousNode(); 80 | walker.nextNode(); 81 | } 82 | walker.currentNode.focus(); 83 | } 84 | 85 | function moveDown() { 86 | walker.currentNode = document.activeElement; 87 | var result = null; 88 | if (walker.currentNode) { 89 | result = walker.nextNode(); 90 | } 91 | if (!result) { 92 | walker.currentNode = walker.root; 93 | walker.nextNode(); 94 | } 95 | walker.currentNode.focus(); 96 | } 97 | 98 | function openDropdown() { 99 | var container = document.querySelector('.om-open'); 100 | if (container) { 101 | // Existing dropdown. Just move to it. 102 | container.querySelector('a').focus(); 103 | return; 104 | } 105 | var selectedItem = document.activeElement; 106 | if (!selectedItem || !selectedItem.parentElement) return; 107 | if (selectedItem.parentElement.classList.contains('om-has-dropdown')) { 108 | var toggle = selectedItem.querySelector('.om-edit-toggle'); 109 | if (toggle) { 110 | toggle.click(); 111 | } else { 112 | selectedItem.click(); 113 | } 114 | } 115 | } 116 | 117 | function closeDropdown() { 118 | var container = document.querySelector('.om-open'); 119 | if (container) { 120 | container.classList.remove('om-open'); 121 | container.querySelector('a').focus(); 122 | } 123 | } 124 | 125 | function showKeyboardHelp() { 126 | $script('js/keyboard_help.js'); 127 | } 128 | 129 | function clickById(id) { 130 | var element = document.getElementById(id); 131 | if (element) element.click(); 132 | } 133 | 134 | })(); 135 | -------------------------------------------------------------------------------- /omega-web/src/popup/js/keyboard_help.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var keyForId = { 3 | 'js-direct': '0', 4 | 'js-system': 'S', 5 | 'js-external': 'E', 6 | 'js-addrule': 'A', 7 | 'js-temprule': 'T', 8 | 'js-option': 'O', 9 | 'js-reqinfo': 'R' 10 | } 11 | Object.keys(keyForId).forEach(function (id) { 12 | showHelp(id, keyForId[id]); 13 | }); 14 | 15 | for (var i = 1; i <= 9; i++) { 16 | showHelp('js-profile-' + i, '' + i); 17 | } 18 | 19 | return; 20 | 21 | function showHelp(id, key) { 22 | var element = document.getElementById(id); 23 | if (!element) return; 24 | if (!element.querySelector('.om-keyboard-help')) { 25 | var span = document.createElement('span'); 26 | span.classList.add('om-keyboard-help'); 27 | span.textContent = key; 28 | var reference = element.querySelector('.glyphicon'); 29 | reference.parentNode.insertBefore(span, reference.nextSibling); 30 | } 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /omega-web/src/popup/js/loader.js: -------------------------------------------------------------------------------- 1 | window.OmegaPopup = {}; 2 | $script(['js/index.js', 'js/profiles.js', 'js/keyboard.js'], 'om-main'); 3 | $script(['js/i18n.js']); 4 | $script('../js/omega_target_popup.js', 'om-target', function() { 5 | OmegaTargetPopup.getActivePageInfo(function(err, info) { 6 | window.OmegaPopup.pageInfo = info; 7 | $script.done('om-page-info'); 8 | }); 9 | OmegaTargetPopup.getState([ 10 | 'availableProfiles', 11 | 'currentProfileName', 12 | 'validResultProfiles', 13 | 'isSystemProfile', 14 | 'currentProfileCanAddRule', 15 | 'proxyNotControllable', 16 | 'externalProfile', 17 | 'showExternalProfile', 18 | ], function(err, state) { 19 | window.OmegaPopup.state = state; 20 | $script.done('om-state'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /omega-web/src/popup/js/proxy_not_controllable.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function closePopup() { 3 | window.close(); 4 | // If the popup is opened as a tab, the above won't work. Let's reload then. 5 | document.body.style.opacity = 0; 6 | setTimeout(function() { history.go(0); }, 300); 7 | } 8 | var closeButton = document.getElementById('js-close'); 9 | closeButton.addEventListener('click', closePopup, false); 10 | 11 | var manageButton = document.getElementById('js-manage-ext'); 12 | manageButton.addEventListener('click', function () { 13 | OmegaTargetPopup.openManage(closePopup); 14 | }, false); 15 | 16 | var learnMoreButton = document.getElementById('js-nc-learn-more'); 17 | learnMoreButton.addEventListener('click', function () { 18 | OmegaTargetPopup.openOptions('#!/general', closePopup); 19 | }, false); 20 | 21 | closeButton.textContent = OmegaTargetPopup.getMessage('dialog_cancel'); 22 | learnMoreButton.textContent = 'Learn More' 23 | //OmegaTargetPopup.getMessage('popup_proxyNotControllableLearnMore'); 24 | manageButton.textContent = OmegaTargetPopup.getMessage( 25 | 'popup_proxyNotControllableManage'); 26 | 27 | 28 | OmegaTargetPopup.getState([ 29 | 'proxyNotControllable', 30 | ], function(err, state) { 31 | var reason = state.proxyNotControllable; 32 | var messageElement = document.getElementById('js-nc-text'); 33 | var detailsElement = document.getElementById('js-nc-details'); 34 | messageElement.textContent = OmegaTargetPopup.getMessage( 35 | 'popup_proxyNotControllable_' + reason); 36 | var detailsMessage = OmegaTargetPopup.getMessage( 37 | 'popup_proxyNotControllableDetails_' + reason); 38 | if (!detailsMessage) detailsMessage = OmegaTargetPopup.getMessage( 39 | 'popup_proxyNotControllableDetails'); 40 | 41 | detailsElement.textContent = detailsMessage; 42 | }); 43 | })(); 44 | -------------------------------------------------------------------------------- /omega-web/src/popup/proxy_not_controllable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SwitchyOmega Popup 6 | 7 | 8 | 9 | 10 |
11 |

12 |

13 |

14 | 15 | 16 | 17 |

18 |
19 | 20 | 21 | 22 | 23 | --------------------------------------------------------------------------------