├── extlib └── .gitkeep ├── chrome ├── .gitignore ├── Makefile ├── .eslintrc.js ├── manifest.json └── background.js ├── misc ├── 128x128.png ├── 440x280.png └── screenshot.png ├── host ├── .gitignore ├── com.clear_code.ieview_we_host.json ├── com.clear_code.ieview_we_host.chrome.json ├── com.clear_code.ieview_we_host.edge.json ├── uninstall.bat ├── wix.json ├── install.bat ├── templates │ ├── LicenseAgreementDlg_HK.wxs │ ├── WixUI_HK.wxs │ └── product.wxs.template ├── build.sh ├── host.go └── LICENSE ├── .gitignore ├── .eslintignore ├── SECURITY.md ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── bug_report.yaml │ └── release_task.yaml ├── release.yml └── PULL_REQUEST_TEMPLATE.md ├── background ├── .eslintrc.js └── background.js ├── common ├── .eslintrc.js └── common.js ├── options ├── .eslintrc.js ├── init.js └── options.html ├── .gitmodules ├── managed-storage ├── edge-use-thinbridge.reg ├── chrome-use-thinbridge.reg ├── edge-use-browserselector.reg ├── chrome-use-browserselector.reg ├── ieview-we@clear-code.com.json ├── uninstall.bat ├── policies.json └── install.bat ├── package.json ├── manifest.json ├── DEVELOPMENT.md ├── managed_schema.json ├── _locales ├── ja │ └── messages.json └── en │ └── messages.json ├── .eslintrc.js ├── tools └── eslint │ └── for-module.js ├── Makefile ├── README.md └── LICENSE /extlib/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chrome/.gitignore: -------------------------------------------------------------------------------- 1 | *.zip 2 | dev/ 3 | 4 | -------------------------------------------------------------------------------- /misc/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/ieview-we/HEAD/misc/128x128.png -------------------------------------------------------------------------------- /misc/440x280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/ieview-we/HEAD/misc/440x280.png -------------------------------------------------------------------------------- /misc/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/ieview-we/HEAD/misc/screenshot.png -------------------------------------------------------------------------------- /host/.gitignore: -------------------------------------------------------------------------------- 1 | 386 2 | amd64 3 | outdir 4 | templates/product.wxs 5 | *.msi 6 | *.exe 7 | build_msi.bat 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.xpi 2 | *.zip 3 | extlib/*.js 4 | *.exe 5 | .goutputstream-* 6 | 7 | node_modules 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # include as a lint target. 2 | !.eslintrc.js 3 | 4 | chrome/ 5 | 6 | # The Makefile will copy external libraries into this directory 7 | extlib/ 8 | submodules/ 9 | -------------------------------------------------------------------------------- /chrome/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean zip 2 | 3 | FILES = manifest.json \ 4 | background.js 5 | 6 | all: zip 7 | 8 | clean: 9 | rm -f *.zip 10 | 11 | zip: $(FILES) 12 | zip -9 - $(FILES) > ieview-we-chrome.zip 13 | -------------------------------------------------------------------------------- /host/com.clear_code.ieview_we_host.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.clear_code.ieview_we_host", 3 | "description": "IE View WE Native Messaging Host", 4 | "path": "host.exe", 5 | "type": "stdio", 6 | "allowed_extensions": [ 7 | "ieview-we@clear-code.com" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 1.6.x | :white_check_mark: | 8 | | < 1.6.1 | :x: | 9 | 10 | ## Reporting a Vulnerability 11 | 12 | Contact firefox-support@clear-code.com 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Report a vulnerability 4 | url: https://github.com/clear-code/ieview-we/security/policy 5 | about: For reporting a vulnerability, please refer our Security Policy here: https://github.com/clear-code/ieview-we/security/policy 6 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | # .github/release.yml 2 | 3 | changelog: 4 | categories: 5 | - title: Breaking Changes 🛠 6 | labels: 7 | - breaking-change 8 | - title: New Features 🎉 9 | labels: 10 | - enhancement 11 | - title: Bug Fixes 12 | labels: 13 | - bug 14 | - title: Other Changes 15 | labels: 16 | - "*" 17 | -------------------------------------------------------------------------------- /host/com.clear_code.ieview_we_host.chrome.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.clear_code.ieview_we_host", 3 | "description": "IE View WE Native Messaging Host", 4 | "path": "__INSTALL_PATH__", 5 | "type": "stdio", 6 | "allowed_origins": [ 7 | "chrome-extension://nifnmbbelnhfhgeiampkfghakhmgggcf/", 8 | "chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/", 9 | "chrome-extension://gahanoflpdcmbcaijjkopjeheaikclcl/" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /host/com.clear_code.ieview_we_host.edge.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.clear_code.ieview_we_host", 3 | "description": "IE View WE Native Messaging Host", 4 | "path": "__INSTALL_PATH__", 5 | "type": "stdio", 6 | "allowed_origins": [ 7 | "chrome-extension://nifnmbbelnhfhgeiampkfghakhmgggcf/", 8 | "chrome-extension://ifacgepgdnnddnckleiinelkadppopgh/", 9 | "chrome-extension://mfdehfklpjemdaaanpfdnhiflhjhlnjl/" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /background/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | /*eslint-env commonjs*/ 7 | /*eslint quote-props: ['error', "always"] */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = { 12 | 'extends': [ 13 | '../tools/eslint/for-module.js', 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /chrome/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | /*eslint-env commonjs*/ 7 | /*eslint quote-props: ['error', "always"] */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = { 12 | 'extends': [ 13 | '../tools/eslint/for-module.js', 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /common/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | /*eslint-env commonjs*/ 7 | /*eslint quote-props: ['error', "always"] */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = { 12 | 'extends': [ 13 | '../tools/eslint/for-module.js', 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /options/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | /*eslint-env commonjs*/ 7 | /*eslint quote-props: ['error', "always"] */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = { 12 | 'extends': [ 13 | '../tools/eslint/for-module.js', 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Which issue(s) this PR fixes: 2 | 3 | > Describe issue number. If issue doesn't exist, fill N/A. 4 | 5 | # What this PR does / why we need it: 6 | 7 | > Explain the detail of the problem. 8 | 9 | 10 | # How to verify the fixed issue: 11 | 12 | > Describe the following information to help that the tester able to do it. 13 | 14 | ## The steps to verify: 15 | 16 | 1. 17 | 2. 18 | 19 | ## Expected result: 20 | 21 | > Describe the obvious situation to verify. 22 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submodules/webextensions-lib-configs"] 2 | path = submodules/webextensions-lib-configs 3 | url = https://github.com/piroor/webextensions-lib-configs.git 4 | [submodule "submodules/webextensions-lib-l10n"] 5 | path = submodules/webextensions-lib-l10n 6 | url = https://github.com/piroor/webextensions-lib-l10n.git 7 | [submodule "submodules/webextensions-lib-options"] 8 | path = submodules/webextensions-lib-options 9 | url = https://github.com/piroor/webextensions-lib-options.git 10 | -------------------------------------------------------------------------------- /chrome/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "IE View WE MV3", 4 | "version": "3.0.0", 5 | "description": "Provides ability to open pages and links by other browsers based on pre-defined rules for ThinBridge.", 6 | "permissions": [ 7 | "nativeMessaging", 8 | "alarms", 9 | "scripting", 10 | "storage", 11 | "tabs", 12 | "webNavigation", 13 | "webRequest", 14 | "webRequestBlocking" 15 | ], 16 | "host_permissions": [ 17 | "" 18 | ], 19 | "background": { 20 | "service_worker": "background.js", 21 | "type": "module" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /managed-storage/edge-use-thinbridge.reg: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | 3 | [HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\3rdparty\extensions\ifacgepgdnnddnckleiinelkadppopgh\policy] 4 | "debug"=dword:00000001 5 | "talkEnabled"=dword:00000001 6 | "talkServerName"="com.clear_code.thinbridge" 7 | "talkBrowserName"="edge" 8 | 9 | [HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\3rdparty\extensions\mfdehfklpjemdaaanpfdnhiflhjhlnjl\policy] 10 | "debug"=dword:00000001 11 | "talkEnabled"=dword:00000001 12 | "talkServerName"="com.clear_code.thinbridge" 13 | "talkBrowserName"="edge" 14 | -------------------------------------------------------------------------------- /managed-storage/chrome-use-thinbridge.reg: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | 3 | [HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\3rdparty\extensions\nifnmbbelnhfhgeiampkfghakhmgggcf\policy] 4 | "debug"=dword:00000001 5 | "talkEnabled"=dword:00000001 6 | "talkServerName"="com.clear_code.thinbridge" 7 | "talkBrowserName"="chrome" 8 | 9 | [HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\3rdparty\extensions\gahanoflpdcmbcaijjkopjeheaikclcl\policy] 10 | "debug"=dword:00000001 11 | "talkEnabled"=dword:00000001 12 | "talkServerName"="com.clear_code.thinbridge" 13 | "talkBrowserName"="chrome" 14 | -------------------------------------------------------------------------------- /managed-storage/edge-use-browserselector.reg: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | 3 | [HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\3rdparty\extensions\ifacgepgdnnddnckleiinelkadppopgh\policy] 4 | "debug"=dword:00000001 5 | "talkEnabled"=dword:00000001 6 | "talkServerName"="com.clear_code.browserselector_talk" 7 | "talkBrowserName"="edge" 8 | 9 | [HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\3rdparty\extensions\mfdehfklpjemdaaanpfdnhiflhjhlnjl\policy] 10 | "debug"=dword:00000001 11 | "talkEnabled"=dword:00000001 12 | "talkServerName"="com.clear_code.browserselector_talk" 13 | "talkBrowserName"="edge" 14 | -------------------------------------------------------------------------------- /managed-storage/chrome-use-browserselector.reg: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | 3 | [HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\3rdparty\extensions\nifnmbbelnhfhgeiampkfghakhmgggcf\policy] 4 | "debug"=dword:00000001 5 | "talkEnabled"=dword:00000001 6 | "talkServerName"="com.clear_code.browserselector_talk" 7 | "talkBrowserName"="chrome" 8 | 9 | [HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\3rdparty\extensions\gahanoflpdcmbcaijjkopjeheaikclcl\policy] 10 | "debug"=dword:00000001 11 | "talkEnabled"=dword:00000001 12 | "talkServerName"="com.clear_code.browserselector_talk" 13 | "talkBrowserName"="chrome" 14 | -------------------------------------------------------------------------------- /managed-storage/ieview-we@clear-code.com.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ieview-we@clear-code.com", 3 | "description": "Managed Storage for IE View WE", 4 | "type": "storage", 5 | "data": { 6 | "forceielist" : "http://www.example.com/*", 7 | "disableForce" : false, 8 | "closeReloadPage" : true, 9 | "contextMenu" : true, 10 | "onlyMainFrame" : true, 11 | "ignoreQueryString": false, 12 | "sitesOpenedBySelf": "", 13 | "disableException" : false, 14 | "logging" : true, 15 | "talkEnabled" : false, 16 | "talkServerName" : "", 17 | "debug" : false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /managed-storage/uninstall.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | SET NAME=ieview-we@clear-code.com 4 | 5 | ECHO Uninstalling managed storage for %NAME%... 6 | 7 | ECHO Checking permission... 8 | SET INSTALL_DIR=%ProgramData%\%NAME% 9 | SET REG_BASE=HKLM 10 | MD "%INSTALL_DIR%_try" 11 | IF EXIST "%INSTALL_DIR%_try\" ( 12 | ECHO Uninstall for all users 13 | RMDIR /Q /S "%INSTALL_DIR%_try" 14 | ) ELSE ( 15 | ECHO Uninstall for the current user 16 | SET INSTALL_DIR=%LocalAppData%\%NAME% 17 | SET REG_BASE=HKCU 18 | ) 19 | 20 | REG DELETE "%REG_BASE%\SOFTWARE\Mozilla\NativeMessagingHosts\%NAME%" /f 21 | 22 | RMDIR /Q /S "%INSTALL_DIR%" 23 | 24 | ECHO Done. 25 | PAUSE 26 | -------------------------------------------------------------------------------- /managed-storage/policies.json: -------------------------------------------------------------------------------- 1 | { 2 | "policies": { 3 | "3rdparty": { 4 | "Extensions": { 5 | "ieview-we@clear-code.com": { 6 | "forceielist" : "http://www.example.com/*", 7 | "disableForce" : false, 8 | "closeReloadPage" : true, 9 | "contextMenu" : true, 10 | "onlyMainFrame" : true, 11 | "ignoreQueryString": false, 12 | "sitesOpenedBySelf": "", 13 | "disableException" : false, 14 | "logging" : true, 15 | "talkEnabled" : false, 16 | "talkServerName" : "", 17 | "talkAlarmMinutes" : 1, 18 | "debug" : false 19 | } 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /host/uninstall.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | SET NAME=com.clear_code.ieview_we_host 4 | 5 | ECHO Uninstalling %NAME%... 6 | 7 | ECHO Checking permission... 8 | SET INSTALL_DIR=%ProgramFiles%\%NAME% 9 | SET REG_BASE=HKLM 10 | MD "%INSTALL_DIR%_try" 11 | IF EXIST "%INSTALL_DIR%_try\" ( 12 | ECHO Uninstall for all users 13 | RMDIR /Q /S "%INSTALL_DIR%_try" 14 | ) ELSE ( 15 | ECHO Uninstall for the current user 16 | SET INSTALL_DIR=%LocalAppData%\%NAME% 17 | SET REG_BASE=HKCU 18 | ) 19 | 20 | REG DELETE "%REG_BASE%\SOFTWARE\Mozilla\NativeMessagingHosts\%NAME%" /f 21 | REG DELETE "%REG_BASE%\SOFTWARE\Google\Chrome\NativeMessagingHosts\%NAME%" /f 22 | REG DELETE "%REG_BASE%\SOFTWARE\Microsoft\Edge\NativeMessagingHosts\%NAME%" /f 23 | 24 | RMDIR /Q /S "%INSTALL_DIR%" 25 | 26 | ECHO Done. 27 | PAUSE 28 | -------------------------------------------------------------------------------- /managed-storage/install.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | SET NAME=ieview-we@clear-code.com 4 | 5 | ECHO Installing %NAME%... 6 | 7 | ECHO Checking permission... 8 | SET INSTALL_DIR=%ProgramData%\%NAME% 9 | SET REG_BASE=HKLM 10 | MD "%INSTALL_DIR%_try" 11 | IF EXIST "%INSTALL_DIR%_try\" ( 12 | ECHO Install for all users 13 | RMDIR /Q /S "%INSTALL_DIR%_try" 14 | ) ELSE ( 15 | ECHO Install for the current user 16 | SET INSTALL_DIR=%LocalAppData%\%NAME% 17 | SET REG_BASE=HKCU 18 | ) 19 | 20 | MD "%INSTALL_DIR%" 21 | CD /D %~dp0 22 | COPY *.json "%INSTALL_DIR%\" 23 | 24 | ECHO Registering... 25 | FOR %%f IN ("%INSTALL_DIR%") DO SET EXPANDED_PATH=%%~sf 26 | REG ADD "%REG_BASE%\SOFTWARE\Mozilla\ManagedStorage\%NAME%" /ve /t REG_SZ /d "%EXPANDED_PATH%\%NAME%.json" /f 27 | 28 | ECHO Done. 29 | PAUSE 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ieview-we", 3 | "version": "0.0.0", 4 | "engines": { 5 | "node": ">=13.2" 6 | }, 7 | "dependencies": { 8 | "webextension-polyfill": "*" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "devDependencies": { 14 | "babel-core": "^6.0.0", 15 | "babel-eslint": "^10.1.0", 16 | "babel-plugin-module-resolver": "^3.0.0", 17 | "eslint": "^7.18.0", 18 | "eslint-import-resolver-babel-module": "^4.0.0", 19 | "eslint-plugin-import": "^2.20.1", 20 | "jsonlint-cli": "*", 21 | "lodash": ">=4.17.21", 22 | "minimist": ">=1.2.2", 23 | "morphdom": "^2.5.12", 24 | "node-fetch": ">=2.6.1", 25 | "trim-newlines": ">=3.0.1", 26 | "tunnel-agent": ">=0.6.0", 27 | "underscore": ">=1.12.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "__MSG_extensionName__", 4 | "version": "2.0.1", 5 | "description": "__MSG_extensionDescription__", 6 | "permissions": [ 7 | "contextMenus", 8 | "nativeMessaging", 9 | "storage", 10 | "alarms", 11 | "tabs", 12 | "webRequest", 13 | "webRequestBlocking" 14 | ], 15 | "host_permissions": [ 16 | "" 17 | ], 18 | "background": { 19 | "service_worker": "background/background.js", 20 | "type": "module" 21 | }, 22 | "icons": { 23 | "128": "misc/128x128.png" 24 | }, 25 | "options_ui": { 26 | "page": "options/options.html" 27 | }, 28 | "default_locale": "en", 29 | "applications": { 30 | "gecko": { 31 | "id": "ieview-we@clear-code.com", 32 | "strict_min_version": "52.0" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Development Guidelines 2 | 3 | * Branch guidelines 4 | * Issue guidelines 5 | * Release guidelines 6 | 7 | ## Branch guidelines 8 | 9 | * Use master branch as stable branch 10 | * Use topic branch to implement new features 11 | * Don't directly commit to master branch 12 | 13 | ## Issue and Pull request guidelines 14 | 15 | * Create new issue before implement new features 16 | * Discuss and review specification beforehand 17 | * Create new pull request corresponding to existing issue 18 | * Each pull request must be reviewed 19 | * Assign bug, enhancement or breaking-change label to pull request 20 | 21 | ## Release guidelines 22 | 23 | * Create a new tag vX.Y.Z (such as v1.6.6) 24 | * Create new issue for release 25 | * Create new release via https://github.com/clear-code/ieview-we/releases/new 26 | * Test assets for release 27 | * Publish release note 28 | -------------------------------------------------------------------------------- /managed_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "ieapp": {"type": "string"}, 5 | "ieargs": {"type": "string"}, 6 | "forceielist": {"type": "string"}, 7 | "disableForce": {"type": "boolean"}, 8 | "closeReloadPage": {"type": "boolean"}, 9 | "closeReloadPageMaxDelayMsec": {"type": "integer"}, 10 | "contextMenu": {"type": "boolean"}, 11 | "onlyMainFrame": {"type": "boolean"}, 12 | "ignoreQueryString": {"type": "boolean"}, 13 | "sitesOpenedBySelf": {"type": "string"}, 14 | "disableException": {"type": "boolean"}, 15 | "logging": {"type": "boolean"}, 16 | "logRotationCount": {"type": "integer"}, 17 | "logRotationTime": {"type": "integer"}, 18 | "talkEnabled": {"type": "boolean"}, 19 | "talkServerName": {"type": "string"}, 20 | "talkAlarmMinutes": {"type": "integer"}, 21 | "talkBrowserName": {"type": "string"}, 22 | "debug": {"type": "boolean"} 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /host/wix.json: -------------------------------------------------------------------------------- 1 | { 2 | "product": "IE View WE Native Messaging Host", 3 | "company": "ClearCode Inc.", 4 | "upgrade-code": "a57431ca-9141-4e78-ab2d-bbba133a68c4", 5 | "files": { 6 | "guid": "8e352576-6293-4578-a649-37ac9bbd829a", 7 | "items": [ 8 | "host.exe", 9 | "com.clear_code.ieview_we_host.json", 10 | "com.clear_code.ieview_we_host.edge.json", 11 | "com.clear_code.ieview_we_host.chrome.json" 12 | ] 13 | }, 14 | "env": { 15 | "guid": "f662a755-a488-4f2f-b8ea-c5bfb521e968", 16 | "vars": [ 17 | { 18 | "name": "PATH", 19 | "value": "[INSTALLDIR]", 20 | "permanent": "no", 21 | "system": "no", 22 | "action": "set", 23 | "part": "last" 24 | } 25 | ] 26 | }, 27 | "shortcuts": {}, 28 | "choco": { 29 | "description": "IE View WE Native Messaging Host", 30 | "project-url": "https://github.com/clear-code/ieview-we", 31 | "tags": "firefox addon", 32 | "license-url": "https://github.com/clear-code/ieview-we/blob/master/webextensions/host/LICENSE" 33 | } 34 | } -------------------------------------------------------------------------------- /options/init.js: -------------------------------------------------------------------------------- 1 | /* 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 'use strict'; 7 | 8 | import Options from '/extlib/Options.js'; 9 | import '/extlib/l10n.js'; 10 | 11 | import { 12 | configs, 13 | } from '/common/common.js'; 14 | 15 | /*const options = */new Options(configs); 16 | 17 | /* 18 | * Control "BrowserSelector" section in the option page. 19 | */ 20 | const BrowserSelector = { 21 | 22 | async init() { 23 | configs.$addObserver(this.update); 24 | await configs.$loaded; 25 | this.update(); 26 | this.detectFirefox(); 27 | }, 28 | 29 | update(_key) { 30 | const fieldset = document.querySelector('#BS'); 31 | if (configs.talkEnabled) { 32 | fieldset.removeAttribute('disabled'); 33 | } else { 34 | fieldset.setAttribute('disabled', 'true'); 35 | } 36 | }, 37 | 38 | detectFirefox() { 39 | if (!browser || !browser.runtime || !browser.runtime.getBrowserInfo) 40 | return; 41 | 42 | browser.runtime.getBrowserInfo().then((info) => { 43 | if (info.name === 'Firefox') { 44 | const fieldset = document.querySelector('#BS'); 45 | fieldset.classList.add('firefox'); 46 | } 47 | }); 48 | }, 49 | } 50 | 51 | document.addEventListener('DOMContentLoaded', function() { 52 | BrowserSelector.init(); 53 | }); 54 | -------------------------------------------------------------------------------- /common/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 'use strict'; 7 | 8 | import Configs from '/extlib/Configs.js'; 9 | 10 | export function getDefaultBrowser() { 11 | /* Assume Chrome if UA is not accessible */ 12 | if (!navigator || !navigator.userAgent) 13 | return 'chrome'; 14 | 15 | if (/Edg/.test(navigator.userAgent)) 16 | return 'edge'; 17 | 18 | return 'chrome'; 19 | } 20 | 21 | export const configs = new Configs({ 22 | ieapp : '', 23 | ieargs : '', 24 | forceielist : '', 25 | disableForce : false, 26 | closeReloadPage : true, 27 | closeReloadPageMaxDelayMsec: 150, 28 | contextMenu : true, 29 | onlyMainFrame : true, 30 | ignoreQueryString: false, 31 | sitesOpenedBySelf: '', 32 | disableException : false, 33 | logging : true, 34 | logRotationCount : 12, 35 | logRotationTime : 24, 36 | talkEnabled : false, 37 | talkServerName : 'com.clear_code.browserselector_talk', 38 | talkAlarmMinutes : 1, 39 | talkBrowserName : getDefaultBrowser(), 40 | debug : false 41 | }, {logging: true}); 42 | 43 | export function log(aMessage, ...aArgs) 44 | { 45 | if (!configs || !configs.logging) 46 | return; 47 | 48 | console.log('ieview-we: ' + aMessage, ...aArgs); 49 | } 50 | 51 | export function debug(aMessage, ...aArgs) 52 | { 53 | if (!configs || !configs.debug) 54 | return; 55 | 56 | log('[DEBUG] ' + aMessage, ...aArgs); 57 | } 58 | -------------------------------------------------------------------------------- /_locales/ja/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionName": { "message": "IE View WE" }, 3 | "extensionDescription": { "message": "ページやリンクをInternet Explorerで開く機能を提供します(WebExtensionsベースのIE Viewクローン)" }, 4 | 5 | "contextMenu_page_label": { "message": "このページをIEで開く" }, 6 | "contextMenu_link_label": { "message": "このリンクをIEで開く" }, 7 | 8 | "config_ieapp_label_before": { "message": "Internet Explorerのパス" }, 9 | "config_ieargs_label_before": { "message": "起動オプション" }, 10 | "config_contextMenu_label": { "message": "コンテキストメニューに「IEで開く」を追加する" }, 11 | "config_onlyMainFrame_label": { "message": "アドレスバーに表示されるURLのみを判定する" }, 12 | "config_forceielist_caption": { "message": "Internet Explorerで常に開くWebサイト" }, 13 | "config_disableForce_label": { "message": "無効化" }, 14 | "config_closeReloadPage_label": { "message": "リダイレクト後に残された空のタブを閉じる" }, 15 | "config_sitesOpenedBySelf_caption": { "message": "IEで開く対象から除外する(このブラウザーで直接開く)サイト" }, 16 | "config_disableException_label": { "message": "無効化" }, 17 | "config_ignoreQueryString_label": { "message": "URLの?以降を無視する" }, 18 | 19 | "config_logging_label": { "message": "ログを保存する" }, 20 | "config_logRotationCount_label": { "message": "ログを指定した世代保存する" }, 21 | "config_logRotationTime_label": { "message": "ログをローテーションする間隔(時間)" }, 22 | "config_debug_label": { "message": "デバッグログを出力" }, 23 | "config_talkEnabled_label": { "message": "Talkサーバーとの対話モードを有効化する(要再起動)" }, 24 | "config_talkServerName_label": { "message": "接続するTalkサーバー" }, 25 | "config_talkAlarmMinutes_label": { "message": "TalkサーバーとN分ごとに設定を同期する" }, 26 | "config_talkBrowserName_label": { "message": "ブラウザの種別" } 27 | } 28 | -------------------------------------------------------------------------------- /_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionName": { "message": "IE View WE" }, 3 | "extensionDescription": { "message": "Provides ability to open pages and links by Internet Explorer (Cloned IE View based on WebExtensions-based)" }, 4 | 5 | "contextMenu_page_label": { "message": "Open this Page by IE" }, 6 | "contextMenu_link_label": { "message": "Open this Link by IE" }, 7 | 8 | "config_ieapp_label_before": { "message": "Path to Internet Explorer:" }, 9 | "config_ieargs_label_before": { "message": "Command Line Arguments:" }, 10 | "config_contextMenu_label": { "message": "Add \"Open by IE\" items to the context menu" }, 11 | "config_onlyMainFrame_label": { "message": "Only check URL which is shown in location bar" }, 12 | "config_forceielist_caption": { "message": "Websites to be opened by IE always" }, 13 | "config_disableForce_label": { "message": "Disable" }, 14 | "config_closeReloadPage_label": { "message": "Close blank tab left after redirection" }, 15 | "config_sitesOpenedBySelf_caption": { "message": "Exceptive websites (opened in this browser directly)" }, 16 | "config_disableException_label": { "message": "Disable" }, 17 | "config_ignoreQueryString_label": { "message": "Ignore query string in URL" }, 18 | 19 | "config_logging_label": { "message": "Save log" }, 20 | "config_logRotationCount_label": { "message": "Max count of log files" }, 21 | "config_logRotationTime_label": { "message": "Rotate log file by specified hour" }, 22 | "config_debug_label": { "message": "Print debug log" }, 23 | "config_talkEnabled_label": { "message": "Enable Talk client mode (Needs Restart)" }, 24 | "config_talkServerName_label": { "message": "Talk server to connect" }, 25 | "config_talkAlarmMinutes_label": { "message": "Sync with Talk server for every N minutes" }, 26 | "config_talkBrowserName_label": { "message": "Choose the browser model" } 27 | } 28 | -------------------------------------------------------------------------------- /host/install.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | SET NAME=com.clear_code.ieview_we_host 4 | 5 | ECHO Installing %NAME%... 6 | 7 | ECHO Checking permission... 8 | SET INSTALL_DIR=%ProgramFiles%\%NAME% 9 | SET REG_BASE=HKLM 10 | MD "%INSTALL_DIR%_try" 11 | IF EXIST "%INSTALL_DIR%_try\" ( 12 | ECHO Install for all users 13 | RMDIR /Q /S "%INSTALL_DIR%_try" 14 | ) ELSE ( 15 | ECHO Install for the current user 16 | SET INSTALL_DIR=%LocalAppData%\%NAME% 17 | SET REG_BASE=HKCU 18 | ) 19 | 20 | MD "%INSTALL_DIR%" 21 | CD /D %~dp0 22 | IF %PROCESSOR_ARCHITECTURE% == AMD64 ( 23 | ECHO Copying binary for AMD64... 24 | COPY amd64\*.exe "%INSTALL_DIR%\" 25 | ) ELSE ( 26 | ECHO Copying binary for x86... 27 | COPY 386\*.exe "%INSTALL_DIR%\" 28 | ) 29 | COPY *.json "%INSTALL_DIR%\" 30 | COPY *.bat "%INSTALL_DIR%\" 31 | 32 | DEL "%INSTALL_DIR%\%NAME%.chrome.json" 33 | DEL "%INSTALL_DIR%\%NAME%.edge.json" 34 | 35 | setlocal enabledelayedexpansion 36 | for /f "delims=" %%A in (%NAME%.chrome.json) do ( 37 | set source=%%A 38 | set install_dir_filled=!source:__INSTALL_PATH__=%INSTALL_DIR%\host.exe! 39 | set escaped=!install_dir_filled:\=\\! 40 | echo !escaped!>>"%INSTALL_DIR%\%NAME%.chrome.json" 41 | ) 42 | 43 | for /f "delims=" %%A in (%NAME%.edge.json) do ( 44 | set source=%%A 45 | set install_dir_filled=!source:__INSTALL_PATH__=%INSTALL_DIR%\host.exe! 46 | set escaped=!install_dir_filled:\=\\! 47 | echo !escaped!>>"%INSTALL_DIR%\%NAME%.edge.json" 48 | ) 49 | endlocal 50 | 51 | ECHO Registering... 52 | FOR %%f IN ("%INSTALL_DIR%") DO SET EXPANDED_PATH=%%~sf 53 | REG ADD "%REG_BASE%\SOFTWARE\Mozilla\NativeMessagingHosts\%NAME%" /ve /t REG_SZ /d "%EXPANDED_PATH%\%NAME%.json" /f 54 | REG ADD "%REG_BASE%\SOFTWARE\Google\Chrome\NativeMessagingHosts\%NAME%" /ve /t REG_SZ /d "%EXPANDED_PATH%\%NAME%.chrome.json" /f 55 | REG ADD "%REG_BASE%\SOFTWARE\Microsoft\Edge\NativeMessagingHosts\%NAME%" /ve /t REG_SZ /d "%EXPANDED_PATH%\%NAME%.edge.json" /f 56 | 57 | ECHO Done. 58 | PAUSE 59 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a report with a procedure for reproducing the bug 3 | body: 4 | - type: textarea 5 | id: description 6 | attributes: 7 | label: Describe the bug 8 | description: A clear and concise description of what the bug is 9 | validations: 10 | required: true 11 | - type: textarea 12 | id: reproduce 13 | attributes: 14 | label: To Reproduce 15 | description: Steps to reproduce the behavior 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: expected 20 | attributes: 21 | label: Expected behavior 22 | description: A clear and concise description of what you expected to happen 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: environment 27 | attributes: 28 | label: Your Environment 29 | description: | 30 | - Firefox version: 31 | - IE View WE addon version: See about:addons. 32 | 33 | Tip: If you hit the problem with older IE View WE version, try latest version first. 34 | value: | 35 | - Firefox version: 36 | - IE View WE Firefox addon version: 37 | render: markdown 38 | validations: 39 | required: true 40 | - type: textarea 41 | id: configuration 42 | attributes: 43 | label: Your Configuration 44 | description: | 45 | Write your IE View WE configuration. Minimum reproducible one is recommended. 46 | value: | 47 | - Force IE list: 48 | - Sites opened by self: 49 | render: markdown 50 | validations: 51 | required: true 52 | - type: textarea 53 | id: logs 54 | attributes: 55 | label: Your debug log 56 | description: Write your debug log here 57 | render: shell 58 | validations: 59 | required: false 60 | - type: textarea 61 | id: addtional-context 62 | attributes: 63 | label: Additional context 64 | description: Add any other context about the problem here. 65 | validations: 66 | required: false 67 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/release_task.yaml: -------------------------------------------------------------------------------- 1 | name: Issue for new release 2 | title: "Release: x.y.z" 3 | description: Create a issue for a new release 4 | body: 5 | - type: markdown 6 | id: description 7 | attributes: 8 | value: | 9 | This is a issue for new release x.y.z 10 | - type: checkboxes 11 | id: create-new-tag 12 | attributes: 13 | label: Create a new tag 14 | description: Tag a new version for planned release 15 | options: 16 | - label: New tag has been created! 17 | required: false 18 | - type: checkboxes 19 | id: create-release-notes 20 | attributes: 21 | label: Create release note 22 | description: Add release note as pre-release. 23 | options: 24 | - label: Release note has been created! 25 | required: false 26 | - type: checkboxes 27 | id: build-assets 28 | attributes: 29 | label: Create new assets for release 30 | description: | 31 | Build binary files to upload. 32 | See https://github.com/clear-code/ieview-we#how-to-build-the-native-messaging-host-and-its-installer 33 | options: 34 | - label: Asset files has been built! 35 | required: false 36 | - type: checkboxes 37 | id: test-assets 38 | attributes: 39 | label: Test assets for release 40 | description: | 41 | Test prebuilt binary files whether it is production ready. 42 | Described issues in release note should be tested. 43 | options: 44 | - label: Asset files has been attached! 45 | required: false 46 | - type: checkboxes 47 | id: publish-release-notes 48 | attributes: 49 | label: Publish release notes 50 | description: If it is ready to ship, publish the release note! 51 | options: 52 | - label: Release note has been published 53 | required: false 54 | - type: textarea 55 | id: addtional-context 56 | attributes: 57 | label: Additional context 58 | description: Add any other context about the problem here. 59 | validations: 60 | required: false 61 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | /*eslint-env commonjs*/ 7 | /*eslint quote-props: ['error', "always"] */ 8 | 9 | module.exports = { 10 | 'root': true, 11 | 12 | 'parserOptions': { 13 | 'ecmaVersion': 2020, 14 | }, 15 | 16 | 'env': { 17 | 'browser': true, 18 | 'es6': true, 19 | 'webextensions': true, 20 | }, 21 | 22 | 'settings': { 23 | 'import/resolver': { 24 | 'babel-module': { 25 | 'root': ['./'], 26 | } 27 | } 28 | }, 29 | 30 | 'rules': { 31 | 'no-const-assign': 'error', 32 | 'prefer-const': ['warn', { 33 | 'destructuring': 'any', 34 | 'ignoreReadBeforeAssign': false 35 | }], 36 | 'no-var': 'error', 37 | 'no-unused-vars': ['warn', { // Not make an error for debugging. 38 | 'vars': 'all', 39 | 'args': 'after-used', 40 | 'argsIgnorePattern': '^_', 41 | 'caughtErrors': 'all', 42 | 'caughtErrorsIgnorePattern': '^_', // Allow `catch (_e) {...}` 43 | }], 44 | 'no-use-before-define': ['error', { // the measure for Temporary Dead Zone 45 | 'functions': false, // Function declarations are hoisted. 46 | 'classes': true, // Class declarations are not hoisted. We should warn it. 47 | }], 48 | 'no-unused-expressions': 'error', 49 | 'no-unused-labels': 'error', 50 | 'no-undef': ['error', { 51 | 'typeof': true, 52 | }], 53 | 54 | // stylisitc problem 55 | 'indent': ['warn', 2, { 56 | 'SwitchCase': 1, 57 | 'MemberExpression': 1, 58 | 'CallExpression': { 59 | 'arguments': 'first', 60 | }, 61 | 'VariableDeclarator': { 62 | 'var': 2, 63 | 'let': 2, 64 | 'const': 3, 65 | } 66 | }], 67 | 'no-underscore-dangle': ['warn', { // Ban the name which starts with `_`. 68 | 'allowAfterThis': true, // allow after this to create a private member. 69 | }], 70 | 'quotes': ['warn', 'single', { 71 | 'avoidEscape': true, 72 | 'allowTemplateLiterals': true, 73 | }], 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /host/templates/LicenseAgreementDlg_HK.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | CostingComplete = 1 12 | "1"]]> 13 | LicenseAccepted = "1" 14 | 15 | 16 | 1 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 1 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tools/eslint/for-module.js: -------------------------------------------------------------------------------- 1 | /* 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | /*eslint-env commonjs*/ 7 | /*eslint quote-props: ['error', "always"] */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = { 12 | 'parserOptions': { 13 | 'sourceType': 'module', 14 | }, 15 | 16 | 'plugins': [ 17 | 'import', 18 | ], 19 | 20 | 'rules': { 21 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/default.md 22 | 'import/default': 'error', 23 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/namespace.md 24 | 'import/namespace': 'error', 25 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-duplicates.md 26 | 'import/no-duplicates': 'error', 27 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/export.md 28 | 'import/export': 'error', 29 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/extensions.md 30 | 'import/extensions': ['error', 'always'], 31 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/first.md 32 | 'import/first': 'error', 33 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/named.md 34 | 'import/named': 'error', 35 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-named-as-default.md 36 | 'import/no-named-as-default': 'error', 37 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-named-as-default-member.md 38 | 'import/no-named-as-default-member': 'error', 39 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-cycle.md 40 | 'import/no-cycle': ['warn', { 41 | // If we comment out this, `maxDepth` is `Infinity`. 42 | //'maxDepth': 1, 43 | }], 44 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-webpack-loader-syntax.md 45 | 'import/no-self-import': 'error', 46 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-unresolved.md 47 | 'import/no-unresolved': ['error', { 48 | 'caseSensitive': true, 49 | }], 50 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-useless-path-segments.md 51 | 'import/no-useless-path-segments': 'error', 52 | }, 53 | }; 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: xpi-prepare xpi chrome host managed install_dependency lint format install_hook 2 | TIMESTAMP=$(shell date +%Y%m%d) 3 | 4 | NPM_MOD_DIR := $(CURDIR)/node_modules 5 | NPM_BIN_DIR := $(NPM_MOD_DIR)/.bin 6 | 7 | xpi-prepare: 8 | git submodule update --init 9 | cp submodules/webextensions-lib-configs/Configs.js extlib/; echo 'export default Configs;' >> extlib/Configs.js 10 | cp submodules/webextensions-lib-options/Options.js extlib/; echo 'export default Options;' >> extlib/Options.js 11 | cp submodules/webextensions-lib-l10n/l10n.js extlib/; echo 'export default l10n;' >> extlib/l10n.js 12 | 13 | xpi: xpi-prepare 14 | rm -f ieview-we.xpi 15 | zip -r -9 ieview-we.xpi manifest.json *.js _locales common options misc/128x128.png extlib -x '*/.*' -x extlib/browser-polyfill.min.js 16 | 17 | chrome: 18 | [ -d node_modules ] || npm install 19 | rm -rf ieview-we-chrome-${TIMESTAMP}.zip 20 | cd chrome && make 21 | cp chrome/ieview-we-chrome.zip ./ieview-we-chrome-${TIMESTAMP}.zip 22 | 23 | # knldjmfmopnpolahpmmgbagdohdnhkik 24 | DUMMY_KEY="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcBHwzDvyBQ6bDppkIs9MP4ksKqCMyXQ/A52JivHZKh4YO/9vJsT3oaYhSpDCE9RPocOEQvwsHsFReW2nUEc6OLLyoCFFxIb7KkLGsmfakkut/fFdNJYh0xOTbSN8YvLWcqph09XAY2Y/f0AL7vfO1cuCqtkMt8hFrBGWxDdf9CQIDAQAB" 25 | 26 | chrome-test: 27 | cat chrome/manifest.json | jq '.key = ${DUMMY_KEY}' > chrome/manifest.json.tmp 28 | mv chrome/manifest.json.tmp chrome/manifest.json 29 | cd chrome && make dev 30 | cp chrome/ieview-we-chrome-dev.zip ./ieview-we-chrome-dev-${TIMESTAMP}.zip 31 | 32 | host: 33 | host/build.sh 34 | rm -f ieview-we-host.zip 35 | cd host && zip -r -9 ../ieview-we-host.zip 386 amd64 *.bat *.json 36 | 37 | managed: xpi-prepare 38 | rm -f ieview-we-managed-storage.zip 39 | cd managed-storage && zip -r -9 ../ieview-we-managed-storage.zip *.bat *.json 40 | 41 | all: host managed xpi chrome 42 | 43 | clean: 44 | rm -rf chrome 45 | rm -f *.zip 46 | rm -f *.xpi 47 | 48 | install_dependency: 49 | [ -e "$(NPM_BIN_DIR)/eslint" -a -e "$(NPM_BIN_DIR)/jsonlint-cli" ] || npm install --save-dev 50 | 51 | lint: install_dependency 52 | "$(NPM_BIN_DIR)/eslint" . --ext=.js --report-unused-disable-directives 53 | find . -type d -name node_modules -prune -o -type f -name '*.json' -print | xargs "$(NPM_BIN_DIR)/jsonlint-cli" 54 | 55 | format: install_dependency 56 | "$(NPM_BIN_DIR)/eslint" . --ext=.js --report-unused-disable-directives --fix 57 | 58 | install_hook: 59 | echo '#!/bin/sh\nmake lint' > "$(CURDIR)/.git/hooks/pre-commit" && chmod +x "$(CURDIR)/.git/hooks/pre-commit" 60 | -------------------------------------------------------------------------------- /options/options.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 17 | 18 |

22 |

26 |

29 |

32 |
33 | 36 |

39 |
42 |
43 | 46 |
47 |
50 |

53 |
54 |

57 |

59 |

61 |

64 | 65 |
66 | 67 | 68 | 69 |
70 | 72 |
73 |
74 | 76 |
77 |
78 | 83 |
84 |
85 | 86 | 87 | -------------------------------------------------------------------------------- /host/templates/WixUI_HK.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 1 29 | "1"]]> 30 | 31 | 1 32 | 33 | NOT Installed 38 | Installed AND PATCH 39 | 40 | 1 41 | LicenseAccepted = "1" 42 | 43 | 1 44 | 1 45 | NOT WIXUI_DONTVALIDATEPATH 46 | "1"]]> 47 | WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1" 48 | 49 | 1 50 | 1 51 | 52 | Installed 53 | 54 | 1 55 | 56 | 1 57 | 1 58 | 1 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /host/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #set -x 4 | 5 | dist_dir="$(cd "$(dirname "$0")" && pwd)" 6 | temp_src="src/temp_ieview_we" 7 | 8 | if go version 2>&1 >/dev/null 9 | then 10 | echo "using $(go version)" 11 | else 12 | echo 'ERROR: golang is missing.' 1>&2 13 | exit 1 14 | fi 15 | 16 | if [ "$GOPATH" = '' ] 17 | then 18 | echo 'ERROR: You must set GOPATH environment variable before you run this script.' 1>&2 19 | exit 1 20 | fi 21 | 22 | if [ -d "$temp_src" ] 23 | then 24 | echo "ERROR: You must remove previous '$temp_src' before building." 1>&2 25 | exit 1 26 | fi 27 | 28 | main() { 29 | build_host 30 | prepare_msi_sources 31 | } 32 | 33 | build_host() { 34 | cd "$GOPATH" 35 | 36 | echo "preparing dependencies..." 37 | prepare_dependency github.com/mitchellh/gox 38 | # prepare_dependency github.com/clear-code/ieview-we 39 | mkdir -p "$temp_src" 40 | ln -s "$dist_dir" "$temp_src/host" 41 | prepare_dependency golang.org/x/sys/windows/registry 42 | prepare_dependency github.com/lhside/chrome-go 43 | prepare_dependency github.com/robertkrimen/otto 44 | prepare_dependency github.com/clear-code/mcd-go 45 | prepare_dependency github.com/lestrrat/go-file-rotatelogs 46 | 47 | addon_version="$(cat "$dist_dir/../manifest.json" | jq -r .version)" 48 | echo "version is ${addon_version}" 49 | sed -i -r -e "s/^(const VERSION = \")[^\"]*(\")/\1${addon_version}\2/" "$temp_src/host/host.go" 50 | 51 | local path="$(echo "$temp_src" | sed 's;^src/;;')/host" 52 | gox -os="windows" "$path" 53 | 54 | local arch 55 | for binary in *.exe 56 | do 57 | arch="$(basename "$binary" '.exe' | sed 's/.\+_windows_//')" 58 | mkdir -p "$dist_dir/$arch" 59 | mv "$binary" "$dist_dir/$arch/host.exe" 60 | done 61 | 62 | rm "$temp_src/host" 63 | rm -rf "$temp_src" 64 | 65 | echo "done." 66 | } 67 | 68 | prepare_dependency() { 69 | local path="$1" 70 | [ -d "src/$path" ] || go get "$path" 71 | } 72 | 73 | prepare_msi_sources() { 74 | cd "$dist_dir" 75 | 76 | product_name="$(cat wix.json | jq -r .product)" 77 | host_name="$(ls *.json | grep -E -v 'wix.json|chrome.json|edge.json' | sed -r -e 's/.json$//')" 78 | vendor_name="$(cat wix.json | jq -r .company)" 79 | addon_version="$(cat ../manifest.json | jq -r .version)" 80 | upgrade_code_guid="$(cat wix.json | jq -r '."upgrade-code"')" 81 | files_guid="$(cat wix.json | jq -r .files.guid)" 82 | env_guid="$(cat wix.json | jq -r .env.guid)" 83 | 84 | cat templates/product.wxs.template | 85 | sed -r -e "s/%PRODUCT%/${product_name}/g" \ 86 | -e "s/%NAME%/${host_name}/g" \ 87 | -e "s/%VENDOR%/${vendor_name}/g" \ 88 | -e "s/%VERSION%/${addon_version}/g" \ 89 | -e "s/%UPGRADE_CODE_GUID%/${upgrade_code_guid}/g" \ 90 | -e "s/%FILES_GUID%/${files_guid}/g" \ 91 | -e "s/%ENV_GUID%/${env_guid}/g" \ 92 | > templates/product.wxs 93 | 94 | build_msi_bat="build_msi.bat" 95 | msi_basename="ieview-we-nmh" 96 | 97 | rm -f "$build_msi_bat" 98 | touch "$build_msi_bat" 99 | echo -e "set MSITEMP=%USERPROFILE%\\\\temp%RANDOM%\r" >> "$build_msi_bat" 100 | echo -e "set SOURCE=%~dp0\r" >> "$build_msi_bat" 101 | echo -e "xcopy \"%SOURCE%\\*\" \"%MSITEMP%\" /S /I \r" >> "$build_msi_bat" 102 | echo -e "cd \"%MSITEMP%\" \r" >> "$build_msi_bat" 103 | echo -e "copy 386\\host.exe \"%cd%\\\" \r" >> "$build_msi_bat" 104 | echo -e "go-msi.exe make --msi ${msi_basename}-386.msi --version ${addon_version} --src templates --out \"%cd%\\outdir\" --arch 386 \r" >> "$build_msi_bat" 105 | echo -e "del host.exe \r" >> "$build_msi_bat" 106 | echo -e "copy amd64\\host.exe \"%cd%\\\" \r" >> "$build_msi_bat" 107 | echo -e "go-msi.exe make --msi ${msi_basename}-amd64.msi --version ${addon_version} --src templates --out \"%cd%\\outdir\" --arch amd64 \r" >> "$build_msi_bat" 108 | echo -e "xcopy *.msi \"%SOURCE%\" /I /Y \r" >> "$build_msi_bat" 109 | echo -e "cd \"%SOURCE%\" \r" >> "$build_msi_bat" 110 | echo -e "rd /S /Q \"%MSITEMP%\" \r" >> "$build_msi_bat" 111 | } 112 | 113 | main 114 | -------------------------------------------------------------------------------- /host/templates/product.wxs.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | NOT NEWERVERSIONDETECTED 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | 45 | 46 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project Status 2 | 3 | This project will no longer be updated and no future updates are planned. Please consider using [BrowserSelector](https://gitlab.com/clear-code/browserselector) as the alternative. 4 | 5 | # IE View WE 6 | 7 | Provides ability to open pages and links by Internet Explorer (Cloned IE View based on WebExtensions-based.) 8 | 9 | This works only on Windows. 10 | 11 | # for Google Chrome (IE View WE MV3) 12 | 13 | This was initially started as a cloned IE View based on WebExtensions API, but finally dropped support of original IE View compatible features including the options page. 14 | Only ThinBridge compatible implementation is left on the browser extension part, thus you need to install [ThinBridge](https://github.com/ThinBridge/ThinBridge/) as the native messaging host. 15 | 16 | 1. Download the [latest installer of ThinBridge](https://github.com/ThinBridge/ThinBridge/releases). 17 | 2. Install ThinBridge with the installer. 18 | 3. Put an [example configuration file](https://raw.githubusercontent.com/ThinBridge/ThinBridge/master/Resources/ThinBridgeBHO.ini) to the location `C:\Program Files\ThinBridge\ThinBridgeBHO.ini`. 19 | 20 | And you need to install this extension via GPO. 21 | 22 | 1. Add a URL entry `"chrome-extension://gahanoflpdcmbcaijjkopjeheaikclcl/"` to the list of `"allowed_origins"` in `C:\Program Files\ThinBridge\ThinBridgeHost\chrome.json `. 23 | 2. Install group policy template for Chrome and configure Chrome to load the addon. 24 | 1. Download [Google Chrome Bundle](https://support.google.com/chrome/a/answer/187202?hl=en#zippy=%2Cwindows) and extract contents of the saved zip file. 25 | 2. Copy `Configuration\admx\*.admx`, `Configuration\admx\en-US` and `Configuration\admx\ja` to `C:\Windows\PolicyDefinitions`. 26 | 3. Launch `gpedit.msc` and open `Local Computer Policy` => `Computer Configuration` => `Administrative Templates` => `Google` => `Google Chrome` => `Extensions` => `Configure the list of force-installed apps and extensions`. 27 | 4. Switch it to `Enabled`. 28 | 5. Click `Show...`. 29 | 6. Add `gahanoflpdcmbcaijjkopjeheaikclcl` to the list. 30 | 3. Set up the client as a domain member if it is not joined to any Active Directory domain. For example, lauch `cmd.exe` as the system administrator and run following commands. 31 | (Ref: https://hitco.at/blog/apply-edge-policies-for-non-domain-joined-devices/ ) 32 | ``` 33 | reg add HKLM\SOFTWARE\Microsoft\Enrollments\FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF /v EnrollmentState /t reg_dword /d 1 /f 34 | reg add HKLM\SOFTWARE\Microsoft\Enrollments\FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF /v EnrollmentType /t reg_dword /d 0 /f 35 | reg add HKLM\SOFTWARE\Microsoft\Enrollments\FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF /v IsFederated /t reg_dword /d 0 /f 36 | reg add HKLM\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts\FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF /v Flags /t reg_dword /d 0xd6fb7f /f 37 | reg add HKLM\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts\FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF /v AcctUId /t reg_sz /d "0x000000000000000000000000000000000000000000000000000000000000000000000000" /f 38 | reg add HKLM\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts\FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF /v RoamingCount /t reg_dword /d 0 /f 39 | reg add HKLM\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts\FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF /v SslClientCertReference /t reg_sz /d "MY;User;0000000000000000000000000000000000000000" /f 40 | reg add HKLM\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts\FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF /v ProtoVer /t reg_sz /d "1.2" /f 41 | ``` 42 | 4. Launch Chrome and confirm the IE VIew WE MV3 is automatically installed. 43 | 44 | # for Firefox (IE View WE MV2) 45 | 46 | *IMPORTANT NOTE: The list of URLs which should be opened by IE is not compatible to the legacy version's one.* 47 | You need to rewrite them based on the [matching pattern spec for Firefox addons](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Match_patterns). 48 | For example: `http://example.com` => `http://example.com/*` (note that the added wild card to match any page under the domain.) 49 | 50 | ## Steps to install 51 | 52 | 1. Download the MSI suitable for your environment's architecture, or a zip package "ieview-we-host.zip" from the [releases page](https://github.com/clear-code/ieview-we/releases/latest). 53 | 2. If you've downloaded an MSI, run it to install. Otherwise, unzip the downloaded file and double-click the batch file named "install.bat". 54 | 3. [Install "IE View WE" Firefox addon from its xpi package.](https://addons.mozilla.org/firefox/addon/ie-view-we/) 55 | 56 | ## Steps to uninstall 57 | 58 | 1. Uninstall "IE View WE" Firefox addon via the addon manager. 59 | 2. Double-click the batch file named `uninstall.bat`. 60 | 61 | ## How to customize Options 62 | 63 | There are some options which you can customize default behavior: 64 | You can also customize preset configuration via MCD. 65 | 66 | * `extensions.ieview.ieapp` Path to Internet Explorer (default: auto detection) 67 | * `extensions.ieview.ieargs` Command Line Arguments (default: empty) 68 | * `extensions.ieview.contextMenu` Add "Open by IE" items to the context menu (default: true) 69 | 70 | ### Rules to open by ... 71 | 72 | * `extensions.ieview.forceielist` Websites to be opened by IE always (default: empty) 73 | * `extensions.ieview.disableForce` Disable websites opened by IE always (dfault: false) 74 | * `extensions.ieview.sitesOpenedBySelf` Exceptive websites (opened in Firefox directly) (default: empty) 75 | * `extensions.ieview.disableException` Disable websites directly opened by Firefox (default: false) 76 | * `extensions.ieview.onlyMainFrame` Only check URL which is shown in location bar (default: true) 77 | * `extensions.ieview.ignoreQueryString` Ignore query string in URL (default: false) 78 | 79 | ### Logging & Debugging 80 | 81 | * `extensions.ieview.debug` Print Debug log (default: false) 82 | * `extensions.ieview.logging` Save log (default: true) 83 | * `extensions.ieview.logRotationTime` Rotate log file by specified hour (default: 24) 84 | * `extensions.ieview.logRotationCount` Max count of log files (default: 12) 85 | 86 | ### How to build the native messaging host and its installer 87 | 88 | On Windows 10 + WSL: 89 | 90 | 1. [Install and setup Golang](https://golang.org/doc/install) on your Linux environment. 91 | 2. Install go-msi https://github.com/mh-cbon/go-msi *via an MSI to your Windows environment*. 92 | 3. Install WiX Toolset https://wixtoolset.org/releases/ to your Windows environment. 93 | 4. Set PATH to go-msi (ex. `C:\Program Files\go-msi`) and WiX Toolse (ex. `C:\Program Files (x86)\WiX Toolset v3.11\bin`). 94 | 5. Run `make host`. 95 | Then `.exe` files and a batch file to build MSI will be generated. 96 | 6. Double-click the generated `host\build_msi.bat` on your Windows environment. 97 | Then two MSIs will be generated. 98 | 99 | # License 100 | 101 | MPL 2.0 102 | -------------------------------------------------------------------------------- /host/host.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "github.com/clear-code/mcd-go" 8 | rotatelogs "github.com/lestrrat/go-file-rotatelogs" 9 | "github.com/lhside/chrome-go" 10 | "golang.org/x/sys/windows/registry" 11 | "io/ioutil" 12 | "log" 13 | "os" 14 | "os/exec" 15 | "path/filepath" 16 | "strings" 17 | "syscall" 18 | "time" 19 | ) 20 | 21 | const VERSION = "1.6.6"; 22 | 23 | const CREATE_BREAKAWAY_FROM_JOB = 0x01000000 24 | 25 | type RequestParams struct { 26 | // launch 27 | Path string `json:path` 28 | Args []string `json:args` 29 | Url string `json:url` 30 | } 31 | type Request struct { 32 | Command string `json:"command"` 33 | Params RequestParams `json:"params"` 34 | Logging bool `json:"logging"` 35 | LogRotationCount int `json:"logRotationCount"` 36 | LogRotationTime int `json:"logRotationTime"` 37 | Debug bool `json:"debug"` 38 | } 39 | 40 | var DebugLogs []string 41 | var Logging bool 42 | var Debug bool 43 | 44 | func main() { 45 | shouldReportVersion := flag.Bool("v", false, "v") 46 | flag.Parse() 47 | if *shouldReportVersion == true { 48 | fmt.Println(VERSION) 49 | return 50 | } 51 | 52 | log.SetOutput(ioutil.Discard) 53 | 54 | rawRequest, err := chrome.Receive(os.Stdin) 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | request := &Request{} 59 | if err := json.Unmarshal(rawRequest, request); err != nil { 60 | log.Fatal(err) 61 | } 62 | 63 | Logging = request.Logging 64 | Debug = request.Debug 65 | if Logging { 66 | logfileDir := os.ExpandEnv(`${temp}`) 67 | // 68 | logRotationTime := time.Duration(request.LogRotationTime) * time.Hour 69 | logRotationCount := request.LogRotationCount 70 | maxAge := time.Duration(-1) 71 | // for debugging 72 | //logRotationTime = time.Duration(request.LogRotationTime) * time.Minute 73 | rotateLog, err := rotatelogs.New(filepath.Join(logfileDir, "com.clear_code.ieview_we.log.%Y%m%d%H%M.txt"), 74 | rotatelogs.WithMaxAge(maxAge), 75 | rotatelogs.WithRotationTime(logRotationTime), 76 | rotatelogs.WithRotationCount(logRotationCount), 77 | ) 78 | if err != nil { 79 | log.Fatal(err) 80 | } 81 | defer rotateLog.Close() 82 | 83 | log.SetOutput(rotateLog) 84 | log.SetFlags(log.Ldate | log.Ltime) 85 | LogForDebug("logRotationCount:" + fmt.Sprint(logRotationCount)) 86 | LogForDebug("logRotationTime:" + fmt.Sprint(logRotationTime)) 87 | } 88 | 89 | LogForDebug("Command is " + request.Command) 90 | switch command := request.Command; command { 91 | case "launch": 92 | Launch(request.Params.Path, request.Params.Args, request.Params.Url) 93 | case "get-ie-path": 94 | SendIEPath() 95 | case "read-mcd-configs": 96 | SendMCDConfigs() 97 | default: // just echo 98 | err = chrome.Post(rawRequest, os.Stdout) 99 | if err != nil { 100 | log.Fatal(err) 101 | } 102 | } 103 | } 104 | 105 | func LogForInfo(message string) { 106 | DebugLogs = append(DebugLogs, message) 107 | if Logging { 108 | fmt.Fprintf(os.Stderr, "[info] "+message+"\n") 109 | log.Print(message + "\r\n") 110 | } 111 | } 112 | 113 | func LogForDebug(message string) { 114 | DebugLogs = append(DebugLogs, message) 115 | if Logging && Debug { 116 | fmt.Fprintf(os.Stderr, "[debug] "+message+"\n") 117 | log.Print(message + "\r\n") 118 | } 119 | } 120 | 121 | type LaunchResponse struct { 122 | Success bool `json:"success"` 123 | Path string `json:"path"` 124 | Args []string `json:"args"` 125 | Logs []string `json:"logs"` 126 | } 127 | 128 | func Launch(path string, defaultArgs []string, url string) { 129 | args := []string{} 130 | replacedPlaceholder := false 131 | for _, arg := range defaultArgs { 132 | if strings.Contains(arg, "%s") { 133 | args = append(args, strings.Replace(arg, "%s", url, 1)) 134 | replacedPlaceholder = true 135 | } else if strings.Contains(arg, "%**") { 136 | args = append(args, strings.Replace(arg, "%**", url, 1)) 137 | replacedPlaceholder = true 138 | } else { 139 | args = append(args, arg) 140 | } 141 | } 142 | if !replacedPlaceholder { 143 | args = append(args, url) 144 | } 145 | LogForInfo("Args: \n " + strings.Join(args, "\n ")) 146 | command := exec.Command(path, args...) 147 | 148 | if !strings.HasPrefix(os.Args[1], "chrome-extension://") { // non-Chromium caller => Firefox 149 | // See also: 150 | // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Native_messaging#Closing_the_native_app 151 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx 152 | command.SysProcAttr = &syscall.SysProcAttr{CreationFlags: CREATE_BREAKAWAY_FROM_JOB} 153 | } else { 154 | command.SysProcAttr = &syscall.SysProcAttr{CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP} 155 | } 156 | 157 | response := &LaunchResponse{true, path, args, DebugLogs} 158 | 159 | err := command.Start() 160 | if err != nil { 161 | LogForDebug("Failed to launch " + path) 162 | log.Fatal(err) 163 | response.Success = false 164 | } 165 | // Wait until the launcher completely finishes. 166 | time.Sleep(3 * time.Second) 167 | 168 | response.Logs = DebugLogs 169 | body, err := json.Marshal(response) 170 | if err != nil { 171 | log.Fatal(err) 172 | } 173 | err = chrome.Post(body, os.Stdout) 174 | if err != nil { 175 | log.Fatal(err) 176 | } 177 | LogForInfo("Opened by external application: " + url) 178 | } 179 | 180 | type SendIEPathResponse struct { 181 | Path string `json:"path"` 182 | Logs []string `json:"logs"` 183 | } 184 | 185 | func SendIEPath() { 186 | path := GetIEPath() 187 | response := &SendIEPathResponse{path, DebugLogs} 188 | body, err := json.Marshal(response) 189 | if err != nil { 190 | log.Fatal(err) 191 | } 192 | err = chrome.Post(body, os.Stdout) 193 | if err != nil { 194 | log.Fatal(err) 195 | } 196 | } 197 | 198 | func GetIEPath() (path string) { 199 | keyPath := `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\iexplore.exe` 200 | key, err := registry.OpenKey(registry.LOCAL_MACHINE, 201 | keyPath, 202 | registry.QUERY_VALUE) 203 | if err != nil { 204 | LogForDebug("Failed to open key " + keyPath) 205 | log.Fatal(err) 206 | } 207 | defer key.Close() 208 | 209 | path, _, err = key.GetStringValue("") 210 | if err != nil { 211 | LogForDebug("Failed to get value from key " + keyPath) 212 | log.Fatal(err) 213 | } 214 | return 215 | } 216 | 217 | type SendMCDConfigsResponse struct { 218 | IEApp string `json:"ieapp"` 219 | IEArgs string `json:"ieargs"` 220 | ForceIEList string `json:"forceielist"` 221 | DisableForce bool `json:"disableForce"` 222 | CloseReloadPage bool `json:"closeReloadPage"` 223 | ContextMenu bool `json:"contextMenu"` 224 | OnlyMainFrame bool `json:"onlyMainFrame"` 225 | SitesOpenedBySelf string `json:"sitesOpenedBySelf"` 226 | DisableException bool `json:"disableException"` 227 | IgnoreQueryString bool `json:"ignoreQueryString"` 228 | Logging bool `json:"logging"` 229 | LogRotationCount int64 `json:"logRotationCount"` 230 | LogRotationTime int64 `json:"logRotationTime"` 231 | Debug bool `json:"debug"` 232 | LoadedKeys []string `json:"loadedKeys"` 233 | Logs []string `json:"logs"` 234 | } 235 | 236 | func SendMCDConfigs() { 237 | configs, err := mcd.New() 238 | if len(mcd.DebugLogs) > 0 { 239 | LogForDebug("Logs from mcd:\n " + strings.Join(mcd.DebugLogs, "\n ")) 240 | } 241 | if err != nil { 242 | LogForDebug("Failed to read MCD configs.\n" + err.Error()) 243 | //log.Fatal(err) 244 | } 245 | 246 | response := &SendMCDConfigsResponse{} 247 | 248 | var LoadedKeys []string 249 | ieApp, err := configs.GetStringValue("extensions.ieview.ieapp") 250 | if err == nil { 251 | response.IEApp = ieApp 252 | LoadedKeys = append(LoadedKeys, "ieApp") 253 | } 254 | ieArgs, err := configs.GetStringValue("extensions.ieview.ieargs") 255 | if err == nil { 256 | response.IEArgs = ieArgs 257 | LoadedKeys = append(LoadedKeys, "ieArgs") 258 | } 259 | forceIEList, err := configs.GetStringValue("extensions.ieview.forceielist") 260 | if err == nil { 261 | response.ForceIEList = forceIEList 262 | LoadedKeys = append(LoadedKeys, "forceIEList") 263 | } 264 | disableForce, err := configs.GetBooleanValue("extensions.ieview.disableForce") 265 | if err == nil { 266 | response.DisableForce = disableForce 267 | LoadedKeys = append(LoadedKeys, "disableForce") 268 | } else { 269 | LogForDebug("Failed to read extensions.ieview.disableForce.\n" + err.Error()) 270 | } 271 | closeReloadPage, err := configs.GetBooleanValue("extensions.ieview.closeReloadPage") 272 | if err == nil { 273 | response.CloseReloadPage = closeReloadPage 274 | LoadedKeys = append(LoadedKeys, "closeReloadPage") 275 | } else { 276 | LogForDebug("Failed to read extensions.ieview.closeReloadPage.\n" + err.Error()) 277 | } 278 | sitesOpenedBySelf, err := configs.GetStringValue("extensions.ieview.sitesOpenedBySelf") 279 | if err == nil { 280 | response.SitesOpenedBySelf = sitesOpenedBySelf 281 | LoadedKeys = append(LoadedKeys, "sitesOpenedBySelf") 282 | } 283 | disableException, err := configs.GetBooleanValue("extensions.ieview.disableException") 284 | if err == nil { 285 | response.DisableException = disableException 286 | LoadedKeys = append(LoadedKeys, "disableException") 287 | } else { 288 | LogForDebug("Failed to read extensions.ieview.disableException.\n" + err.Error()) 289 | } 290 | contextMenu, err := configs.GetBooleanValue("extensions.ieview.contextMenu") 291 | if err == nil { 292 | response.ContextMenu = contextMenu 293 | LoadedKeys = append(LoadedKeys, "contextMenu") 294 | } else { 295 | LogForDebug("Failed to read extensions.ieview.contextMenu.\n" + err.Error()) 296 | } 297 | onlyMainFrame, err := configs.GetBooleanValue("extensions.ieview.onlyMainFrame") 298 | if err == nil { 299 | response.OnlyMainFrame = onlyMainFrame 300 | LoadedKeys = append(LoadedKeys, "onlyMainFrame") 301 | } else { 302 | LogForDebug("Failed to read extensions.ieview.onlyMainFrame.\n" + err.Error()) 303 | } 304 | ignoreQueryString, err := configs.GetBooleanValue("extensions.ieview.ignoreQueryString") 305 | if err == nil { 306 | response.IgnoreQueryString = ignoreQueryString 307 | LoadedKeys = append(LoadedKeys, "ignoreQueryString") 308 | } else { 309 | LogForDebug("Failed to read extensions.ieview.ignoreQueryString.\n" + err.Error()) 310 | } 311 | logging, err := configs.GetBooleanValue("extensions.ieview.logging") 312 | if err == nil { 313 | response.Logging = logging 314 | LoadedKeys = append(LoadedKeys, "logging") 315 | } 316 | logRotationCount, err := configs.GetIntegerValue("extensions.ieview.logRotationCount") 317 | if err == nil { 318 | response.LogRotationCount = logRotationCount 319 | LoadedKeys = append(LoadedKeys, "logRotationCount") 320 | } 321 | logRotationTime, err := configs.GetIntegerValue("extensions.ieview.logRotationTime") 322 | if err == nil { 323 | response.LogRotationTime = logRotationTime 324 | LoadedKeys = append(LoadedKeys, "logRotationTime") 325 | } 326 | debug, err := configs.GetBooleanValue("extensions.ieview.debug") 327 | if err == nil { 328 | response.Debug = debug 329 | LoadedKeys = append(LoadedKeys, "debug") 330 | } else { 331 | LogForDebug("Failed to read extensions.ieview.debug.\n" + err.Error()) 332 | } 333 | 334 | if len(configs.DebugLogs) > 0 { 335 | LogForDebug("Logs from mcd configs:\n " + strings.Join(configs.DebugLogs, "\n ")) 336 | } 337 | response.LoadedKeys = LoadedKeys 338 | response.Logs = DebugLogs 339 | body, err := json.Marshal(response) 340 | if err != nil { 341 | log.Fatal(err) 342 | } 343 | err = chrome.Post(body, os.Stdout) 344 | if err != nil { 345 | log.Fatal(err) 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /host/LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /chrome/background.js: -------------------------------------------------------------------------------- 1 | /* 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 'use strict'; 7 | 8 | /* 9 | * Basic settings for modern browsers 10 | * 11 | * Programming Note: Just tweak these constants for each browser. 12 | * It should work fine across Edge, Chrome and Firefox without any 13 | * further modifications. 14 | */ 15 | const BROWSER = 'chrome'; 16 | const DMZ_SECTION = 'custom18'; 17 | const CONTINUOUS_SECTION = 'custom19'; 18 | const SERVER_NAME = 'com.clear_code.thinbridge'; 19 | const ALARM_MINUTES = 0.5; 20 | /* 21 | * When `{cancel: 1}` is used to block loading, Edge shows a warning page which 22 | * indicates that loading is canceled by an add-on. To avoid it, move back to 23 | * the previous page instead of blocking. 24 | */ 25 | const CANCEL_REQUEST = {redirectUrl:`data:text/html,${escape('')}`}; 26 | /* 27 | * Although even if we return `CANCEL_REQUEST` from `onBeforeRequest()` on a 28 | * sub-frame, `history.back()` will be performed against it's parent main 29 | * frame when there is no page to back in the sub-frame. As a result main 30 | * frame moves back to the previous page unexpectedly. 31 | * To avoid it, just move to blank page instead. 32 | */ 33 | const CANCEL_REQUEST_FOR_SUBFRAME = {redirectUrl:'about:blank'}; 34 | const REDIRECT_INTERVAL_LIMIT = 1000; 35 | 36 | /* 37 | * ThinBridge's matching function (See BHORedirector/URLRedirectCore.h) 38 | * 39 | * 1. `?` represents a single character. 40 | * 2. `*` represents an arbitrary substring. 41 | * 42 | * >>> wildcmp("http?://*.example.com/*", "https://www.example.com/") 43 | * true 44 | */ 45 | function wildcmp(wild, string) { 46 | let i = 0; 47 | let j = 0; 48 | let mp, cp; 49 | 50 | while ((j < string.length) && (wild[i] != '*')) { 51 | if ((wild[i] != string[j]) && (wild[i] != '?')) { 52 | return 0; 53 | } 54 | i += 1; 55 | j += 1; 56 | } 57 | while (j < string.length) { 58 | if (wild[i] == '*') { 59 | i += 1; 60 | 61 | if (i == wild.length) { 62 | return 1; 63 | } 64 | mp = i; 65 | cp = j + 1 66 | } else if ((wild[i] == string[j]) || (wild[i] == '?')) { 67 | i += 1; 68 | j += 1; 69 | } else { 70 | i = mp; 71 | j = cp; 72 | cp += 1; 73 | } 74 | } 75 | while (wild[i] == '*' && i < wild.length) { 76 | i += 1; 77 | } 78 | return i >= wild.length; 79 | }; 80 | 81 | /* 82 | * Observe WebRequests with config fetched from ThinBridge. 83 | * 84 | * A typical configuration looks like this: 85 | * 86 | * { 87 | * CloseEmptyTab:1, OnlyMainFrame:1, IgnoreQueryString:1, DefaultBrowser:"IE", 88 | * Sections: [ 89 | * {Name:"ie", Path:"", Patterns:["*://example.com/*"], Excludes:[]}, 90 | * ... 91 | * ] 92 | * } 93 | */ 94 | const ThinBridgeTalkClient = { 95 | newTabIds: new Set(), 96 | knownTabIds: new Set(), 97 | resumed: false, 98 | 99 | init() { 100 | this.cached = null; 101 | this.ensureLoadedAndConfigured(); 102 | this.recentRequests = {}; 103 | console.log('Running as Thinbridge Talk client'); 104 | }, 105 | 106 | async ensureLoadedAndConfigured() { 107 | return this._promisedLoadedAndConfigured = this._promisedLoadedAndConfigured || Promise.all([ 108 | !this.cached && this.configure(), 109 | this.load(), 110 | ]); 111 | }, 112 | _promisedLoadedAndConfigured: null, 113 | 114 | async configure() { 115 | const query = new String('C ' + BROWSER); 116 | 117 | const resp = await chrome.runtime.sendNativeMessage(SERVER_NAME, query); 118 | if (chrome.runtime.lastError) { 119 | console.log('Cannot fetch config', JSON.stringify(chrome.runtime.lastError)); 120 | return; 121 | } 122 | const isStartup = (this.cached == null); 123 | this.cached = resp.config; 124 | this.cached.NamedSections = Object.fromEntries(resp.config.Sections.map(section => [section.Name.toLowerCase(), section])); 125 | console.log('Fetch config', JSON.stringify(this.cached)); 126 | 127 | if (isStartup && !this.resumed) { 128 | this.handleStartup(this.cached); 129 | } 130 | }, 131 | 132 | save() { 133 | chrome.storage.session.set({ 134 | newTabIds: [...this.newTabIds], 135 | knownTabIds: [...this.knownTabIds], 136 | }); 137 | }, 138 | 139 | async load() { 140 | if (this.$promisedLoaded) 141 | return this.$promisedLoaded; 142 | 143 | console.log(`Redirector: loading previous state`); 144 | return this.$promisedLoaded = new Promise(async (resolve, _reject) => { 145 | try { 146 | const { newTabIds, knownTabIds } = await chrome.storage.session.get({ newTabIds: null, knownTabIds: null }); 147 | console.log(`ThinBridgeTalkClient: loaded newTabIds, knownTabIds => `, JSON.stringify(newTabIds), JSON.stringify(knownTabIds)); 148 | this.resumed = !!(newTabIds || knownTabIds); 149 | if (newTabIds) { 150 | for (const tabId of newTabIds) { 151 | this.newTabIds.add(tabId); 152 | } 153 | } 154 | if (knownTabIds) { 155 | for (const tabId of knownTabIds) { 156 | this.knownTabIds.add(tabId); 157 | } 158 | } 159 | } 160 | catch(error) { 161 | console.log('ThinBridgeTalkClient: failed to load previous state: ', error.name, error.message); 162 | } 163 | resolve(); 164 | }); 165 | }, 166 | 167 | /* 168 | * Request redirection to Native Messaging Hosts. 169 | * 170 | * * chrome.tabs.get() is to confirm that the URL is originated from 171 | * an actual tab (= not an internal prefetch request). 172 | * 173 | * * Request Example: "Q edge https://example.com/". 174 | */ 175 | redirect(url, tabId, closeTab) { 176 | chrome.tabs.get(tabId).then(async tab => { 177 | if (chrome.runtime.lastError) { 178 | console.log(`* Ignore prefetch request`); 179 | return; 180 | } 181 | if (!tab) { 182 | console.log(`* URL is not coming from an actual tab`); 183 | return; 184 | } 185 | 186 | const query = new String('Q ' + BROWSER + ' ' + url); 187 | await chrome.runtime.sendNativeMessage(SERVER_NAME, query); 188 | 189 | if (!closeTab) 190 | return; 191 | 192 | let existingTab = tab; 193 | let counter = 0; 194 | do { 195 | if (!existingTab) 196 | break; 197 | if (counter > 100) { 198 | console.log(`couldn't close tab ${tabId} within ${counter} times retry.`); 199 | break; 200 | } 201 | if (counter++ > 0) 202 | console.log(`tab ${tabId} still exists: trying to close (${counter})`); 203 | await chrome.tabs.remove(tabId); 204 | } while (existingTab = await chrome.tabs.get(tabId).catch(_error => null)); 205 | }); 206 | }, 207 | 208 | match(section, url, namedSections) { 209 | for (const name of (section.ExcludeGroups || [])) { 210 | const foreignSection = namedSections[name.toLowerCase()]; 211 | //console.log(`* Referring exclude group ${name}: ${JSON.stringify(foreignSection && (foreignSection.URLPatterns || foreignSection.Patterns))}`); 212 | if (!foreignSection) 213 | continue; 214 | for (let pattern of (foreignSection.URLPatterns || foreignSection.Patterns || [])) { 215 | if (Array.isArray(pattern)) { 216 | pattern = pattern[0]; 217 | } 218 | if (wildcmp(pattern, url)) { 219 | console.log(`* Match Exclude ${section.Name} (referring ${name}) [${pattern}]`); 220 | return false; 221 | } 222 | } 223 | } 224 | 225 | for (let pattern of (section.URLExcludePatterns || section.Excludes || [])) { 226 | if (Array.isArray(pattern)) { 227 | pattern = pattern[0]; 228 | } 229 | if (wildcmp(pattern, url)) { 230 | console.log(`* Match Exclude ${section.Name} [${pattern}]`); 231 | return false; 232 | } 233 | } 234 | 235 | for (let pattern of (section.URLPatterns || section.Patterns || [])) { 236 | if (Array.isArray(pattern)) { 237 | pattern = pattern[0]; 238 | } 239 | if (wildcmp(pattern, url)) { 240 | console.log(`* Match ${section.Name} [${pattern}]`); 241 | return true; 242 | } 243 | } 244 | return false; 245 | }, 246 | 247 | getBrowserName(section) { 248 | const name = section.Name.toLowerCase(); 249 | 250 | if (name == DMZ_SECTION) 251 | return name; 252 | 253 | /* Guess the browser name from the executable path */ 254 | if (name.match(/^custom/i)) { 255 | if (section.Path.match(RegExp(BROWSER, 'i'))) 256 | return BROWSER; 257 | } 258 | return name; 259 | }, 260 | 261 | checkRedirectIntervalLimit(tabId, url) { 262 | const now = Date.now(); 263 | let skip = false; 264 | if (!this.recentRequests) { 265 | // in unit test 266 | return false; 267 | } 268 | for (const key in this.recentRequests) { 269 | if (Math.abs(now - this.recentRequests[key].time) > REDIRECT_INTERVAL_LIMIT) 270 | delete this.recentRequests[key]; 271 | } 272 | const recent = this.recentRequests[tabId]; 273 | if (recent && recent.url === url) { 274 | skip = true; 275 | } 276 | this.recentRequests[tabId] = { tabId: tabId, url: url, time: now } 277 | return skip; 278 | }, 279 | 280 | handleURLAndBlock(config, tabId, url, isClosableTab) { 281 | if (!url) { 282 | console.log(`* Empty URL found`); 283 | return false; 284 | } 285 | 286 | if (!/^https?:/.test(url)) { 287 | console.log(`* Ignore non-HTTP/HTTPS URL ${url}`); 288 | return false; 289 | } 290 | 291 | // Just store recent request, don't block here. 292 | // It should be determined by caller. 293 | // (onBeforeRequest() should always block loading redirect URL.) 294 | this.checkRedirectIntervalLimit(tabId, url); 295 | 296 | const urlToMatch = config.IgnoreQueryString ? url.replace(/\?.*/, '') : url; 297 | 298 | console.log(`* Lookup sections for ${urlToMatch}`); 299 | 300 | const closeTabOnRedirect = config.CloseEmptyTab && isClosableTab; 301 | 302 | let loadCount = 0; 303 | let redirectCount = 0; 304 | let isActionMode = false; 305 | const matchedSectionNames = []; 306 | sectionsLoop: 307 | for (const section of config.Sections) { 308 | console.log(`handleURLAndBlock: check for section ${section.Name} (${JSON.stringify(section)})`); 309 | 310 | if (section.Action) 311 | isActionMode = true; 312 | 313 | if (!this.match(section, urlToMatch, config.NamedSections)) { 314 | console.log(` => unmatched`); 315 | continue; 316 | } 317 | 318 | const sectionName = (section.Name || '').toLowerCase(); 319 | matchedSectionNames.push(sectionName); 320 | 321 | console.log(` => matched, action = ${section.Action}`); 322 | if (section.Action) { 323 | // a.k.a "full mode" in IE View WE 324 | switch(section.Action.toLowerCase()) { 325 | case 'redirect': 326 | redirectCount++; 327 | break; 328 | 329 | case 'load': 330 | default: 331 | loadCount++; 332 | break; 333 | } 334 | if (sectionName == DMZ_SECTION || sectionName == CONTINUOUS_SECTION) 335 | break sectionsLoop; 336 | } 337 | else { 338 | // Compatible mode with ManifestV2 version of this add-on 339 | switch (this.getBrowserName(section)) { 340 | case DMZ_SECTION: 341 | console.log(` => action not defined, default action for CUSTOM18: load`); 342 | loadCount++; 343 | break sectionsLoop; 344 | 345 | case BROWSER.toLowerCase(): 346 | console.log(` => action not defined, default action for ${BROWSER}: load`); 347 | loadCount++; 348 | break; 349 | 350 | default: 351 | console.log(` => action not defined, default action: redirect`); 352 | redirectCount++; 353 | if (sectionName == CONTINUOUS_SECTION) 354 | break sectionsLoop; 355 | break; 356 | } 357 | } 358 | } 359 | console.log(`* Result: [${matchedSectionNames.join(', ')}]`); 360 | 361 | if (isActionMode) { 362 | // a.k.a "full mode" in IE View WE 363 | console.log(`* Dispatch as action mode`); 364 | if (redirectCount > 0 || loadCount == 0) { 365 | console.log(`* Redirect to another browser`); 366 | this.redirect(url, tabId, closeTabOnRedirect); 367 | } 368 | console.log(`* Continue to load: ${loadCount > 0}`); 369 | return loadCount == 0; 370 | } 371 | else { 372 | // Compatible mode with ManifestV2 version of this add-on 373 | console.log(`* Dispatch as compatible mode`); 374 | 375 | if (loadCount > 0) { 376 | console.log(`* Continue to load`); 377 | return false; 378 | } 379 | 380 | if (redirectCount > 0) { 381 | console.log(`* Redirect to another browser`); 382 | this.redirect(url, tabId, closeTabOnRedirect); 383 | return true; 384 | } 385 | 386 | if (config.DefaultBrowser) { 387 | console.log(`* Use DefaultBrowser: ${config.DefaultBrowser}`); 388 | if (String(config.DefaultBrowser).toLowerCase() == BROWSER.toLowerCase()) { 389 | return false; 390 | } else { 391 | this.redirect(url, tabId, closeTabOnRedirect); 392 | return true; 393 | } 394 | } else { 395 | console.log(`* DefaultBrowser is blank`); 396 | return false; 397 | } 398 | } 399 | }, 400 | 401 | /* Handle startup tabs preceding to onBeforeRequest */ 402 | handleStartup(config) { 403 | chrome.tabs.query({}).then(tabs => { 404 | tabs.forEach((tab) => { 405 | const url = tab.url || tab.pendingUrl; 406 | console.log(`handleStartup ${url} (tab=${tab.id})`); 407 | if (!this.handleURLAndBlock(config, tab.id, url, true)) 408 | this.knownTabIds.add(tab.id); 409 | }); 410 | }); 411 | }, 412 | 413 | async onTabCreated(tab) { 414 | await this.ensureLoadedAndConfigured(); 415 | this.newTabIds.add(tab.id); 416 | this.save(); 417 | }, 418 | 419 | async onTabRemoved(tabId, _removeInfo) { 420 | await this.ensureLoadedAndConfigured(); 421 | this.newTabIds.delete(tabId); 422 | this.knownTabIds.delete(tabId); 423 | this.save(); 424 | }, 425 | 426 | async onTabUpdated(tabId, info, tab) { 427 | await this.ensureLoadedAndConfigured(); 428 | 429 | // We should close new (empty) tab, but not for already handled tab by 430 | // handleStartup or onBeforeReqeust that are possible to be called before 431 | // onTabCreated. The later condition is the guard for it. 432 | const isClosableTab = this.newTabIds.has(tabId) && !this.knownTabIds.has(tabId) 433 | this.knownTabIds.add(tabId); 434 | this.newTabIds.delete(tabId); 435 | 436 | const config = this.cached; 437 | const url = tab.pendingUrl || tab.url; 438 | 439 | if (!config) { 440 | this.save(); 441 | return; 442 | } 443 | 444 | console.log(`onTabUpdated ${url} (tab=${tabId}, windowId=${tab.windowId}, status=${info.status}/${tab.status})`); 445 | 446 | if (info.status !== 'loading' && 447 | info.status !== undefined /* IE Mode tab on Edge will have undefined status */) 448 | return; 449 | 450 | if (this.checkRedirectIntervalLimit(tabId, url)) { 451 | console.log(`A request for same URL and same tabId already occurred in ${REDIRECT_INTERVAL_LIMIT} msec. Skip it.`); 452 | return false; 453 | } 454 | 455 | // If onBeforeRequest() fails to redirect due to missing config, the next chance to do it is here. 456 | if (!this.handleURLAndBlock(config, tabId, url, isClosableTab)) 457 | return; 458 | 459 | if (isClosableTab) { 460 | // The tab is considered to be closed by handleURLAndBlock(). 461 | return; 462 | } 463 | 464 | /* Call executeScript() to stop the page loading immediately. 465 | * Then let the tab go back to the previous page. 466 | */ 467 | chrome.scripting.executeScript({ 468 | target: { tabId }, 469 | func: function goBack() { 470 | window.stop(); 471 | window.history.back(); 472 | }, 473 | }); 474 | }, 475 | 476 | /* Callback for webRequest.onBeforeRequest */ 477 | onBeforeRequest(details) { 478 | const config = this.cached; 479 | const isMainFrame = (details.type == 'main_frame'); 480 | 481 | console.log(`onBeforeRequest ${details.url} (tab=${details.tabId})`); 482 | 483 | if (!config) { 484 | console.log('* Config cache is empty. Fetching...'); 485 | this.configure(); 486 | return; 487 | } 488 | 489 | if (details.tabId < 0 || 490 | details.documentLifecycle == 'prerender') { 491 | console.log(`* Ignore internal request`); 492 | return; 493 | } 494 | 495 | if (config.OnlyMainFrame && !isMainFrame) { 496 | console.log(`* Ignore subframe request`); 497 | return; 498 | } 499 | 500 | const isClosableTab = isMainFrame && (this.newTabIds.has(details.tabId) || !this.knownTabIds.has(details.tabId)); 501 | 502 | if (this.handleURLAndBlock(config, details.tabId, details.url, isClosableTab)) { 503 | if (isMainFrame) 504 | return CANCEL_REQUEST; 505 | else 506 | return CANCEL_REQUEST_FOR_SUBFRAME; 507 | } 508 | 509 | this.knownTabIds.add(details.tabId); 510 | }, 511 | }; 512 | 513 | chrome.webRequest.onBeforeRequest.addListener( 514 | ThinBridgeTalkClient.onBeforeRequest.bind(ThinBridgeTalkClient), 515 | { 516 | urls: [''], 517 | types: ['main_frame','sub_frame'] 518 | }, 519 | ['blocking'] 520 | ); 521 | 522 | /* Refresh config for every N minute */ 523 | console.log('Poll config for every', ALARM_MINUTES , 'minutes'); 524 | chrome.alarms.create('poll-config', {'periodInMinutes': ALARM_MINUTES}); 525 | 526 | chrome.alarms.onAlarm.addListener((alarm) => { 527 | if (alarm.name === 'poll-config') { 528 | ThinBridgeTalkClient.configure(); 529 | } 530 | }); 531 | 532 | /* Tab book-keeping for intelligent tab handlings */ 533 | chrome.tabs.onCreated.addListener(ThinBridgeTalkClient.onTabCreated.bind(ThinBridgeTalkClient)); 534 | chrome.tabs.onUpdated.addListener(ThinBridgeTalkClient.onTabUpdated.bind(ThinBridgeTalkClient)); 535 | 536 | 537 | /* 538 | * Support ThinBridge's resource cap feature 539 | */ 540 | const ResourceCap = { 541 | 542 | init() { 543 | console.log('Running Resource Cap client'); 544 | }, 545 | 546 | /* 547 | * On each navigation, we ask the host program to check the 548 | * current resource usage. 549 | */ 550 | onNavigationCommitted(details) { 551 | console.log(`onNavigationCommitted: ${details.url}`); 552 | 553 | /* frameId != 0 indicates iframe requests */ 554 | if (details.frameId) { 555 | console.log(`* Ignore subframe requests`); 556 | return; 557 | } 558 | 559 | chrome.tabs.query({}).then(tabs => { 560 | const ntabs = this.count(tabs); 561 | console.log(`* Perform resource check (ntabs=${ntabs})`); 562 | this.check(details.tabId, ntabs); 563 | }); 564 | }, 565 | 566 | check(tabId, ntabs) { 567 | const query = new String(`R ${BROWSER} ${ntabs}`); 568 | chrome.runtime.sendNativeMessage(SERVER_NAME, query).then(resp => { 569 | // Need this to support ThinBridge v4.0.2.3 (or before) 570 | if (chrome.runtime.lastError) { 571 | return; 572 | } 573 | 574 | if (resp.closeTab) { 575 | chrome.tabs.remove(tabId).then(() => { 576 | if (chrome.runtime.lastError) { 577 | console.log(`* ${chrome.runtime.lastError}`); 578 | return; 579 | } 580 | console.log(`* Close Tab#${tabId}`) 581 | }); 582 | } 583 | }); 584 | }, 585 | 586 | count(tabs) { 587 | /* Exclude the internal pages such as "edge://blank" */ 588 | tabs = tabs.filter((tab) => { 589 | const url = tab.url || tab.pendingUrl; 590 | return /^https?:/.test(url); 591 | }); 592 | return tabs.length; 593 | } 594 | }; 595 | 596 | chrome.webNavigation.onCommitted.addListener(ResourceCap.onNavigationCommitted.bind(ResourceCap)); 597 | 598 | ThinBridgeTalkClient.init(); 599 | ResourceCap.init(); 600 | -------------------------------------------------------------------------------- /background/background.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { 4 | configs, 5 | log, 6 | debug, 7 | } from '/common/common.js'; 8 | 9 | let isFirefox = !!browser.runtime.getBrowserInfo; 10 | let isChromium = !browser.runtime.getBrowserInfo; 11 | const BROWSER = isFirefox ? 'Firefox' : 12 | /Edg/.test(navigator.userAgent) ? 'Edge' : 13 | 'Chrome'; 14 | 15 | const CANCEL_RESPONSE = isChromium ? 16 | { redirectUrl: `data:text/html,${escape('')}` } : 17 | { cancel: true } ; 18 | 19 | const VALID_MATCH_PATTERN = (() => { 20 | const schemeSegment = '(\\*|http|https|file|ftp)'; 21 | const hostSegment = '(\\*|(?:\\*\\.)?(?:[^/*]+))?'; 22 | const pathSegment = '(.*)'; 23 | const regex = new RegExp( 24 | `^${schemeSegment}://${hostSegment}/${pathSegment}$` 25 | ); 26 | return regex; 27 | })(); 28 | 29 | let sitesOpenedBySelfList = []; 30 | let sitesOpenedBySelfRegex = null; 31 | const openingTabs = new Map(); 32 | 33 | 34 | function installMenuItems() { 35 | browser.contextMenus.create({ 36 | id: 'page', 37 | type: 'normal', 38 | title: browser.i18n.getMessage('contextMenu_page_label'), 39 | contexts: installMenuItems.supportsTabContext ? ['page', 'tab'] : ['page'] 40 | }); 41 | browser.contextMenus.create({ 42 | id: 'link', 43 | type: 'normal', 44 | title: browser.i18n.getMessage('contextMenu_link_label'), 45 | contexts: ['link'] 46 | }); 47 | } 48 | installMenuItems.supportsTabContext = false; 49 | 50 | 51 | let forceIEListRegex = null; 52 | 53 | function installBlocker() { 54 | if (configs.talkEnabled) 55 | return; 56 | 57 | const list = configs.forceielist.trim().split(/\s+/).filter((aItem) => !!aItem); 58 | log('force list: ', list); 59 | 60 | const types = ['main_frame']; 61 | if (!configs.onlyMainFrame) 62 | types.push('sub_frame'); 63 | 64 | debug('frame types: ', types); 65 | let urls = list; 66 | forceIEListRegex = new RegExp(list.map(pattern => { 67 | if (!VALID_MATCH_PATTERN.exec(pattern)) { 68 | urls = ['']; 69 | return `${migratePatternToRegExp(pattern)}`.replace(/^\/(.+)\//, '$1') 70 | } 71 | else { 72 | return `${matchPatternToRegExp(pattern)}`.replace(/^\/(.+)\//, '$1'); 73 | } 74 | }).join('|')); 75 | log('forceIEListRegex:', forceIEListRegex); 76 | 77 | if (list.length > 0 && 78 | !browser.webRequest.onBeforeRequest.hasListener(onBeforeRequest)) 79 | browser.webRequest.onBeforeRequest.addListener( 80 | onBeforeRequest, 81 | { urls, types }, 82 | ['blocking'] 83 | ); 84 | } 85 | function uninstallBlocker() { 86 | if (browser.webRequest.onBeforeRequest.hasListener(onBeforeRequest)) 87 | browser.webRequest.onBeforeRequest.removeListener(onBeforeRequest); 88 | forceIEListRegex = null; 89 | } 90 | function onBeforeRequest(details) { 91 | log('onBeforeRequest', details); 92 | let redirected = true; 93 | 94 | if (details.tabId < 0) { 95 | log('invalid tabId: ', details.tabId); 96 | redirected = false; 97 | } 98 | else { 99 | let targetURL = details.url; 100 | if (configs.ignoreQueryString) 101 | targetURL = details.url.replace(/\?.*/, ''); 102 | 103 | debug('targetURL: ', targetURL); 104 | if (forceIEListRegex) { 105 | debug('forceIEListRegex: ', forceIEListRegex); 106 | const matched = forceIEListRegex.test(targetURL); 107 | debug('matched to forceIEListRegex?: ', matched); 108 | if (matched) 109 | redirected = true; 110 | else { 111 | redirected = false; 112 | } 113 | } 114 | else { 115 | redirected = false; 116 | } 117 | if (sitesOpenedBySelfRegex) { 118 | debug('sitesOpenedBySelfList: ', sitesOpenedBySelfList); 119 | debug('sitesOpenedBySelfRegex: ', sitesOpenedBySelfRegex); 120 | debug('test url:', targetURL); 121 | const matched = sitesOpenedBySelfRegex.test(targetURL); 122 | debug('matched to sitesOpenedBySelfRegex?: ', matched); 123 | if (matched) 124 | redirected = false; 125 | } 126 | debug('redirected?: ', redirected); 127 | if (redirected) { 128 | launch(details.url); 129 | log('is opening tab?: ', openingTabs.has(details.tabId)); 130 | if (configs.closeReloadPage && 131 | openingTabs.has(details.tabId)) { 132 | openingTabs.delete(details.tabId); 133 | browser.tabs.remove(details.tabId); 134 | } 135 | } 136 | else { 137 | log('url is not redirected: ', details.url); 138 | } 139 | } 140 | 141 | if (!redirected) 142 | return {}; 143 | 144 | return CANCEL_RESPONSE; 145 | } 146 | 147 | /* 148 | * Talk Protocol Support 149 | * 150 | * This implements the "bridge" mode that delegates the URL handling 151 | * to Talk-compatible host programs (like BrowserSelector). 152 | * 153 | * For more details, visit the project page of BrowserSelector. 154 | * https://gitlab.com/clear-code/browserselector/ 155 | */ 156 | const TalkClient = { 157 | 158 | init() { 159 | if (this.running) 160 | return; 161 | 162 | this.isNewTab = {}; 163 | this.callback = this.onBeforeRequest.bind(this); 164 | this.listen(); 165 | this.running = true; 166 | log('Running as Talk client'); 167 | }, 168 | 169 | listen() { 170 | browser.webRequest.onBeforeRequest.addListener( 171 | this.callback, 172 | { 173 | urls: [''], 174 | types: ['main_frame'] 175 | }, 176 | ['blocking'] 177 | ); 178 | 179 | /* Tab book-keeping for intelligent tab handlings */ 180 | browser.tabs.onCreated.addListener(tab => { 181 | this.isNewTab[tab.id] = 1; 182 | }); 183 | 184 | browser.tabs.onUpdated.addListener((tabId, changeInfo, _tab) => { 185 | if (changeInfo.status === 'complete') { 186 | if (changeInfo.url && !/^(about:(blank|newtab|home))$/.test(changeInfo.url)) { 187 | delete this.isNewTab[tabId]; 188 | } 189 | } 190 | }); 191 | }, 192 | 193 | async onBeforeRequest(details) { 194 | const server = configs.talkServerName; 195 | const query = `Q firefox ${details.url}`; 196 | 197 | debug(`Query "${query}" to ${server}`); 198 | const response = await browser.runtime.sendNativeMessage(server, query); 199 | 200 | debug('Response was', JSON.stringify(response)); 201 | if (!response) 202 | return {}; // Continue anyway 203 | 204 | if (response.open) { 205 | if (response.close_tab && this.isNewTab[details.tabId]) { 206 | debug('Cloding tab', details.tabId); 207 | delete this.isNewTab[details.tabId]; 208 | await browser.tabs.remove(details.tabId); 209 | } 210 | return CANCEL_RESPONSE; // Stop the request 211 | } 212 | return {}; 213 | } 214 | }; 215 | 216 | /* 217 | * Talk Client for Chrome (and Edge). 218 | * 219 | * We need a separate implementation for Google Chrome since 220 | * chrome.webRequest won't allow to communicate with the host 221 | * program within onBeforeRequest(). 222 | */ 223 | const ChromeTalkClient = { 224 | 225 | NAME: 'ChromeTalkClient', 226 | 227 | init() { 228 | if (this.running) 229 | return; 230 | 231 | this.cached = null; 232 | this.isNewTab = {}; 233 | this.configure(); 234 | this.listen(); 235 | this.running = true; 236 | log('Running as Talk client for', configs.talkBrowserName); 237 | }, 238 | 239 | configure() { 240 | const server = configs.talkServerName; 241 | const query = new String(`C ${configs.talkBrowserName}`); 242 | 243 | browser.runtime.sendNativeMessage(server, query).then(response => { 244 | this.cached = response.config; 245 | debug('[Talk] configure', JSON.stringify(response.config)); 246 | }); 247 | }, 248 | 249 | listen() { 250 | browser.webRequest.onBeforeRequest.addListener( 251 | this.onBeforeRequest.bind(this), 252 | { 253 | urls: [''], 254 | types: ['main_frame'] 255 | }, 256 | ['blocking'] 257 | ); 258 | 259 | /* Refresh config for every N minute */ 260 | log('[Talk] poll config for every', configs.talkAlarmMinutes, 'minutes'); 261 | browser.alarms.create(this.NAME, {'periodInMinutes': configs.talkAlarmMinutes}); 262 | 263 | browser.alarms.onAlarm.addListener(alarm => { 264 | if (alarm.name != this.NAME) 265 | return; 266 | this.configure(); 267 | }); 268 | 269 | /* Tab book-keeping for intelligent tab handlings */ 270 | browser.tabs.onCreated.addListener(tab => { 271 | this.isNewTab[tab.id] = 1; 272 | }); 273 | 274 | browser.tabs.onUpdated.addListener((id, info, tab) => { 275 | if (info.status === 'complete') { 276 | delete this.isNewTab[tab.id]; 277 | } 278 | }); 279 | }, 280 | 281 | /* Convert BrowserSelector's pattern into RegExp */ 282 | regex(pattern, bs) { 283 | if (bs.UseRegex) 284 | return RegExp(pattern); 285 | 286 | // BrowserSelector support a 'simple' pattern that allows to use 287 | // `*` for any strings, and `?` for any single character. 288 | const specials = /(\.|\+|\(|\)|\[|\]|\\|\^|\$|\|)/g; 289 | // . + ( ) [ ] \ ^ $ | 290 | 291 | pattern = pattern.replace(specials, '\\$1'); 292 | pattern = pattern.replace(/\*/g, '.*'); 293 | pattern = pattern.replace(/\?/g, '.'); 294 | 295 | return RegExp(`^${pattern}$`, 'i'); 296 | }, 297 | 298 | redirect(bs, details) { 299 | const server = configs.talkServerName; 300 | const query = new String(`Q ${configs.talkBrowserName} ${details.url}`); 301 | 302 | if (details.tabId < 0) 303 | return; 304 | 305 | browser.tabs.get(details.tabId).then(tab => { 306 | /* This is required for Chrome's "preload" tabs */ 307 | if (browser.runtime.lastError) return; 308 | if (!tab) return; 309 | 310 | /* Open another browser via Query */ 311 | browser.runtime.sendNativeMessage(server, query); 312 | 313 | /* Close the opening tab automatically (if required) */ 314 | if (bs.CloseEmptyTab && this.isNewTab[details.tabId]) { 315 | browser.tabs.remove(details.tabId); 316 | } 317 | }); 318 | return CANCEL_RESPONSE; 319 | }, 320 | 321 | onBeforeRequest(details) { 322 | const bs = this.cached; 323 | const host = details.url.split('/')[2]; 324 | 325 | if (!bs) { 326 | log('[Talk] config cache is empty. Fetching...'); 327 | this.configure(); 328 | return; 329 | } 330 | 331 | /* URLPatterns */ 332 | for (let i = 0; i < bs.URLPatterns.length; i++) { 333 | const pattern = bs.URLPatterns[i][0]; 334 | const browser = bs.URLPatterns[i][1].toLowerCase(); 335 | 336 | if (this.regex(pattern, bs).test(details.url)) { 337 | debug('[Talk] Match', JSON.stringify({pattern: pattern, url: details.url, browser: browser})) 338 | if (browser == configs.talkBrowserName) 339 | return; 340 | if (browser == '' && bs.SecondBrowser == configs.talkBrowserName) 341 | return; 342 | return this.redirect(bs, details); 343 | } 344 | } 345 | 346 | /* HostNamePatterns */ 347 | for (let i = 0; i < bs.HostNamePatterns.length; i++) { 348 | const pattern = bs.HostNamePatterns[i][0]; 349 | const browser = bs.HostNamePatterns[i][1].toLowerCase(); 350 | 351 | if (this.regex(pattern, bs).test(host)) { 352 | debug('[Talk] Match', JSON.stringify({pattern: pattern, host: host, browser: browser})) 353 | if (browser == configs.talkBrowserName) 354 | return; 355 | if (browser == '' && bs.SecondBrowser == configs.talkBrowserName) 356 | return; 357 | return this.redirect(bs, details); 358 | } 359 | } 360 | 361 | /* No pattern matched */ 362 | debug('[Talk] No pattern matched', {url: details.url}) 363 | if (bs.DefaultBrowser !== configs.talkBrowserName) 364 | return this.redirect(bs, details); 365 | } 366 | }; 367 | 368 | /* 369 | * ThinBridge's matching function (See BHORedirector/URLRedirectCore.h) 370 | * 371 | * 1. `?` represents a single character. 372 | * 2. `*` represents an arbitrary substring. 373 | * 374 | * >>> wildcmp("http?://*.example.com/*", "https://www.example.com/") 375 | * true 376 | */ 377 | function wildcmp(wild, string) { 378 | let i = 0; 379 | let j = 0; 380 | let mp, cp; 381 | 382 | while ((j < string.length) && (wild[i] != '*')) { 383 | if ((wild[i] != string[j]) && (wild[i] != '?')) { 384 | return 0; 385 | } 386 | i += 1; 387 | j += 1; 388 | } 389 | while (j < string.length) { 390 | if (wild[i] == '*') { 391 | i += 1; 392 | 393 | if (i == wild.length) { 394 | return 1; 395 | } 396 | mp = i; 397 | cp = j + 1 398 | } else if ((wild[i] == string[j]) || (wild[i] == '?')) { 399 | i += 1; 400 | j += 1; 401 | } else { 402 | i = mp; 403 | j = cp; 404 | cp += 1; 405 | } 406 | } 407 | while (wild[i] == '*' && i < wild.length) { 408 | i += 1; 409 | } 410 | return i >= wild.length; 411 | }; 412 | 413 | /* 414 | * Talk Client for ThinBridge (Google Chrome). 415 | * 416 | * This class is used when configs.talkServerName is configured 417 | * to 'com.clear_code.thinbridge'. 418 | */ 419 | const ThinBridgeTalkClient = { 420 | 421 | NAME: 'ThinBridgeTalkClient', 422 | 423 | init() { 424 | if (this.running) 425 | return; 426 | 427 | this.cached = null; 428 | this.isNewTab = {}; 429 | this.configure(); 430 | this.listen(); 431 | this.running = true; 432 | console.log('Running as Thinbridge Talk client'); 433 | }, 434 | 435 | configure() { 436 | const query = new String('C chrome'); 437 | 438 | browser.runtime.sendNativeMessage(configs.talkServerName, query).then(response => { 439 | if (browser.runtime.lastError) { 440 | console.log('Cannot fetch config', JSON.stringify(browser.runtime.lastError)); 441 | return; 442 | } 443 | const isStartup = (this.cached == null); 444 | this.cached = response.config || {}; 445 | console.log('Fetch config', JSON.stringify(this.cached)); 446 | if (this.cached.Sections) { // full mode 447 | const sectionsByName = {}; 448 | for (const section of this.cached.Sections) { 449 | sectionsByName[(section.Name || '').toLowerCase()] = section; 450 | } 451 | for (const section of this.cached.Sections) { 452 | if (!section.ExcludeGroups) 453 | continue; 454 | for (const name of section.ExcludeGroups) { 455 | const referredSection = sectionsByName[name.toLowerCase()]; 456 | if (!referredSection) 457 | continue; 458 | section.URLExcludePatterns = [ 459 | ...(section.URLExcludePatterns || []), 460 | ...(referredSection.URLPatterns || []), 461 | ]; 462 | } 463 | } 464 | this.cached.Sections = [ 465 | ...(sectionsByName.custom18 ? [sectionsByName.custom18] : []), 466 | ...this.cached.Sections.filter(section => (section.Name || '').toLowerCase() != 'custom18'), 467 | ]; 468 | console.log('Populated config', JSON.stringify(this.cached)); 469 | } 470 | 471 | if (isStartup) { 472 | this.handleStartup(this.cached); 473 | } 474 | }); 475 | }, 476 | 477 | listen() { 478 | browser.webRequest.onBeforeRequest.addListener( 479 | this.onBeforeRequest.bind(this), 480 | { 481 | urls: [''], 482 | types: ['main_frame','sub_frame'] 483 | }, 484 | ['blocking'] 485 | ); 486 | 487 | /* Refresh config for every N minute */ 488 | console.log('Poll config for every', configs.talkAlarmMinutes, 'minutes'); 489 | browser.alarms.create(this.NAME, {'periodInMinutes': configs.talkAlarmMinutes}); 490 | 491 | browser.alarms.onAlarm.addListener(alarm => { 492 | if (alarm.name === this.NAME) { 493 | this.configure(); 494 | } 495 | }); 496 | 497 | /* Tab book-keeping for intelligent tab handlings */ 498 | browser.tabs.onCreated.addListener(tab => { 499 | this.isNewTab[tab.id] = 1; 500 | }); 501 | 502 | browser.tabs.onUpdated.addListener((id, info, tab) => { 503 | if (info.status === 'complete') { 504 | delete this.isNewTab[tab.id]; 505 | } 506 | }); 507 | }, 508 | 509 | redirect(url, tabId, closeTab) { 510 | browser.tabs.get(tabId).then(tab => { 511 | if (browser.runtime.lastError) { 512 | console.log(`* Ignore prefetch request`); 513 | return; 514 | } 515 | if (!tab) { 516 | console.log(`* URL is not coming from an actual tab`); 517 | return; 518 | } 519 | 520 | const query = new String(`Q chrome ${url}`); 521 | browser.runtime.sendNativeMessage(configs.talkServerName, query).then(_response => { 522 | if (closeTab) { 523 | browser.tabs.remove(tabId); 524 | } 525 | }); 526 | }); 527 | }, 528 | 529 | isMatchedURL(tbconfig, url) { 530 | if (tbconfig.IgnoreQueryString) { 531 | url = url.replace(/\?.*/, ''); 532 | } 533 | console.log(`* Check patterns for ${url}`); 534 | 535 | for (let pattern of (tbconfig.URLExcludePatterns || tbconfig.Excludes)) { 536 | if (Array.isArray(pattern)) 537 | pattern = pattern[0]; 538 | if (wildcmp(pattern, url)) { 539 | console.log(`* Match Exclude [${pattern}]`) 540 | return false; 541 | } 542 | } 543 | 544 | for (let pattern of (tbconfig.URLPatterns || tbconfig.Patterns)) { 545 | if (Array.isArray(pattern)) 546 | pattern = pattern[0]; 547 | if (wildcmp(pattern, url)) { 548 | console.log(`* Match [${pattern}]`) 549 | return true; 550 | } 551 | } 552 | console.log(`* No pattern matched`); 553 | return false; 554 | }, 555 | 556 | isMatchedURLLegacy(tbconfig, url) { 557 | if (tbconfig.IgnoreQueryString) { 558 | url = url.replace(/\?.*/, ''); 559 | } 560 | console.log(`* Check patterns for ${url}`); 561 | 562 | for (let pattern of (tbconfig.URLExcludePatterns || tbconfig.Excludes)) { 563 | if (Array.isArray(pattern)) 564 | pattern = pattern[0]; 565 | if (wildcmp(pattern, url)) { 566 | console.log(`* Match Exclude [${pattern}]`) 567 | return true; 568 | } 569 | } 570 | 571 | for (let pattern of (tbconfig.URLPatterns || tbconfig.Patterns)) { 572 | if (Array.isArray(pattern)) 573 | pattern = pattern[0]; 574 | if (wildcmp(pattern, url)) { 575 | console.log(`* Match [${pattern}]`) 576 | return false; 577 | } 578 | } 579 | console.log(`* No pattern matched`); 580 | return true; 581 | }, 582 | 583 | handleURLAndBlock({ tbconfig, tabId, url, isClosableTab }) { 584 | if (!url) { 585 | console.log(`* Empty URL found`); 586 | return false; 587 | } 588 | 589 | if (!/^https?:/.test(url)) { 590 | console.log(`* Ignore non-HTTP/HTTPS URL (${url})`); 591 | return false; 592 | } 593 | 594 | if (tbconfig.Sections) { 595 | // full mode 596 | let loadCount = 0; 597 | let redirectCount = 0; 598 | let closeTabCount = 0; 599 | const matchedSectionNames = []; 600 | sectionsLoop: 601 | for (const section of tbconfig.Sections) { 602 | const config = { 603 | IgnoreQueryString: tbconfig.IgnoreQueryString, 604 | CloseEmptyTab: tbconfig.CloseEmptyTab, 605 | ...section, 606 | }; 607 | console.log(`handleURLAndBlock: check for section ${section.Name} (${JSON.stringify(config)})`); 608 | if (!this.isMatchedURL(config, url)) { 609 | console.log(` => unmached`); 610 | continue; 611 | } 612 | 613 | const sectionName = (config.Name || '').toLowerCase(); 614 | matchedSectionNames.push(sectionName); 615 | 616 | if (config.CloseEmptyTab && isClosableTab) 617 | closeTabCount++; 618 | 619 | console.log(` => matched, action = ${config.Action}`); 620 | if (config.Action) { 621 | switch(config.Action.toLowerCase()) { 622 | case 'redirect': 623 | redirectCount++; 624 | break; 625 | 626 | case 'load': 627 | default: 628 | loadCount++; 629 | break; 630 | } 631 | if (sectionName == 'custom18' || sectionName == 'custom19') 632 | break sectionsLoop; 633 | } 634 | else { 635 | switch (sectionName) { 636 | case 'custom18': 637 | console.log(` => action not defined, default action for CUSTMO18: load`); 638 | loadCount++; 639 | break sectionsLoop; 640 | 641 | case BROWSER.toLowerCase(): 642 | console.log(` => action not defined, default action for ${BROWSER}: load`); 643 | loadCount++; 644 | break; 645 | 646 | default: 647 | console.log(` => action not defined, default action: redirect`); 648 | redirectCount++; 649 | if (sectionName == 'custom19') 650 | break sectionsLoop; 651 | break; 652 | } 653 | } 654 | } 655 | 656 | if (redirectCount == 0) { 657 | console.log(`* No redirection: fallback to default`); 658 | if (tbconfig.DefaultBrowser == '' || 659 | String(tbconfig.DefaultBrowser).toLowerCase() == BROWSER.toLowerCase()) { 660 | console.log(`* Continue to load as the default reaction`); 661 | loadCount++; 662 | } 663 | else { 664 | console.log(`* Redirect to the default browser ${tbconfig.DefaultBrowser}`); 665 | redirectCount++; 666 | } 667 | } 668 | 669 | if (redirectCount > 0 || loadCount == 0) { 670 | console.log(`* Redirect to another browser`); 671 | this.redirect(url, tabId, closeTabCount > 0); 672 | } 673 | console.log(`* Continue to load: ${loadCount > 0}`); 674 | return loadCount == 0; 675 | } 676 | else { 677 | // legacy mode 678 | if (!this.isMatchedURLLegacy(tbconfig, url)) 679 | return false; 680 | 681 | console.log(`* Redirect to another browser`); 682 | this.redirect(url, tabId, tbconfig.CloseEmptyTab && isClosableTab); 683 | return true; 684 | } 685 | }, 686 | 687 | /* Handle startup tabs preceding to onBeforeRequest */ 688 | handleStartup(tbconfig) { 689 | browser.tabs.query({}).then(tabs => { 690 | tabs.forEach((tab) => { 691 | const url = tab.url || tab.pendingUrl; 692 | console.log(`handleStartup ${url} (tab=${tab.id})`); 693 | this.handleURLAndBlock({ tbconfig, tabId: tab.id, url, isClosableTab: true }); 694 | }); 695 | }); 696 | }, 697 | 698 | /* Callback for webRequest.onBeforeRequest */ 699 | onBeforeRequest(details) { 700 | const tbconfig = this.cached; 701 | const isMainFrame = (details.type == 'main_frame'); 702 | 703 | console.log(`onBeforeRequest ${details.url} (tab=${details.tabId})`); 704 | 705 | if (!tbconfig) { 706 | console.log('* Config cache is empty. Fetching...'); 707 | this.configure(); 708 | return; 709 | } 710 | 711 | if (details.tabId < 0) { 712 | console.log(`* Ignore internal request`); 713 | return; 714 | } 715 | 716 | if (tbconfig.OnlyMainFrame && !isMainFrame) { 717 | console.log(`* Ignore subframe request`); 718 | return; 719 | } 720 | 721 | const isClosableTab = isMainFrame && this.isNewTab[details.tabId]; 722 | 723 | if (this.handleURLAndBlock({ tbconfig, tabId: details.tabId, url: details.url, isClosableTab })) { 724 | return CANCEL_RESPONSE; 725 | } 726 | }, 727 | }; 728 | 729 | function runTalkServer() { 730 | if (configs.talkServerName == 'com.clear_code.thinbridge') 731 | return ThinBridgeTalkClient.init(); 732 | 733 | if (isChromium) 734 | return ChromeTalkClient.init(); 735 | 736 | return TalkClient.init(); 737 | } 738 | 739 | /* 740 | * Listen `chrome.storage.onChange` to launch talkServer on 741 | * delay-loaded GPO settings. 742 | */ 743 | function onTalkEnabled(data, storageName) { 744 | if (data.talkBrowserName) 745 | configs.talkBrowserName = data.talkBrowserName.newValue; 746 | 747 | if (data.talkServerName) 748 | configs.talkServerName = data.talkServerName.newValue; 749 | 750 | if (data.talkEnabled) 751 | configs.talkEnabled = data.talkEnabled.newValue; 752 | 753 | if (data.talkEnabled && data.talkEnabled.newValue) { 754 | log('[Talk] talkEnabled is turned on. Launch a client...'); 755 | uninstallBlocker(); 756 | runTalkServer(); 757 | } 758 | log('browser.storage.onChange: ', storageName, JSON.stringify(data)); 759 | } 760 | 761 | /* 762 | * This is a safety lock to ensure ThinBridgeTalkClient is running. 763 | * 764 | * We have seen a few stability issues on Chrome where IEView WE 765 | * fails to get managed policy on startup, so we check the running 766 | * state a few seconds later. 767 | */ 768 | function checkThinBridgeMode() { 769 | if (!browser.storage.managed) 770 | log('[managed] managed storage is null') 771 | 772 | browser.storage.managed.get().then((m) => { 773 | log(`[managed] config = `, JSON.stringify(m)); 774 | 775 | if (!isChromium) { 776 | log('[managed] browser was not chrome'); 777 | return; 778 | } 779 | if (ChromeTalkClient.running) { 780 | log('[managed] ChromeTalkClient already running'); 781 | return; 782 | } 783 | if (ThinBridgeTalkClient.running) { 784 | log('[managed] ThinBridgeTalkClient already running'); 785 | return; 786 | } 787 | 788 | if (m.talkEnabled && m.talkServerName == 'com.clear_code.thinbridge') { 789 | log('[managed] Do dispatch ThinBridgeClient') 790 | configs.talkEnabled = m.talkEnabled; 791 | configs.talkServerName = m.talkServerName; 792 | uninstallBlocker(); 793 | runTalkServer(); 794 | } 795 | }); 796 | } 797 | 798 | /* 799 | * main 800 | */ 801 | 802 | /** 803 | * Transforms a pattern with wildcards (for original IE View) into a 804 | * regular expression 805 | * Note that two pass conversion is executed. First, pattern is converted into match patterns, 806 | * then it is converted into regular expressions finally. 807 | * 808 | * @param {string} pattern The pattern to transform. 809 | * @return {RegExp} The pattern's equivalent as a RegExp. 810 | */ 811 | function migratePatternToRegExp(invalidPattern) { 812 | let pattern = invalidPattern; 813 | if (pattern.charAt(0) === '*' && pattern.charAt(pattern.length - 1) === '*') { 814 | const extracted = pattern.substring(1, pattern.length - 1); 815 | log('convert host to regex:', `*://*.${extracted}/*`); 816 | const hostRegex = matchPatternToRegExp(`*://*.${extracted}/*`); 817 | log('convert path to regex:', `*://*/${pattern}`); 818 | const pathRegex = matchPatternToRegExp(`*://*/${pattern}`); 819 | log('migrated match pattern based regex:', `${hostRegex}|${pathRegex}`); 820 | return new RegExp(`${hostRegex.replace(/^\/(.+)\//, '$1')}|${pathRegex.replace(/^\/(.+)\//, '$1')}`); 821 | } else { 822 | // Just convert * and ? 823 | pattern = pattern.replace(/\*/g, '.*'); 824 | pattern = pattern.replace(/\?/g, '.?'); 825 | log('migrated regex pattern:', pattern); 826 | return new RegExp(pattern); 827 | } 828 | } 829 | 830 | /** 831 | * Transforms a valid match pattern into a regular expression 832 | * which matches all URLs included by that pattern. 833 | * 834 | * See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Match_patterns 835 | * 836 | * @param {string} pattern The pattern to transform. 837 | * @return {RegExp} The pattern's equivalent as a RegExp. 838 | * @throws {TypeError} If the pattern is not a valid MatchPattern 839 | */ 840 | function matchPatternToRegExp(pattern) { 841 | if (pattern === '') 842 | return (/^(?:http|https|file|ftp|app):\/\//); 843 | 844 | const match = VALID_MATCH_PATTERN.exec(pattern); 845 | if (!match) { 846 | log('pattern is not a valid MatchPattern', pattern); 847 | throw new TypeError(`"${pattern}" is not a valid MatchPattern`); 848 | } 849 | 850 | // eslint-disable-next-line prefer-const 851 | let [, scheme, host, path] = match; 852 | if (!host) 853 | throw new TypeError(`"${pattern}" does not have a valid host`); 854 | 855 | let regex = '^'; 856 | 857 | if (scheme === '*') { 858 | regex += '(http|https)'; 859 | } 860 | else { 861 | regex += scheme; 862 | } 863 | 864 | regex += '://'; 865 | 866 | if (host && host === '*') { 867 | regex += '[^/]+?'; 868 | } 869 | else if (host) { 870 | if (host.match(/^\*\./)) { 871 | regex += '[^/]*?'; 872 | host = host.substring(2); 873 | } 874 | regex += host.replace(/\./g, '\\.'); 875 | } 876 | 877 | if (path) { 878 | if (path === '*') { 879 | regex += '(/.*)?'; 880 | } 881 | else if (path.charAt(0) !== '/') { 882 | regex += '/'; 883 | regex += path.replace(/\./g, '\\.').replace(/\*/g, '.*?'); 884 | regex += '/?'; 885 | } 886 | } 887 | 888 | regex += '$'; 889 | return new RegExp(regex); 890 | } 891 | 892 | (async () => { 893 | await configs.$loaded; 894 | 895 | if (configs.talkEnabled) 896 | return runTalkServer(); 897 | 898 | log('Running as a stand-alone mode') 899 | 900 | await applyMCDConfigs(); 901 | await setDefaultPath(); 902 | 903 | const browserInfo = browser.runtime.getBrowserInfo && await browser.runtime.getBrowserInfo(); 904 | isFirefox = browserInfo && browserInfo.name == 'Firefox'; 905 | isChromium = !isFirefox; 906 | if (isFirefox && 907 | parseInt(browserInfo.version.split('.')[0]) >= 53) 908 | installMenuItems.supportsTabContext = true; 909 | 910 | if (configs.contextMenu) 911 | installMenuItems(); 912 | 913 | if (!configs.disableForce) 914 | installBlocker(); 915 | 916 | setSitesOpenedBySelf(); 917 | 918 | configs.$addObserver(onConfigUpdated); 919 | 920 | browser.storage.onChanged.addListener(onTalkEnabled); 921 | 922 | browser.tabs.onCreated.addListener(tab => { 923 | debug('new tab: ', tab.id); 924 | openingTabs.set(tab.id, true); 925 | }); 926 | browser.tabs.onUpdated.addListener((tabId, changeInfo, _tab) => { 927 | if (changeInfo.status == 'complete' || 928 | (changeInfo.url && 929 | !/^(about:(blank|newtab|home))$/.test(changeInfo.url))) { 930 | const alarmName = `onUpdated-${tabId}`; 931 | browser.alarms.create(alarmName, { delayInMinutes: configs.closeReloadPageMaxDelayMsec / 1000 / 60 }); 932 | browser.alarms.onAlarm.addListener(alarm => { 933 | if (alarm.name != alarmName) 934 | return; 935 | debug('remove tab from opening tabs list: ', tabId); 936 | // This needs to be done after the onBeforeRequest listener is processed. 937 | openingTabs.delete(tabId); 938 | browser.alarms.clear(alarmName); 939 | }); 940 | } 941 | }); 942 | 943 | browser.alarms.create('checkThinBridgeMode', { delayInMinutes: 2500 / 1000 / 60 }); 944 | browser.alarms.onAlarm.addListener(alarm => { 945 | if (alarm.name != 'checkThinBridgeMode') 946 | return; 947 | checkThinBridgeMode(); 948 | }); 949 | })(); 950 | 951 | async function applyMCDConfigs() { 952 | try { 953 | const response = await send({ command: 'read-mcd-configs' }); 954 | log('loaded MCD configs: ', JSON.stringify(response)); 955 | if ('loadedKeys' in response) { 956 | if (Array.isArray(response.loadedKeys)) 957 | response.loadedKeys.forEach(key => { 958 | configs[key] = response[key]; 959 | configs.$lock(key); 960 | }); 961 | } 962 | else { 963 | Object.keys(response).forEach(key => { 964 | configs[key] = response[key]; 965 | configs.$lock(key); 966 | }); 967 | } 968 | } 969 | catch(error) { 970 | log('Failed to read MCD configs: ', error); 971 | } 972 | } 973 | 974 | async function setDefaultPath() { 975 | if (configs.ieapp) 976 | return; 977 | try { 978 | const response = await send({ command: 'get-ie-path' }); 979 | if (response) { 980 | log('Received: ', JSON.stringify(response)); 981 | if (response.path) 982 | configs.ieapp = response.path; 983 | } 984 | } 985 | catch(error) { 986 | log('Error: ', error); 987 | } 988 | } 989 | 990 | function setSitesOpenedBySelf() { 991 | if (configs.disableException) { 992 | sitesOpenedBySelfList = []; 993 | sitesOpenedBySelfRegex = null; 994 | } 995 | else { 996 | sitesOpenedBySelfList = configs.sitesOpenedBySelf.trim().split(/\s+/).filter(item => !!item); 997 | if (sitesOpenedBySelfList.length > 0) 998 | sitesOpenedBySelfRegex = new RegExp(sitesOpenedBySelfList.map(pattern => { 999 | if (VALID_MATCH_PATTERN.exec(pattern)) { 1000 | return `${matchPatternToRegExp(pattern)}`.replace(/^\/(.+)\//, '$1'); 1001 | } 1002 | else { 1003 | return `${migratePatternToRegExp(pattern)}`.replace(/^\/(.+)\//, '$1'); 1004 | } 1005 | }).join('|')); 1006 | else 1007 | sitesOpenedBySelfRegex = null; 1008 | } 1009 | } 1010 | 1011 | function onConfigUpdated(key) { 1012 | switch (key) { 1013 | case 'contextMenu': 1014 | if (configs.contextMenu) { 1015 | installMenuItems(); 1016 | } 1017 | else { 1018 | browser.contextMenus.removeAll(); 1019 | } 1020 | break; 1021 | 1022 | case 'onlyMainFrame': 1023 | // fall through 1024 | case 'forceielist': 1025 | uninstallBlocker(); 1026 | if (!configs.disableForce) 1027 | installBlocker(); 1028 | break; 1029 | 1030 | case 'disableForce': 1031 | if (configs.disableForce) { 1032 | uninstallBlocker(); 1033 | } 1034 | else { 1035 | installBlocker(); 1036 | } 1037 | break; 1038 | case 'sitesOpenedBySelf': 1039 | // fall through 1040 | case 'disableException': 1041 | setSitesOpenedBySelf() 1042 | break; 1043 | } 1044 | } 1045 | 1046 | browser.contextMenus.onClicked.addListener((info, tab) => { 1047 | const url = info.linkUrl || info.pageUrl || tab.url; 1048 | log(`procesing url = ${url}`); 1049 | 1050 | launch(url); 1051 | }); 1052 | 1053 | 1054 | async function launch(url) { 1055 | if (!configs.ieapp && !configs.ieargs) 1056 | return; 1057 | 1058 | const message = { 1059 | command: 'launch', 1060 | params: { 1061 | path: configs.ieapp, 1062 | args: configs.ieargs.trim().split(/\s+/).filter(item => !!item), 1063 | url 1064 | } 1065 | }; 1066 | try{ 1067 | const response = await send(message); 1068 | log('Received: ', JSON.stringify(response)); 1069 | } 1070 | catch(error) { 1071 | log('Error: ', error); 1072 | } 1073 | } 1074 | 1075 | function send(message) { 1076 | if (configs.logging) 1077 | message.logging = true; 1078 | if (configs.debug) 1079 | message.debug = true; 1080 | message.logRotationCount = configs.logRotationCount; 1081 | message.logRotationTime = configs.logRotationTime; 1082 | log('Sending: ', JSON.stringify(message)); 1083 | return browser.runtime.sendNativeMessage('com.clear_code.ieview_we_host', message); 1084 | } 1085 | --------------------------------------------------------------------------------