├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .gitmodules ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── README_ZH.md ├── _locales ├── de │ └── messages.json ├── en │ └── messages.json ├── fr │ └── messages.json ├── ja │ └── messages.json ├── ko │ └── messages.json ├── zh_CN │ └── messages.json └── zh_TW │ └── messages.json ├── css ├── bookmark.css ├── custom.css └── reset.css ├── image ├── avatar.jpg ├── followCard.jpg ├── icon │ ├── default │ │ ├── icon128.png │ │ ├── icon19.png │ │ ├── icon256.png │ │ ├── icon38.png │ │ ├── icon512.png │ │ └── icon64.png │ ├── gray │ │ ├── icon128.png │ │ ├── icon256.png │ │ ├── icon512.png │ │ └── icon64.png │ ├── green │ │ ├── icon128.png │ │ ├── icon256.png │ │ ├── icon512.png │ │ └── icon64.png │ ├── option │ │ ├── guide │ │ │ ├── Illustrations_airport_2581@2x.png │ │ │ ├── Illustrations_fingerprint_swrc@2x.png │ │ │ ├── Illustrations_having_fun_iais@2x.png │ │ │ ├── Illustrations_heatmap_uyye@2x.png │ │ │ ├── Illustrations_privacy_protection_nlwy@2x.png │ │ │ └── Illustrations_to_the_moon_v1mv@2x.png │ │ ├── icons-add.svg │ │ ├── icons-ai.svg │ │ ├── icons-book.svg │ │ ├── icons-bug.svg │ │ ├── icons-checked_thick.svg │ │ ├── icons-checkmark.svg │ │ ├── icons-chrome.svg │ │ ├── icons-clear.svg │ │ ├── icons-cloud_connection.svg │ │ ├── icons-coins.svg │ │ ├── icons-delete_trash.svg │ │ ├── icons-dns.svg │ │ ├── icons-error.svg │ │ ├── icons-hand.svg │ │ ├── icons-in_progress.svg │ │ ├── icons-info.svg │ │ ├── icons-smartphone_ram.svg │ │ ├── icons-statistics.svg │ │ ├── icons-unchecked_thick.svg │ │ ├── icons-user_shield.svg │ │ ├── icons-warning_shield.svg │ │ ├── right.svg │ │ └── userInfo │ │ │ ├── icons-box-white.svg │ │ │ ├── icons-box.svg │ │ │ ├── icons-hand-white.svg │ │ │ ├── icons-hand.svg │ │ │ ├── icons-link-white.svg │ │ │ ├── icons-link.svg │ │ │ ├── icons-security-white.svg │ │ │ ├── icons-security.svg │ │ │ ├── icons-speed-white.svg │ │ │ └── icons-speed.svg │ ├── popup │ │ ├── Illustrations_heatmap_uyye.png │ │ ├── icons-browser.svg │ │ ├── icons-hand.svg │ │ ├── icons-report.svg │ │ ├── icons-security_checked.svg │ │ ├── icons-settings.svg │ │ └── right.svg │ └── red │ │ ├── icon128.png │ │ ├── icon256.png │ │ ├── icon512.png │ │ └── icon64.png ├── logo-d.svg ├── logo.svg └── md-icon-pause.svg ├── manifest.json ├── package.json ├── src ├── adblockEngine │ └── adblockerEngine.ts ├── adguard │ ├── api │ │ └── chrome │ │ │ └── lib │ │ │ └── api.js │ ├── browser │ │ ├── chrome │ │ │ └── lib │ │ │ │ ├── api │ │ │ │ ├── background-page.js │ │ │ │ └── tabs.js │ │ │ │ ├── content-script │ │ │ │ ├── common-script.js │ │ │ │ └── content-script.js │ │ │ │ └── utils │ │ │ │ ├── local-storage.js │ │ │ │ └── rules-storage.js │ │ └── webkit │ │ │ └── lib │ │ │ └── prefs.js │ └── lib │ │ ├── adguard.js │ │ ├── content-message-handler.js │ │ ├── content-script │ │ ├── adguard-content.js │ │ ├── assistant │ │ │ └── js │ │ │ │ ├── assistant.js │ │ │ │ └── start-assistant.js │ │ ├── i18n-helper.js │ │ ├── preload.js │ │ └── wrappers.js │ │ ├── filter │ │ ├── antibanner.js │ │ ├── cookie-filtering.js │ │ ├── filtering-log.js │ │ ├── filters.js │ │ ├── request-blocking.js │ │ ├── request-context-storage.js │ │ ├── rule-converter.js │ │ ├── rules │ │ │ ├── base-filter-rule.js │ │ │ ├── composite-rule.js │ │ │ ├── content-filter-rule.js │ │ │ ├── content-filter.js │ │ │ ├── cookie-filter.js │ │ │ ├── csp-filter.js │ │ │ ├── css-filter-rule.js │ │ │ ├── css-filter.js │ │ │ ├── domains-lookup-table.js │ │ │ ├── filter-rule-builder.js │ │ │ ├── redirect-filter.js │ │ │ ├── replace-filter.js │ │ │ ├── rules.js │ │ │ ├── script-filter-rule.js │ │ │ ├── script-filter.js │ │ │ ├── scriptlet-rule.js │ │ │ ├── scriptlets │ │ │ │ ├── redirects.js │ │ │ │ └── scriptlets.js │ │ │ ├── shortcuts-lookup-table.js │ │ │ ├── simple-regex.js │ │ │ ├── url-filter-lookup-table.js │ │ │ ├── url-filter-rule.js │ │ │ └── url-filter.js │ │ ├── subscription.js │ │ ├── update-service.js │ │ ├── userrules.js │ │ └── whitelist.js │ │ ├── libs │ │ ├── crypto-js │ │ │ ├── core.js │ │ │ └── md5.js │ │ ├── deferred.js │ │ ├── extended-css.js │ │ ├── filter-downloader.js │ │ └── sha256.js │ │ ├── stealth.js │ │ ├── storage.js │ │ ├── tabs │ │ └── tabs-api.js │ │ ├── utils │ │ ├── browser-utils.js │ │ ├── common.js │ │ ├── cookie.js │ │ ├── element-collapser.js │ │ ├── frames.js │ │ ├── log.js │ │ ├── notifier.js │ │ ├── page-stats.js │ │ ├── public-suffixes.js │ │ ├── punycode.js │ │ ├── service-client.js │ │ ├── url.js │ │ └── user-settings.js │ │ └── webrequest.js ├── background │ ├── adguardEngine │ │ └── index.ts │ ├── background.html │ ├── bookmark.ts │ ├── browserIcon.ts │ ├── contextMenus.ts │ ├── defaultAllowAds.cn.json │ ├── dnt.ts │ ├── heuristics.ts │ ├── initialize.ts │ ├── memoryOptimization.ts │ ├── moduleInterface.d.ts │ ├── statistics.ts │ ├── uninstall.ts │ └── update.ts ├── content │ ├── block.ts │ ├── bookmark.ts │ ├── dnsPrefetch.ts │ ├── memopt.ts │ ├── prerender.ts │ └── website.ts ├── core │ ├── api.ts │ ├── browserIconChange.ts │ ├── config.ts │ ├── grade.ts │ ├── http.ts │ ├── i18n.ts │ ├── logger.ts │ ├── loginState.ts │ ├── message.ts │ ├── notification.ts │ ├── store.ts │ ├── tab.ts │ └── utils.ts ├── guide │ ├── common │ │ ├── switchInfo │ │ │ ├── index.scss │ │ │ └── index.ts │ │ └── utils.ts │ ├── guide.html │ ├── guide.scss │ └── guide.ts ├── index.ts ├── libs │ └── icon │ │ ├── MaterialIcons-Regular.woff2 │ │ └── icon.css ├── option │ ├── common │ │ ├── switchInfo │ │ │ ├── index.scss │ │ │ └── index.ts │ │ └── utils.ts │ ├── component │ │ ├── aboutUs │ │ │ ├── index.scss │ │ │ └── index.ts │ │ ├── adBlock │ │ │ ├── index.scss │ │ │ └── index.ts │ │ ├── auxiliary │ │ │ ├── index.scss │ │ │ └── index.ts │ │ ├── optimization │ │ │ ├── index.scss │ │ │ └── index.ts │ │ ├── safe │ │ │ ├── index.scss │ │ │ └── index.ts │ │ ├── types.ts │ │ └── userAside │ │ │ ├── index.scss │ │ │ └── index.ts │ ├── option.html │ ├── option.scss │ ├── option.ts │ └── routerManager.ts ├── popup │ ├── popup.html │ ├── popup.scss │ └── popup.ts └── suspend │ ├── suspend.html │ └── suspend.ts ├── tsconfig.json ├── tslint.json └── webpack.config.js /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /dev/ 3 | /node_modules/ 4 | yarn.lock 5 | .idea/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/baize"] 2 | path = src/baize 3 | url = git@github.com:cloudoptlab/baize.git 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at support@cloudopt.net. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloudopt AdBlocker 2 | 3 | 4 | 5 | Cloudopt AdBlocker is a browser extension based on AdguardBrowserExtension and Cloudopt's self-developed cloud reputation assessment technology to protect your security, prevent traces, malicious domain names, filter banner ads, pop-up ads, and more. can do: 6 | 7 | 1. Block common ads. 8 | 2. Accelerate page loading, save bandwidth, block ads and pop-ups. 9 | 3. Block various spyware, adware and dial-up installers. 10 | 4. Protect your privacy by blocking common third-party tracking systems. 11 | 5. Protect you against malicious and phishing attacks. 12 | 6. Protect your computer from being targeted by mining scripts. 13 | 7. Prevent scripts from downloading things from dangerous websites. 14 | 15 | [You can also go to the official website to download and use it directly.](https://www.cloudopt.net/) 16 | 17 | ## Getting Involved 18 | 19 | We encourage you to participate in this open source project. We welcome any MR, submission of bugs, submission of suggestions, code review or any other positive contribution. 20 | 21 | MR related specifications can refer to the relevant specifications of our other open source projects: https://next.cloudopt.net/#/contributing. 22 | 23 | ## Build Instructions 24 | 25 | Require [nodejs](https://nodejs.org/en/download/) and [yarn](https://yarnpkg.com/en/docs/install/#mac-stable)。 26 | 27 | 1. Clone or Download the repository: 28 | 29 | ```shell 30 | git clone https://github.com/cloudoptlab/cloudopt-adblocker/ 31 | ``` 32 | 33 | 2. Install local dependencies by running: 34 | 35 | ```shell 36 | yarn install 37 | ``` 38 | 39 | 3. Build on the command line: 40 | ```shell 41 | yarn build 42 | ``` 43 | 44 | After the build is complete, the file will be stored in the dist directory, which can be loaded directly in the developer mode of the browser. 45 | 46 | ## License 47 | 48 | > This Source Code Form is subject to the terms of the GNU General Public License, v. 3.0. If a copy of the GPL was not distributed with this file, You can obtain one at https://www.gnu.org/licenses/gpl-3.0.en.html 49 | 50 | 51 | ## Reference license 52 | 53 | ### AdguardBrowserExtension 54 | - Project: https://github.com/AdguardTeam/AdguardBrowserExtension 55 | - License: [GNU Lesser General Public License v3.0](https://github.com/AdguardTeam/AdguardBrowserExtension/blob/master/LICENSE) 56 | 57 | ### jqKeyboard 58 | - Project: https://github.com/hawkgs/jqKeyboard 59 | - License: [MIT License](https://github.com/hawkgs/jqKeyboard/blob/master/LICENSE) 60 | 61 | ### material-design-lite 62 | - Project: https://github.com/google/material-design-lite 63 | - License: [Apache License 2.0](https://github.com/google/material-design-lite/blob/mdl-1.x/LICENSE) 64 | 65 | ### noty 66 | - Project: https://github.com/needim/noty 67 | - License: [MIT License](https://github.com/needim/noty/blob/master/LICENSE.txt) 68 | 69 | ### DOMPurify 70 | - Project: https://github.com/cure53/DOMPurify 71 | - License: [Apache License Version 2.0](https://github.com/cure53/DOMPurify/blob/master/LICENSE) 72 | 73 | ### underscore 74 | - Project: https://github.com/jashkenas/underscore 75 | - License: [MIT License](https://github.com/jashkenas/underscore/blob/master/LICENSE) 76 | 77 | ### jquery 78 | - Project: https://github.com/jquery/jquery 79 | - License: [MIT License](https://github.com/jquery/jquery/blob/master/LICENSE.txt) 80 | 81 | ### json3 82 | - Project: https://github.com/bestiejs/json3 83 | - License: [MIT License](https://github.com/bestiejs/json3/blob/master/LICENSE) 84 | 85 | ### push 86 | - Project: https://github.com/Nickersoft/push.js 87 | - License: [MIT License](https://github.com/Nickersoft/push.js/blob/master/LICENSE.md) 88 | 89 | ### easylist 90 | - Project: https://github.com/easylist/easylist 91 | - License: [GNU General Public License version 3](https://easylist.to/pages/licence.html) 92 | 93 | ## Chinese Vesion 94 | 95 | [点击阅读中文版](https://github.com/cloudoptlab/cloudopt-adblocker/blob/master/README_ZH.md) 96 | 97 | ---- 98 | 99 | QQ Group: 142574864 100 | 101 | Wechat: 102 | 103 | ![](https://kol-cdn.cloudopt.net/kol/2018/12/qrcode_for_gh_cace0716c068_258.jpg) 104 | -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | # Cloudopt AdBlocker 2 | 3 | 4 | 5 | Cloudopt AdBlocker 是基于 AdguardBrowserExtension 以及 Cloudopt 自主研发的云信誉评估技术的浏览器扩展,实时保护您的安全、防止追迹、恶意域名,过滤横幅广告、弹窗广告等等。可以做到: 6 | 7 | 1. 拦截常见广告。 8 | 2. 加速页面载入,节省带宽,屏蔽广告和弹窗。 9 | 3. 拦截各种间谍软件,广告软件和拨号安装程序。 10 | 4. 通过拦截常见第三方跟踪系统保护您的隐私。 11 | 5. 保护您对抗恶意和钓鱼攻击。 12 | 6. 保护您的电脑不会被挖矿脚本针对。 13 | 7. 阻止脚本从危险网站下载东西。 14 | 15 | [您还可以直接前往官网下载体验。](https://www.cloudopt.net/) 16 | 17 | ## 如何参与 18 | 19 | 我们鼓励您参与这个开源项目。我们欢迎任何MR、提交BUG、提交建议、代码审查或任何其他积极贡献。 20 | 21 | MR相关的规范可以参考我们其它开源项目的相关规范:https://next.cloudopt.net/#/zh-cn/contributing。 22 | 23 | 24 | ## 如何构建 25 | 26 | 本项目依赖于 [nodejs](https://nodejs.org/en/download/) 和 [yarn](https://yarnpkg.com/en/docs/install/#mac-stable)。 27 | 28 | 1. 拉取或者下载源码: 29 | 30 | ```shell 31 | git clone https://github.com/cloudoptlab/cloudopt-adblocker/ 32 | ``` 33 | 34 | 2. 到项目目录执行命令下载依赖: 35 | 36 | ```shell 37 | yarn install 38 | ``` 39 | 40 | 3. 使用yarn构建: 41 | ```shell 42 | yarn build 43 | ``` 44 | 45 | 构建完成后会将文件存放在dist目录下,可以直接使用浏览器的开发者模式加载。 46 | 47 | ## 开源协议 48 | 49 | > This Source Code Form is subject to the terms of the GNU General Public License, v. 3.0. If a copy of the GPL was not distributed with this file, You can obtain one at https://www.gnu.org/licenses/gpl-3.0.en.html 50 | 51 | 52 | ## 引用许可 53 | 54 | ### AdguardBrowserExtension 55 | - Project: https://github.com/AdguardTeam/AdguardBrowserExtension 56 | - License: [GNU Lesser General Public License v3.0](https://github.com/AdguardTeam/AdguardBrowserExtension/blob/master/LICENSE) 57 | 58 | ### jqKeyboard 59 | - Project: https://github.com/hawkgs/jqKeyboard 60 | - License: [MIT License](https://github.com/hawkgs/jqKeyboard/blob/master/LICENSE) 61 | 62 | ### material-design-lite 63 | - Project: https://github.com/google/material-design-lite 64 | - License: [Apache License 2.0](https://github.com/google/material-design-lite/blob/mdl-1.x/LICENSE) 65 | 66 | ### noty 67 | - Project: https://github.com/needim/noty 68 | - License: [MIT License](https://github.com/needim/noty/blob/master/LICENSE.txt) 69 | 70 | ### DOMPurify 71 | - Project: https://github.com/cure53/DOMPurify 72 | - License: [Apache License Version 2.0](https://github.com/cure53/DOMPurify/blob/master/LICENSE) 73 | 74 | ### underscore 75 | - Project: https://github.com/jashkenas/underscore 76 | - License: [MIT License](https://github.com/jashkenas/underscore/blob/master/LICENSE) 77 | 78 | ### jquery 79 | - Project: https://github.com/jquery/jquery 80 | - License: [MIT License](https://github.com/jquery/jquery/blob/master/LICENSE.txt) 81 | 82 | ### json3 83 | - Project: https://github.com/bestiejs/json3 84 | - License: [MIT License](https://github.com/bestiejs/json3/blob/master/LICENSE) 85 | 86 | ### push 87 | - Project: https://github.com/Nickersoft/push.js 88 | - License: [MIT License](https://github.com/Nickersoft/push.js/blob/master/LICENSE.md) 89 | 90 | ### easylist 91 | - Project: https://github.com/easylist/easylist 92 | - License: [GNU General Public License version 3](https://easylist.to/pages/licence.html) 93 | 94 | ---- 95 | 96 | 开发者 QQ 群: 142574864 97 | 98 | 关注 Cloudopt 公众号获取最新资讯: 99 | 100 | ![](https://kol-cdn.cloudopt.net/kol/2018/12/qrcode_for_gh_cace0716c068_258.jpg) -------------------------------------------------------------------------------- /css/bookmark.css: -------------------------------------------------------------------------------- 1 | .cloudopt-bookmark-head { 2 | color: #9ba3af; 3 | } 4 | 5 | .cloudopt-bookmark-head img { 6 | float: right; 7 | width: 70px; 8 | } 9 | 10 | #cloudopt-bookmark { 11 | width: 100%; 12 | padding: 16px; 13 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .14), 0 3px 1px -2px rgba(0, 0, 0, .2), 0 1px 5px 0 rgba(0, 0, 0, .12); 14 | margin-bottom: 16px; 15 | } 16 | 17 | #cloudopt-bookmark .cloudopt-bookmark-li { 18 | padding: 16px; 19 | } 20 | 21 | #cloudopt-bookmark .cloudopt-bookmark-li p { 22 | font-size: 13px; 23 | color: #006d21; 24 | margin: 0px !important; 25 | text-overflow: ellipsis; 26 | overflow: hidden; 27 | white-space: nowrap; 28 | } 29 | 30 | #cloudopt-bookmark .cloudopt-bookmark-li a { 31 | display: block; 32 | font-size: 16px; 33 | color: #001ba0; 34 | margin-bottom: 5px; 35 | } 36 | 37 | .cloudopt-bookmark-head .cloudopt-icon { 38 | float: inherit; 39 | width: 10px; 40 | margin-left: 10px; 41 | } -------------------------------------------------------------------------------- /css/custom.css: -------------------------------------------------------------------------------- 1 | .mdl-card { 2 | min-height: 0px; 3 | } 4 | 5 | .mdl-chip { 6 | margin-left: 10px; 7 | } 8 | 9 | .mdl-list__item-avatar.material-icons { 10 | font-size: 30px; 11 | color: #757575; 12 | background-color: inherit; 13 | } 14 | 15 | #material-card { 16 | height: 500px; 17 | width: 100%; 18 | z-index: -1; 19 | background-color: #3f51b5; 20 | position: absolute; 21 | } 22 | 23 | .mdl-layout-title img { 24 | width: 100px; 25 | } 26 | 27 | #option-main { 28 | max-width: 800px; 29 | margin: auto; 30 | } 31 | 32 | #option-main .logo { 33 | margin-top: 20px; 34 | width: 100px; 35 | } 36 | 37 | #option-main .mdl-card:first-of-type { 38 | margin-top: 20px; 39 | } 40 | 41 | #option-main .mdl-card { 42 | width: 100%; 43 | margin-top: 50px; 44 | } 45 | 46 | #allow-list, #block-list { 47 | width: 100%; 48 | } 49 | 50 | #allow-list .mdl-card__title-text, #block-list .mdl-card__title-text { 51 | width: 100%; 52 | padding: 0px; 53 | } 54 | 55 | .mdl-data-table { 56 | width: 100%; 57 | border: 0px; 58 | overflow-y: auto; 59 | overflow-x: hidden; 60 | } 61 | 62 | .allowListDelete, .blockListDelete, .manualDelete, .customSubscriptionDelete { 63 | cursor: pointer; 64 | } 65 | 66 | table .mdl-list__item-avatar.material-icons { 67 | font-size: 20px; 68 | padding-top: 10px; 69 | } 70 | 71 | .mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-sub-title { 72 | max-width: 600px; 73 | } 74 | 75 | .mdl-checkbox__tick-outline { 76 | -webkit-mask: none; 77 | mask: none; 78 | } 79 | 80 | .mdl-data-table__cell--non-numeric.mdl-data-table__cell--non-numeric { 81 | text-align: left; 82 | max-width: 500px; 83 | white-space: nowrap; 84 | overflow: hidden; 85 | text-overflow: ellipsis; 86 | } 87 | 88 | #follow .mdl-card__title { 89 | min-height: 300px; 90 | background-image: url('/image/followCard.jpg'); 91 | background-color: #ffffff; 92 | background-repeat: no-repeat; 93 | background-position: center; 94 | } 95 | 96 | .mdl-dialog__content img { 97 | width: 100%; 98 | } 99 | 100 | .account-card .mdl-chip { 101 | padding: 16px; 102 | background-color: inherit; 103 | } 104 | 105 | .account-card img { 106 | width: 40px; 107 | height: 40px; 108 | border-radius: 40px; 109 | } 110 | 111 | @media screen and (max-width: 700px) { 112 | .mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-sub-title { 113 | display: none; 114 | } 115 | #option-main .logo { 116 | margin-left: 20px; 117 | } 118 | .mdl-data-table__cell--non-numeric.mdl-data-table__cell--non-numeric { 119 | max-width: 100px; 120 | } 121 | } -------------------------------------------------------------------------------- /image/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/avatar.jpg -------------------------------------------------------------------------------- /image/followCard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/followCard.jpg -------------------------------------------------------------------------------- /image/icon/default/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/default/icon128.png -------------------------------------------------------------------------------- /image/icon/default/icon19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/default/icon19.png -------------------------------------------------------------------------------- /image/icon/default/icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/default/icon256.png -------------------------------------------------------------------------------- /image/icon/default/icon38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/default/icon38.png -------------------------------------------------------------------------------- /image/icon/default/icon512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/default/icon512.png -------------------------------------------------------------------------------- /image/icon/default/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/default/icon64.png -------------------------------------------------------------------------------- /image/icon/gray/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/gray/icon128.png -------------------------------------------------------------------------------- /image/icon/gray/icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/gray/icon256.png -------------------------------------------------------------------------------- /image/icon/gray/icon512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/gray/icon512.png -------------------------------------------------------------------------------- /image/icon/gray/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/gray/icon64.png -------------------------------------------------------------------------------- /image/icon/green/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/green/icon128.png -------------------------------------------------------------------------------- /image/icon/green/icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/green/icon256.png -------------------------------------------------------------------------------- /image/icon/green/icon512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/green/icon512.png -------------------------------------------------------------------------------- /image/icon/green/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/green/icon64.png -------------------------------------------------------------------------------- /image/icon/option/guide/Illustrations_airport_2581@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/option/guide/Illustrations_airport_2581@2x.png -------------------------------------------------------------------------------- /image/icon/option/guide/Illustrations_fingerprint_swrc@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/option/guide/Illustrations_fingerprint_swrc@2x.png -------------------------------------------------------------------------------- /image/icon/option/guide/Illustrations_having_fun_iais@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/option/guide/Illustrations_having_fun_iais@2x.png -------------------------------------------------------------------------------- /image/icon/option/guide/Illustrations_heatmap_uyye@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/option/guide/Illustrations_heatmap_uyye@2x.png -------------------------------------------------------------------------------- /image/icon/option/guide/Illustrations_privacy_protection_nlwy@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/option/guide/Illustrations_privacy_protection_nlwy@2x.png -------------------------------------------------------------------------------- /image/icon/option/guide/Illustrations_to_the_moon_v1mv@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/option/guide/Illustrations_to_the_moon_v1mv@2x.png -------------------------------------------------------------------------------- /image/icon/option/icons-add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-ai.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-book.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-bug.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-checked_thick.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-checkmark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-chrome.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-clear.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-cloud_connection.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-coins.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-delete_trash.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-dns.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-error.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-hand.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-in_progress.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-info.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-smartphone_ram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-statistics.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-unchecked_thick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | background 5 | 6 | 7 | 8 | Layer 1 9 | 10 | 11 | -------------------------------------------------------------------------------- /image/icon/option/icons-user_shield.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/icons-warning_shield.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/userInfo/icons-box-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/userInfo/icons-box.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/userInfo/icons-hand-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/userInfo/icons-hand.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/userInfo/icons-link-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/userInfo/icons-link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/userInfo/icons-security-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/userInfo/icons-security.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/userInfo/icons-speed-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/option/userInfo/icons-speed.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/popup/Illustrations_heatmap_uyye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/popup/Illustrations_heatmap_uyye.png -------------------------------------------------------------------------------- /image/icon/popup/icons-browser.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/popup/icons-hand.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/popup/icons-report.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/popup/icons-security_checked.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/popup/icons-settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/popup/right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/icon/red/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/red/icon128.png -------------------------------------------------------------------------------- /image/icon/red/icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/red/icon256.png -------------------------------------------------------------------------------- /image/icon/red/icon512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/red/icon512.png -------------------------------------------------------------------------------- /image/icon/red/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/image/icon/red/icon64.png -------------------------------------------------------------------------------- /image/logo-d.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | CLOUDOPT 14 | 15 | 16 | -------------------------------------------------------------------------------- /image/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 24 | 25 | 30 | 31 | -------------------------------------------------------------------------------- /image/md-icon-pause.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "__MSG_infoName__", 4 | "short_name": "Cloudopt AdBlocker", 5 | "version": "2.3.0", 6 | "content_security_policy": "script-src 'self'; object-src 'self'", 7 | "description": "__MSG_infoDescription__", 8 | "default_locale": "en", 9 | "icons": { 10 | "64": "image/icon/default/icon64.png", 11 | "128": "image/icon/default/icon128.png", 12 | "256": "image/icon/default/icon256.png", 13 | "512": "image/icon/default/icon512.png" 14 | }, 15 | "permissions": [ 16 | "", 17 | "privacy", 18 | "notifications", 19 | "tabs", 20 | "activeTab", 21 | "bookmarks", 22 | "storage", 23 | "unlimitedStorage", 24 | "webRequest", 25 | "webRequestBlocking", 26 | "contextMenus", 27 | "webNavigation", 28 | "system.memory" 29 | ], 30 | "browser_action": { 31 | "default_icon": { 32 | "19": "image/icon/default/icon19.png", 33 | "38": "image/icon/default/icon38.png" 34 | }, 35 | "default_title": "Cloudopt AdBlocker", 36 | "default_popup": "popup.html" 37 | }, 38 | "options_page": "option.html", 39 | "background": { 40 | "page": "background.html", 41 | "persistent": true 42 | }, 43 | "content_scripts": [{ 44 | "all_frames": true, 45 | "matches": [ 46 | "http://*/*", 47 | "https://*/*" 48 | ], 49 | "match_about_blank": true, 50 | "css": [ 51 | "lib/icon/icon.css", 52 | "css/bookmark.css" 53 | ], 54 | "js": [ 55 | "lib/jquery.min.js", 56 | "content.js" 57 | ] 58 | }, 59 | { 60 | "all_frames": false, 61 | "matches": [ 62 | "http://*.cloudopt.net/*", 63 | "https://*.cloudopt.net/*" 64 | ], 65 | "js": [ 66 | "block.js" 67 | ] 68 | }, 69 | { 70 | "all_frames": true, 71 | "js": [ 72 | "lib/purify.min.js", 73 | "adguard/adguard-content.js" 74 | ], 75 | "matches": [ 76 | "http://*/*", 77 | "https://*/*" 78 | ], 79 | "match_about_blank": true, 80 | "run_at": "document_start" 81 | }, 82 | { 83 | "all_frames": false, 84 | "js": [ 85 | "lib/purify.min.js", 86 | "adguard/adguard-assistant.js" 87 | ], 88 | "matches": [ 89 | "http://*/*", 90 | "https://*/*" 91 | ], 92 | "run_at": "document_end" 93 | } 94 | ], 95 | "web_accessible_resources": [ 96 | "adguard/assistant/assistant.js", 97 | "suspend.html" 98 | ] 99 | } 100 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudopt-adblocker", 3 | "version": "2.3.0", 4 | "description": "Protects you from tracing and malicious domain names, filters banner ads, pop-up ads and video ads, all in real-time.", 5 | "repository": "https://www.cloudopt.net", 6 | "author": "Cloudopt", 7 | "license": "GPL-3.0", 8 | "devDependencies": { 9 | "@types/chrome": "^0.0.89", 10 | "@types/jquery": "^3.3.31", 11 | "copy-webpack-plugin": "^5.0.4", 12 | "html-webpack-plugin": "^3.2.0", 13 | "ini": ">=1.3.6", 14 | "ts-loader": "^6.1.0", 15 | "tslint": "^5.20.0", 16 | "typescript": "^3.6.2", 17 | "webpack": "^4.41.0", 18 | "webpack-cli": "^3.3.9", 19 | "webpack-concat-plugin": "3.0.0", 20 | "fast-csv": "^4.3.2" 21 | }, 22 | "scripts": { 23 | "build": "webpack", 24 | "start": "node_modules/.bin/webpack-dev-server --mode development --progress --colors --public --config webpack.dev.options.config", 25 | "lint": "tslint -c tslint.json src/**/*.ts" 26 | }, 27 | "dependencies": { 28 | "@antv/data-set": "^0.10.2", 29 | "@antv/g2": "^3.5.9", 30 | "@types/dompurify": "^0.0.33", 31 | "@types/lodash": "^4.14.138", 32 | "bootstrap": "^4.3.1", 33 | "cross-env": "^6.0.3", 34 | "css-loader": "^3.2.0", 35 | "dompurify": ">=2.0.17", 36 | "file-loader": "^4.2.0", 37 | "ini": ">=1.3.6", 38 | "inquirer": "^7.0.0", 39 | "jquery": "^3.4.1", 40 | "lodash": ">=4.17.21", 41 | "material-design-lite": "^1.3.0", 42 | "node-sass": "^4.13.0", 43 | "popper.js": "^1.16.0", 44 | "push.js": "^1.0.12", 45 | "sass-loader": "^8.0.0", 46 | "shards-ui": "^3.0.0", 47 | "style-loader": "^1.0.0", 48 | "webpack-dev-server": "^3.9.0", 49 | "glob-parent": ">=5.1.2", 50 | "browserslist": ">=4.16.5", 51 | "hosted-git-info": ">=2.8.9", 52 | "underscore": ">=1.12.1", 53 | "y18n": ">=3.2.2" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/adblockEngine/adblockerEngine.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the interface file of the adblock engine. 3 | * @property {string} name The name of the engin. 4 | * @property {boolean} start The startup function of the engine. The engine must start through the start function. 5 | * It cannot run when the file is loaded. 6 | * @property {boolean} refresh By refresh method, the engine automatically loads the latest settings. 7 | * @property {boolean} stop Used to stop the engine. 8 | */ 9 | 10 | export default interface IAdblockEngine { 11 | readonly name: string, 12 | start: () => boolean, 13 | refresh: () => Promise, 14 | stop: () => boolean 15 | } 16 | -------------------------------------------------------------------------------- /src/adguard/browser/chrome/lib/content-script/common-script.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | /* global adguardContent */ 19 | 20 | (function (adguard, self) { 21 | 'use strict'; 22 | 23 | /** 24 | * https://bugs.chromium.org/p/project-zero/issues/detail?id=1225&desc=6 25 | * Page script can inject global variables into the DOM, 26 | * so content script isolation doesn't work as expected 27 | * So we have to make additional check before accessing a global variable. 28 | */ 29 | function isDefined(property) { 30 | return Object.prototype.hasOwnProperty.call(self, property); 31 | } 32 | 33 | const browserApi = isDefined('browser') && self.browser !== undefined ? self.browser : self.chrome; 34 | 35 | adguard.i18n = browserApi.i18n; 36 | 37 | adguard.runtimeImpl = (function () { 38 | const onMessage = (function () { 39 | if (browserApi.runtime && browserApi.runtime.onMessage) { 40 | // Chromium, Edge, Firefox WebExtensions 41 | return browserApi.runtime.onMessage; 42 | } 43 | // Old Chromium 44 | return browserApi.extension.onMessage || browserApi.extension.onRequest; 45 | })(); 46 | 47 | const sendMessage = (function () { 48 | if (browserApi.runtime && browserApi.runtime.sendMessage) { 49 | // Chromium, Edge, Firefox WebExtensions 50 | return browserApi.runtime.sendMessage; 51 | } 52 | // Old Chromium 53 | return browserApi.extension.sendMessage || browserApi.extension.sendRequest; 54 | })(); 55 | 56 | return { 57 | onMessage, 58 | sendMessage, 59 | }; 60 | })(); 61 | })(typeof adguardContent !== 'undefined' ? adguardContent : adguard, this); // jshint ignore:line 62 | -------------------------------------------------------------------------------- /src/adguard/browser/chrome/lib/content-script/content-script.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | /* global adguardContent */ 19 | 20 | (function (adguard) { 21 | 'use strict'; 22 | 23 | window.i18n = adguard.i18n; 24 | 25 | window.contentPage = { 26 | sendMessage: adguard.runtimeImpl.sendMessage, 27 | onMessage: adguard.runtimeImpl.onMessage, 28 | }; 29 | })(adguardContent); 30 | -------------------------------------------------------------------------------- /src/adguard/browser/chrome/lib/utils/rules-storage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | /** 19 | * Filter rules storage implementation 20 | */ 21 | adguard.rulesStorageImpl = (function () { 22 | /** 23 | * Checks runtime.lastError and calls "callback" if so. 24 | * 25 | * @returns {boolean} true if operation caused error 26 | */ 27 | const checkLastError = function (callback) { 28 | if (browser.runtime.lastError) { 29 | callback(browser.runtime.lastError); 30 | return true; 31 | } 32 | 33 | return false; 34 | }; 35 | 36 | const read = function (path, callback) { 37 | try { 38 | browser.storage.local.get(path, (results) => { 39 | if (!checkLastError(callback)) { 40 | let lines = []; 41 | 42 | if (results && results[path] instanceof Array) { 43 | lines = results[path]; 44 | } 45 | 46 | callback(null, lines); 47 | } 48 | }); 49 | } catch (ex) { 50 | callback(ex); 51 | } 52 | }; 53 | 54 | const write = function (path, data, callback) { 55 | const item = {}; 56 | item[path] = data; 57 | try { 58 | browser.storage.local.set(item, () => { 59 | if (!checkLastError(callback)) { 60 | callback(); 61 | } 62 | }); 63 | } catch (ex) { 64 | callback(ex); 65 | } 66 | }; 67 | 68 | const remove = function (path, successCallback) { 69 | browser.storage.local.remove(path, successCallback); 70 | }; 71 | 72 | return { 73 | read, 74 | write, 75 | remove, 76 | }; 77 | })(); 78 | -------------------------------------------------------------------------------- /src/adguard/lib/adguard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | /** 19 | * Global adguard object 20 | */ 21 | var adguard = (function () { // eslint-disable-line 22 | /** 23 | * This function allows cache property in object. Use with javascript getter. 24 | * 25 | * var Object = { 26 | * 27 | * get someProperty(){ 28 | * return adguard.lazyGet(Object, 'someProperty', function() { 29 | * return calculateSomeProperty(); 30 | * }); 31 | * } 32 | * } 33 | * 34 | * @param object Object 35 | * @param prop Original property name 36 | * @param calculateFunc Calculation function 37 | * @returns {*} 38 | */ 39 | const lazyGet = function (object, prop, calculateFunc) { 40 | const cachedProp = `_${prop}`; 41 | if (cachedProp in object) { 42 | return object[cachedProp]; 43 | } 44 | const value = calculateFunc.apply(object); 45 | object[cachedProp] = value; 46 | return value; 47 | }; 48 | 49 | /** 50 | * Clear cached property 51 | * @param object Object 52 | * @param prop Original property name 53 | */ 54 | const lazyGetClear = function (object, prop) { 55 | delete object[`_${prop}`]; 56 | }; 57 | 58 | function notImplemented() { 59 | return false; 60 | } 61 | 62 | const hitStatsModule = { 63 | addRuleHit: notImplemented, 64 | addDomainView: notImplemented, 65 | cleanup: notImplemented, 66 | }; 67 | 68 | const filteringLogModule = { 69 | addHttpRequestEvent: notImplemented, 70 | clearEventsByTabId: notImplemented, 71 | isOpen: notImplemented, 72 | }; 73 | 74 | const safebrowsingModule = { 75 | checkSafebrowsingFilter: notImplemented, 76 | }; 77 | 78 | const integrationModule = { 79 | isSupported: notImplemented, 80 | isEnabled: notImplemented, 81 | isIntegrationRequest: notImplemented, 82 | shouldOverrideReferrer: notImplemented, 83 | }; 84 | 85 | const syncModule = { 86 | syncService: notImplemented(), 87 | settingsProvider: notImplemented(), 88 | }; 89 | 90 | return { 91 | lazyGet, 92 | lazyGetClear, 93 | 94 | /** 95 | * Define dummy modules. 96 | * In case of simple adguard API, some modules aren't supported 97 | */ 98 | hitStats: hitStatsModule, 99 | filteringLog: filteringLogModule, 100 | safebrowsing: safebrowsingModule, 101 | integration: integrationModule, 102 | sync: syncModule, 103 | }; 104 | })(); 105 | -------------------------------------------------------------------------------- /src/adguard/lib/content-script/adguard-content.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | /** 19 | * Global object for content scripts. 20 | * !!! DO not change to const, because this variable will be redeclared in adguard-api 21 | */ 22 | var adguardContent = {}; // eslint-disable-line no-unused-vars, no-var 23 | -------------------------------------------------------------------------------- /src/adguard/lib/content-script/assistant/js/start-assistant.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | /* global contentPage, adguardAssistant */ 19 | 20 | (function () { 21 | if (window.top !== window || !(document.documentElement instanceof HTMLElement)) { 22 | return; 23 | } 24 | 25 | /** 26 | * `contentPage` may be undefined on the extension startup in FF browser. 27 | * 28 | * Different browsers have different strategies of the content scripts 29 | * injections on extension startup. 30 | * For example, FF injects content scripts in already opened tabs, but Chrome doesn't do it. 31 | * In the case of the FF browser, content scripts with the `document_start` 32 | * option won't injected into opened tabs, so we have to directly check this case. 33 | */ 34 | if (typeof contentPage === 'undefined') { 35 | return; 36 | } 37 | 38 | let assistant; 39 | 40 | // save right-clicked element for assistant 41 | let clickedEl = null; 42 | document.addEventListener('mousedown', (event) => { 43 | if (event.button === 2) { 44 | clickedEl = event.target; 45 | } 46 | }); 47 | 48 | contentPage.onMessage.addListener((message) => { 49 | switch (message.type) { 50 | case 'initAssistant': { 51 | const { options } = message; 52 | const { addRuleCallbackName } = options; 53 | let selectedElement = null; 54 | if (clickedEl && options.selectElement) { 55 | selectedElement = clickedEl; 56 | } 57 | 58 | if (!assistant) { 59 | assistant = adguardAssistant(); 60 | } else { 61 | assistant.close(); 62 | } 63 | 64 | assistant.start(selectedElement, (rules) => { 65 | contentPage.sendMessage({ type: addRuleCallbackName, ruleText: rules }); 66 | }); 67 | break; 68 | } 69 | default: 70 | break; 71 | } 72 | }); 73 | })(); 74 | -------------------------------------------------------------------------------- /src/adguard/lib/content-script/i18n-helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | const I18nHelper = { // eslint-disable-line 18 | 19 | translateElement(element, message) { 20 | try { 21 | while (element.lastChild) { 22 | element.removeChild(element.lastChild); 23 | } 24 | 25 | this.processString(message, element); 26 | } catch (ex) { 27 | // Ignore exceptions 28 | } 29 | }, 30 | 31 | processString(str, element) { 32 | let el; 33 | 34 | const match1 = /^([^]*?)<(a|strong|span|i)([^>]*)>(.*?)<\/\2>([^]*)$/m.exec(str); 35 | const match2 = /^([^]*?)<(br|input)([^>]*)\/?>([^]*)$/m.exec(str); 36 | if (match1) { 37 | this.processString(match1[1], element); 38 | 39 | el = this.createElement(match1[2], match1[3]); 40 | 41 | this.processString(match1[4], el); 42 | element.appendChild(el); 43 | 44 | this.processString(match1[5], element); 45 | } else if (match2) { 46 | this.processString(match2[1], element); 47 | 48 | el = this.createElement(match2[2], match2[3]); 49 | element.appendChild(el); 50 | 51 | this.processString(match2[4], element); 52 | } else { 53 | element.appendChild(document.createTextNode(str.replace(/ /g, '\u00A0'))); 54 | } 55 | }, 56 | 57 | createElement(tagName, attributes) { 58 | const el = document.createElement(tagName); 59 | if (!attributes) { 60 | return el; 61 | } 62 | 63 | const attrs = attributes.split(/([a-z]+='[^']+')/); 64 | for (let i = 0; i < attrs.length; i += 1) { 65 | const attr = attrs[i].trim(); 66 | if (!attr) { 67 | continue; 68 | } 69 | const index = attr.indexOf('='); 70 | let attrName; 71 | let attrValue; 72 | if (index > 0) { 73 | attrName = attr.substring(0, index); 74 | attrValue = attr.substring(index + 2, attr.length - 1); 75 | } 76 | if (attrName && attrValue) { 77 | el.setAttribute(attrName, attrValue); 78 | } 79 | } 80 | 81 | return el; 82 | }, 83 | }; 84 | -------------------------------------------------------------------------------- /src/adguard/lib/filter/rules/composite-rule.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | (function (api) { 19 | /** 20 | * This rule may contain a list of rules generated from one complex ruleText 21 | * @constructor 22 | * 23 | * @example 24 | * input 25 | * ABP snippet rule 26 | * `example.org#$#hide-if-has-and-matches-style someSelector; hide-if-contains someSelector2` 27 | * 28 | * output 29 | * Adguard scriptlet rules 30 | * `example.org#%#//scriptlet("hide-if-has-and-matches-style", "someSelector")` 31 | * `example.org#%#//scriptlet("hide-if-contains", "someSelector2")` 32 | * 33 | */ 34 | function CompositeRule(ruleText, rules) { 35 | this.ruleText = ruleText; 36 | this.rules = rules; 37 | } 38 | 39 | /** 40 | * @static ScriptletRule 41 | */ 42 | api.CompositeRule = CompositeRule; 43 | })(adguard.rules); 44 | -------------------------------------------------------------------------------- /src/adguard/lib/filter/rules/cookie-filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | (function (adguard, api) { 19 | 'use strict'; 20 | 21 | /** 22 | * Filter for cookie filter rules 23 | * https://github.com/AdguardTeam/AdguardBrowserExtension/issues/961 24 | */ 25 | api.CookieFilter = function (rules) { 26 | const cookieWhiteFilter = new api.UrlFilterRuleLookupTable(); 27 | const cookieBlockFilter = new api.UrlFilterRuleLookupTable(); 28 | 29 | /** 30 | * Add rule to filter 31 | * @param rule Rule object 32 | */ 33 | function addRule(rule) { 34 | if (rule.whiteListRule) { 35 | cookieWhiteFilter.addRule(rule); 36 | } else { 37 | cookieBlockFilter.addRule(rule); 38 | } 39 | } 40 | 41 | /** 42 | * Add rules to filter 43 | * @param rules Collection of rules 44 | */ 45 | function addRules(rules) { 46 | for (let i = 0; i < rules.length; i += 1) { 47 | addRule(rules[i]); 48 | } 49 | } 50 | 51 | /** 52 | * Removes from filter 53 | * @param rule Rule to remove 54 | */ 55 | function removeRule(rule) { 56 | if (rule.whiteListRule) { 57 | cookieWhiteFilter.removeRule(rule); 58 | } else { 59 | cookieBlockFilter.removeRule(rule); 60 | } 61 | } 62 | 63 | /** 64 | * All rules in filter 65 | * 66 | * @returns {*|Array.|string|Buffer} 67 | */ 68 | function getRules() { 69 | const rules = cookieWhiteFilter.getRules(); 70 | return rules.concat(cookieBlockFilter.getRules()); 71 | } 72 | 73 | /** 74 | * Finds exception rule for blocking rule 75 | * 76 | * @param {object} blockRule Blocking rule 77 | * @param {Array} whiteListRules Whitelist rules 78 | * @return {object} Found whitelist rule or null 79 | */ 80 | function findWhiteListRule(blockRule, whiteListRules) { 81 | for (let i = 0; i < whiteListRules.length; i += 1) { 82 | const whiteRule = whiteListRules[i]; 83 | const whiteCookieOption = whiteRule.getCookieOption(); 84 | 85 | const blockCookieOption = blockRule.getCookieOption(); 86 | const blockCookieName = blockCookieOption.cookieName; 87 | const blockCookieRegex = blockCookieOption.regex; 88 | 89 | // Matches by cookie name 90 | if (whiteCookieOption.matches(blockCookieName)) { 91 | return whiteRule; 92 | } 93 | 94 | // Rules have the same regex 95 | if (blockCookieRegex && whiteCookieOption.regex 96 | && String(blockCookieRegex) === String(whiteCookieOption.regex)) { 97 | return whiteRule; 98 | } 99 | 100 | // Blocking rule with empty $cookie option value will be unblocked by @@$cookie rule 101 | } 102 | 103 | return null; 104 | } 105 | 106 | /** 107 | * Searches for rules matching specified request. 108 | * 109 | * @param url URL 110 | * @param documentHost Document Host 111 | * @param thirdParty true if request is third-party 112 | * @param requestType Request content type 113 | * @returns Matching rules 114 | */ 115 | function findCookieRules(url, documentHost, thirdParty, requestType) { 116 | const blockRules = cookieBlockFilter.findRules(url, documentHost, thirdParty, requestType); 117 | if (!blockRules || blockRules.length === 0) { 118 | return null; 119 | } 120 | 121 | const whiteRules = cookieWhiteFilter.findRules(url, documentHost, thirdParty, requestType); 122 | if (!whiteRules || whiteRules.length === 0) { 123 | return blockRules; 124 | } 125 | 126 | // Try to find whitelist rule with empty $cookie option => unblock all 127 | const commonWhiteRule = whiteRules.filter(r => r.getCookieOption().isEmpty())[0]; 128 | if (commonWhiteRule) { 129 | return [commonWhiteRule]; 130 | } 131 | 132 | const rulesToApply = blockRules.map((blockRule) => { 133 | const whiteRule = findWhiteListRule(blockRule, whiteRules); 134 | return whiteRule || blockRule; 135 | }); 136 | return rulesToApply.length > 0 ? rulesToApply : null; 137 | } 138 | 139 | if (rules) { 140 | addRules(rules); 141 | } 142 | 143 | return { 144 | addRules, 145 | addRule, 146 | removeRule, 147 | getRules, 148 | findCookieRules, 149 | }; 150 | }; 151 | })(adguard, adguard.rules); 152 | -------------------------------------------------------------------------------- /src/adguard/lib/filter/rules/domains-lookup-table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | (function (adguard, api) { 19 | 20 | 'use strict'; 21 | 22 | /** 23 | * Special lookup table, which improves basic rules search speed by domain. 24 | */ 25 | var DomainsLookupTable = function (rules) { 26 | 27 | this.lookupTable = Object.create(null); 28 | 29 | if (rules) { 30 | for (var i = 0; i < rules.length; i++) { 31 | this.addRule(rules[i]); 32 | } 33 | } 34 | }; 35 | 36 | DomainsLookupTable.prototype = { 37 | 38 | /** 39 | * Adds specified rule to the lookup table (if it is possible). 40 | * If rule has no domain restriction, this method returns false. 41 | * 42 | * @param rule Url filter rule 43 | * @return boolean true if rule was added. Otherwise - false. 44 | */ 45 | addRule: function (rule) { 46 | if (!rule.hasPermittedDomains()) { 47 | // No permitted domains, we can't do anything 48 | return false; 49 | } 50 | 51 | var permittedDomains = rule.getPermittedDomains(); 52 | for (var i = 0; i < permittedDomains.length; i++) { 53 | var domainName = permittedDomains[i]; 54 | var rules = this.lookupTable[domainName]; 55 | if (!rules) { 56 | rules = []; 57 | this.lookupTable[domainName] = rules; 58 | } 59 | 60 | rules.push(rule); 61 | } 62 | 63 | return true; 64 | }, 65 | 66 | /** 67 | * Removes specified rule from the lookup table 68 | * 69 | * @param rule Rule to remove 70 | */ 71 | removeRule: function (rule) { 72 | 73 | if (!rule.hasPermittedDomains()) { 74 | // No permitted domains, we can't do anything 75 | return; 76 | } 77 | 78 | var permittedDomains = rule.getPermittedDomains(); 79 | for (var i = 0; i < permittedDomains.length; i++) { 80 | var domainName = permittedDomains[i]; 81 | var rules = this.lookupTable[domainName]; 82 | if (rules) { 83 | adguard.utils.collections.removeRule(rules, rule); 84 | if (rules.length === 0) { 85 | delete this.lookupTable[domainName]; 86 | } 87 | } 88 | } 89 | }, 90 | 91 | /** 92 | * Clears lookup table 93 | */ 94 | clearRules: function () { 95 | this.lookupTable = Object.create(null); 96 | }, 97 | 98 | /** 99 | * Searches for filter rules restricted to the specified domain 100 | * 101 | * @param domainName Domain name 102 | * @return List of filter rules or null if nothing found 103 | */ 104 | lookupRules: function (domainName) { 105 | if (!domainName) { 106 | return null; 107 | } 108 | 109 | let parts = domainName.split('.'); 110 | if (parts.length === 0) { 111 | return null; 112 | } 113 | 114 | // Resulting list of rules 115 | let result = null; 116 | 117 | // Iterate over all sub-domains 118 | let host = parts[parts.length - 1]; 119 | for (let i = parts.length - 2; i >= 0; i--) { 120 | host = parts[i] + "." + host; 121 | let rules = this.lookupTable[host]; 122 | if (rules && rules.length > 0) { 123 | if (result === null) { 124 | // Lazy initialization of the resulting list 125 | result = []; 126 | } 127 | result = result.concat(rules); 128 | } 129 | } 130 | 131 | return result; 132 | }, 133 | 134 | /** 135 | * @returns {Array} rules in lookup table 136 | */ 137 | getRules: function () { 138 | var result = []; 139 | for (var r in this.lookupTable) { // jshint ignore:line 140 | var value = this.lookupTable[r]; 141 | if (value) { 142 | if (adguard.utils.collections.isArray(value)) { 143 | result = result.concat(value); 144 | } else { 145 | result.push(value); 146 | } 147 | } 148 | } 149 | 150 | return adguard.utils.collections.removeDuplicates(result); 151 | } 152 | }; 153 | 154 | api.DomainsLookupTable = DomainsLookupTable; 155 | 156 | })(adguard, adguard.rules); 157 | 158 | -------------------------------------------------------------------------------- /src/adguard/lib/filter/rules/redirect-filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | /* global adguard, Redirects */ 19 | 20 | (function (adguard, api) { 21 | 'use strict'; 22 | 23 | let redirects; 24 | 25 | api.RedirectFilterService = (function RedirectFilterService() { 26 | function setRedirectSources(rawYaml) { 27 | redirects = new Redirects(rawYaml); 28 | } 29 | 30 | function buildRedirectUrl(title) { 31 | if (!title) { 32 | return null; 33 | } 34 | 35 | const redirectSource = redirects.getRedirect(title); 36 | if (!redirectSource) { 37 | adguard.console.debug(`There is no redirect source with title: "${title}"`); 38 | return null; 39 | } 40 | let { content, contentType } = redirectSource; 41 | // if contentType does not include "base64" string we convert it to base64 42 | const BASE_64 = 'base64'; 43 | if (!contentType.includes(BASE_64)) { 44 | content = window.btoa(content); 45 | contentType = `${contentType};${BASE_64}`; 46 | } 47 | 48 | return `data:${contentType},${content}`; 49 | } 50 | 51 | function hasRedirect(title) { 52 | return !!redirects.getRedirect(title); 53 | } 54 | 55 | return { 56 | setRedirectSources, 57 | hasRedirect, 58 | buildRedirectUrl, 59 | }; 60 | })(); 61 | })(adguard, adguard.rules); 62 | -------------------------------------------------------------------------------- /src/adguard/lib/filter/rules/replace-filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | (function (adguard, api) { 19 | /** 20 | * Filter for replace filter rules 21 | * @param rules 22 | * @param badFilterRules 23 | * @constructor 24 | */ 25 | api.ReplaceFilter = function (rules, badFilterRules) { 26 | const replaceWhiteFilter = new api.UrlFilterRuleLookupTable(); 27 | const replaceBlockFilter = new api.UrlFilterRuleLookupTable(); 28 | 29 | /** 30 | * Add rule to replace filter 31 | * @param rule Rule object 32 | */ 33 | function addRule(rule) { 34 | if (rule.whiteListRule) { 35 | replaceWhiteFilter.addRule(rule); 36 | } else { 37 | replaceBlockFilter.addRule(rule); 38 | } 39 | } 40 | 41 | /** 42 | * Add rules to replace filter 43 | * @param rules Array of rules 44 | */ 45 | function addRules(rules) { 46 | for (let i = 0; i < rules.length; i += 1) { 47 | const rule = rules[i]; 48 | addRule(rule); 49 | } 50 | } 51 | 52 | /** 53 | * Remove rule from replace filter 54 | * @param rule Rule object 55 | */ 56 | function removeRule(rule) { 57 | if (rule.whiteListRule) { 58 | replaceWhiteFilter.removeRule(rule); 59 | } else { 60 | replaceWhiteFilter.removeRule(rule); 61 | } 62 | } 63 | 64 | /** 65 | * Returns rules from replace filter 66 | * @returns {Array} array of rules 67 | */ 68 | function getRules() { 69 | const whiteRules = replaceWhiteFilter.getRules(); 70 | const blockRules = replaceBlockFilter.getRules(); 71 | return whiteRules.concat(blockRules); 72 | } 73 | 74 | /** 75 | * Returns suitable white rule from the list of rules 76 | * @param whiteRules list of white rules 77 | * @param blockRule block rule 78 | * @returns {?object} suitable whiteRule or null 79 | */ 80 | const getWhitelistingRule = (whiteRules, blockRule) => { 81 | for (let i = 0; i < whiteRules.length; i += 1) { 82 | const whiteRule = whiteRules[i]; 83 | if (whiteRule.replaceOption.optionText === blockRule.replaceOption.optionText) { 84 | return whiteRule; 85 | } 86 | } 87 | return null; 88 | }; 89 | 90 | /** 91 | * Function returns filtered replace block rules 92 | * @param url 93 | * @param documentHost 94 | * @param thirdParty 95 | * @param requestType 96 | * @returns {?Array} array of filtered replace blockRules or null 97 | */ 98 | function findReplaceRules(url, documentHost, thirdParty, requestType) { 99 | const blockRules = replaceBlockFilter.findRules(url, documentHost, thirdParty, requestType, badFilterRules); 100 | 101 | if (!blockRules) { 102 | return null; 103 | } 104 | 105 | const whiteRules = replaceWhiteFilter.findRules(url, documentHost, thirdParty, requestType, badFilterRules); 106 | if (!whiteRules) { 107 | return blockRules; 108 | } 109 | 110 | if (whiteRules.length > 0) { 111 | const whiteRulesWithEmptyOptionText = whiteRules.filter(whiteRule => whiteRule.replaceOption.optionText === ''); 112 | 113 | // @@||example.org^$replace will disable all $replace rules matching ||example.org^. 114 | if (whiteRulesWithEmptyOptionText.length > 0) { 115 | // return first matched rule 116 | return whiteRulesWithEmptyOptionText.slice(0, 1); 117 | } 118 | 119 | const foundReplaceRules = []; 120 | blockRules.forEach((blockRule) => { 121 | const whitelistingRule = getWhitelistingRule(whiteRules, blockRule); 122 | if (whitelistingRule) { 123 | foundReplaceRules.push(whitelistingRule); 124 | } else { 125 | foundReplaceRules.push(blockRule); 126 | } 127 | }); 128 | return foundReplaceRules; 129 | } 130 | 131 | return blockRules.length > 0 ? blockRules : null; 132 | } 133 | 134 | if (rules) { 135 | addRules(rules); 136 | } 137 | 138 | return { 139 | addRules: addRules, 140 | addRule: addRule, 141 | removeRule: removeRule, 142 | getRules: getRules, 143 | findReplaceRules: findReplaceRules, 144 | }; 145 | }; 146 | })(adguard, adguard.rules); 147 | -------------------------------------------------------------------------------- /src/adguard/lib/filter/rules/rules.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | /** 19 | * Namespace for adguard rules classes and utils 20 | */ 21 | adguard.rules = (function () { 22 | 'use strict'; 23 | 24 | return {}; 25 | })(); 26 | -------------------------------------------------------------------------------- /src/adguard/lib/filter/rules/script-filter-rule.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | (function (adguard, api) { 19 | 'use strict'; 20 | 21 | /** 22 | * By the rules of AMO and addons.opera.com we cannot use remote scripts 23 | * (and our JS injection rules could be considered as remote scripts). 24 | * 25 | * So, what we do: 26 | * 1. Pre-compile all current JS rules to the add-on and mark them as 'local'. 27 | * Other JS rules (new not pre-compiled) are marked as 'remote'. 28 | * 2. Also we mark as 'local' rules from the "User Filter" (local filter which user can edit) 29 | * 3. In case of Firefox and Opera we apply only 'local' 30 | * JS rules and ignore all marked as 'remote' 31 | * Note: LocalScriptRulesService may be undefined, in this case, we mark all rules as remote. 32 | */ 33 | function getScriptSource(filterId, ruleText) { 34 | return filterId === adguard.utils.filters.USER_FILTER_ID 35 | || (api.LocalScriptRulesService && api.LocalScriptRulesService.isLocal(ruleText)) 36 | ? 'local' 37 | : 'remote'; 38 | } 39 | 40 | /** 41 | * JS injection rule: 42 | * http://adguard.com/en/filterrules.html#javascriptInjection 43 | */ 44 | const ScriptFilterRule = function (rule, filterId) { 45 | api.FilterRule.call(this, rule, filterId); 46 | 47 | this.script = null; 48 | this.whiteListRule = adguard.utils.strings.contains( 49 | rule, 50 | api.FilterRule.MASK_SCRIPT_EXCEPTION_RULE 51 | ); 52 | const mask = this.whiteListRule 53 | ? api.FilterRule.MASK_SCRIPT_EXCEPTION_RULE 54 | : api.FilterRule.MASK_SCRIPT_RULE; 55 | 56 | const indexOfMask = rule.indexOf(mask); 57 | if (indexOfMask > 0) { 58 | // domains are specified, parsing 59 | const domains = rule.substring(0, indexOfMask); 60 | this.loadDomains(domains); 61 | } 62 | 63 | this.script = rule.substring(indexOfMask + mask.length); 64 | 65 | this.scriptSource = getScriptSource(filterId, rule); 66 | }; 67 | 68 | function getScript() { 69 | return this.script; 70 | } 71 | 72 | /** 73 | * returns rule content after mask 74 | * e.g. example.org#%#window.AG_onLoad = function(func) {} -> 75 | * -> #%#window.AG_onLoad = function(func) {} 76 | * @return {string} 77 | */ 78 | function getRuleContent() { 79 | return this.script; 80 | } 81 | 82 | ScriptFilterRule.prototype = Object.create(api.FilterRule.prototype); 83 | 84 | ScriptFilterRule.prototype.getScript = getScript; 85 | 86 | ScriptFilterRule.prototype.getRuleContent = getRuleContent; 87 | 88 | /** 89 | * All content rules markers start with this character 90 | */ 91 | ScriptFilterRule.RULE_MARKER_FIRST_CHAR = '#'; 92 | 93 | /** 94 | * Content rule markers 95 | */ 96 | ScriptFilterRule.RULE_MARKERS = [ 97 | api.FilterRule.MASK_SCRIPT_EXCEPTION_RULE, 98 | api.FilterRule.MASK_SCRIPT_RULE, 99 | ]; 100 | 101 | api.ScriptFilterRule = ScriptFilterRule; 102 | })(adguard, adguard.rules); 103 | -------------------------------------------------------------------------------- /src/adguard/lib/filter/rules/url-filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | (function (api) { 19 | 'use strict'; 20 | 21 | /** 22 | * Filter for Url filter rules. 23 | * Read here for details: 24 | * http://adguard.com/en/filterrules.html#baseRules 25 | */ 26 | const UrlFilter = function (rules, badFilterRules) { 27 | this.basicRulesTable = new api.UrlFilterRuleLookupTable(); 28 | this.badFilterRules = badFilterRules; 29 | 30 | if (rules) { 31 | for (let i = 0; i < rules.length; i += 1) { 32 | this.addRule(rules[i]); 33 | } 34 | } 35 | }; 36 | 37 | UrlFilter.prototype = { 38 | 39 | /** 40 | * Adds rule to UrlFilter 41 | * 42 | * @param rule Rule object 43 | */ 44 | addRule(rule) { 45 | this.basicRulesTable.addRule(rule); 46 | }, 47 | 48 | /** 49 | * Removes rule from UrlFilter 50 | * 51 | * @param rule Rule to remove 52 | */ 53 | removeRule(rule) { 54 | this.basicRulesTable.removeRule(rule); 55 | }, 56 | 57 | /** 58 | * Searches for first rule matching specified request 59 | * 60 | * @param url Request url 61 | * @param documentHost Document host 62 | * @param requestType Request content type (UrlFilterRule.contentTypes) 63 | * @param thirdParty true if request is third-party 64 | * @param skipGenericRules skip generic rules 65 | * @return matching rule or null if no match found 66 | */ 67 | isFiltered(url, documentHost, requestType, thirdParty, skipGenericRules) { 68 | return this.basicRulesTable.findRule(url, 69 | documentHost, 70 | thirdParty, 71 | requestType, 72 | !skipGenericRules, 73 | this.badFilterRules); 74 | }, 75 | 76 | /** 77 | * Returns the array of loaded rules 78 | */ 79 | getRules() { 80 | return this.basicRulesTable.getRules(); 81 | }, 82 | }; 83 | 84 | api.UrlFilter = UrlFilter; 85 | })(adguard.rules); 86 | -------------------------------------------------------------------------------- /src/adguard/lib/filter/userrules.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | /** 19 | * Class for manage user rules 20 | */ 21 | adguard.userrules = (function (adguard) { 22 | 'use strict'; 23 | 24 | /** 25 | * Wraps access to getter. AntiBannerService hasn't been defined yet. 26 | * @returns {*} 27 | */ 28 | function getAntiBannerService() { 29 | return adguard.antiBannerService; 30 | } 31 | 32 | /** 33 | * Adds list of rules to the user filter 34 | * 35 | * @param rulesText List of rules to add 36 | * @param options 37 | */ 38 | const addRules = function (rulesText, options) { 39 | const rules = getAntiBannerService().addUserFilterRules(rulesText); 40 | adguard.listeners.notifyListeners(adguard.listeners.SYNC_REQUIRED, options); 41 | return rules; 42 | }; 43 | 44 | /** 45 | * Removes all user's custom rules 46 | */ 47 | const clearRules = function (options) { 48 | getAntiBannerService().updateUserFilterRules([]); 49 | adguard.listeners.notifyListeners(adguard.listeners.SYNC_REQUIRED, options); 50 | }; 51 | 52 | /** 53 | * Removes user's custom rule 54 | * 55 | * @param ruleText Rule text 56 | */ 57 | const removeRule = function (ruleText) { 58 | getAntiBannerService().removeUserFilterRule(ruleText); 59 | adguard.listeners.notifyListeners(adguard.listeners.SYNC_REQUIRED); 60 | }; 61 | 62 | /** 63 | * Save user rules text to storage 64 | * @param content Rules text 65 | * @param options 66 | */ 67 | const updateUserRulesText = function (content, options) { 68 | const lines = content.length > 0 ? content.split(/\n/) : []; 69 | getAntiBannerService().updateUserFilterRules(lines); 70 | adguard.listeners.notifyListeners(adguard.listeners.SYNC_REQUIRED, options); 71 | }; 72 | 73 | /** 74 | * Loads user rules text from storage 75 | * @param callback Callback function 76 | */ 77 | const getUserRulesText = function (callback) { 78 | adguard.rulesStorage.read(adguard.utils.filters.USER_FILTER_ID, (rulesText) => { 79 | const content = (rulesText || []).join('\n'); 80 | callback(content); 81 | }); 82 | }; 83 | 84 | const unWhiteListFrame = function (frameInfo) { 85 | if (frameInfo.frameRule) { 86 | if (frameInfo.frameRule.filterId === adguard.utils.filters.WHITE_LIST_FILTER_ID) { 87 | adguard.whitelist.unWhiteListUrl(frameInfo.url); 88 | } else { 89 | removeRule(frameInfo.frameRule.ruleText); 90 | } 91 | } 92 | }; 93 | 94 | return { 95 | addRules, 96 | clearRules, 97 | removeRule, 98 | updateUserRulesText, 99 | getUserRulesText, 100 | // TODO: fix 101 | unWhiteListFrame, 102 | }; 103 | })(adguard); 104 | -------------------------------------------------------------------------------- /src/adguard/lib/storage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | /** 19 | * localStorage interface. Implementation depends on browser 20 | */ 21 | adguard.localStorageImpl = adguard.localStorageImpl || (function () { 22 | function notImplemented() { 23 | throw new Error('Not implemented'); 24 | } 25 | 26 | return { 27 | getItem: notImplemented, 28 | setItem: notImplemented, 29 | removeItem: notImplemented, 30 | hasItem: notImplemented, 31 | }; 32 | })(); 33 | 34 | /** 35 | * This class manages local storage 36 | */ 37 | adguard.localStorage = (function (adguard, impl) { 38 | const getItem = function (key) { 39 | return impl.getItem(key); 40 | }; 41 | 42 | const setItem = function (key, value) { 43 | try { 44 | impl.setItem(key, value); 45 | } catch (ex) { 46 | adguard.console.error(`Error while saving item ${key} to the localStorage: ${ex}`); 47 | } 48 | }; 49 | 50 | const removeItem = function (key) { 51 | impl.removeItem(key); 52 | }; 53 | 54 | const hasItem = function (key) { 55 | return impl.hasItem(key); 56 | }; 57 | 58 | const init = function (callback) { 59 | if (typeof impl.init === 'function') { 60 | impl.init(callback); 61 | } else { 62 | callback(); 63 | } 64 | }; 65 | 66 | const isInitialized = function () { 67 | // WebExtension storage has async initialization 68 | if (typeof impl.isInitialized === 'function') { 69 | return impl.isInitialized(); 70 | } 71 | return true; 72 | }; 73 | 74 | return { 75 | getItem, 76 | setItem, 77 | removeItem, 78 | hasItem, 79 | init, 80 | isInitialized, 81 | }; 82 | })(adguard, adguard.localStorageImpl); 83 | 84 | /** 85 | * Rules storage interface. Implementation depends on browser 86 | */ 87 | adguard.rulesStorageImpl = adguard.rulesStorageImpl || (function () { 88 | function notImplemented() { 89 | throw new Error('Not implemented'); 90 | } 91 | 92 | return { 93 | read: notImplemented, 94 | write: notImplemented, 95 | }; 96 | })(); 97 | 98 | /** 99 | * This class manages storage for filters. 100 | */ 101 | adguard.rulesStorage = (function (adguard, impl) { 102 | function getFilePath(filterId) { 103 | return `filterrules_${filterId}.txt`; 104 | } 105 | 106 | /** 107 | * Loads filter from the storage 108 | * 109 | * @param filterId Filter identifier 110 | * @param callback Called when file content has been loaded 111 | */ 112 | const read = function (filterId, callback) { 113 | const filePath = getFilePath(filterId); 114 | impl.read(filePath, (e, rules) => { 115 | if (e) { 116 | adguard.console.error(`Error while reading rules from file ${filePath} cause: ${e}`); 117 | } 118 | callback(rules); 119 | }); 120 | }; 121 | 122 | /** 123 | * Saves filter rules to storage 124 | * 125 | * @param filterId Filter identifier 126 | * @param filterRules Filter rules 127 | * @param callback Called when save operation is finished 128 | */ 129 | const write = function (filterId, filterRules, callback) { 130 | const filePath = getFilePath(filterId); 131 | impl.write(filePath, filterRules, (e) => { 132 | if (e) { 133 | adguard.console.error(`Error writing filters to file ${filePath}. Cause: ${e}`); 134 | } 135 | callback(); 136 | }); 137 | }; 138 | 139 | /** 140 | * IndexedDB implementation of the rules storage requires async initialization. 141 | * Also in some cases IndexedDB isn't supported, so we have to replace implementation 142 | * with the browser.storage 143 | * 144 | * @param callback 145 | */ 146 | const init = function (callback) { 147 | if (typeof impl.init === 'function') { 148 | impl.init((api) => { 149 | impl = api; 150 | callback(); 151 | }); 152 | } else { 153 | callback(); 154 | } 155 | }; 156 | 157 | return { 158 | read, 159 | write, 160 | init, 161 | }; 162 | })(adguard, adguard.rulesStorageImpl); 163 | -------------------------------------------------------------------------------- /src/adguard/lib/utils/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of Adguard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension). 3 | * 4 | * Adguard Browser Extension is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Adguard Browser Extension is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Adguard Browser Extension. If not, see . 16 | */ 17 | 18 | /* eslint-disable no-console */ 19 | 20 | /** 21 | * Simple logger with log levels 22 | */ 23 | adguard.console = (function () { 24 | // Redefine if you need it 25 | const CURRENT_LEVEL = 'INFO'; 26 | 27 | const LEVELS = { 28 | ERROR: 1, 29 | WARN: 2, 30 | INFO: 3, 31 | DEBUG: 4, 32 | }; 33 | 34 | /** 35 | * Pretty-print javascript error 36 | */ 37 | const errorToString = function (error) { 38 | return `${error.toString()}\nStack trace:\n${error.stack}`; 39 | }; 40 | 41 | const getLocalTimeString = (date) => { 42 | const ONE_MINUTE_MS = 60 * 1000; 43 | const timeZoneOffsetMs = date.getTimezoneOffset() * ONE_MINUTE_MS; 44 | const localTime = new Date(date - timeZoneOffsetMs); 45 | return localTime.toISOString().replace('Z', ''); 46 | }; 47 | 48 | /** 49 | * Prints log message 50 | */ 51 | const print = function (level, method, args) { 52 | // check log level 53 | if (LEVELS[CURRENT_LEVEL] < LEVELS[level]) { 54 | return; 55 | } 56 | if (!args || args.length === 0 || !args[0]) { 57 | return; 58 | } 59 | 60 | const str = `${args[0]}`; 61 | args = Array.prototype.slice.call(args, 1); 62 | let formatted = str.replace(/{(\d+)}/g, (match, number) => { 63 | if (typeof args[number] !== 'undefined') { 64 | let value = args[number]; 65 | if (value instanceof Error) { 66 | value = errorToString(value); 67 | } else if (value && value.message) { 68 | value = value.message; 69 | } else if (typeof value === 'object') { 70 | value = JSON.stringify(value); 71 | } 72 | return value; 73 | } 74 | 75 | return match; 76 | }); 77 | 78 | formatted = `${getLocalTimeString(new Date())}: ${formatted}`; 79 | console[method](formatted); 80 | }; 81 | 82 | /** 83 | * Expose public API 84 | */ 85 | return { 86 | debug(...args) { 87 | print('DEBUG', 'log', args); 88 | }, 89 | 90 | info(...args) { 91 | print('INFO', 'info', args); 92 | }, 93 | 94 | warn(...args) { 95 | print('WARN', 'info', args); 96 | }, 97 | 98 | error(...args) { 99 | print('ERROR', 'error', args); 100 | }, 101 | }; 102 | })(); 103 | -------------------------------------------------------------------------------- /src/background/background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/background/bookmark.ts: -------------------------------------------------------------------------------- 1 | import message from '../core/message' 2 | import * as logger from '../core/logger' 3 | 4 | function search(text: any, sender: any, sendResponse: (something: any) => void) { 5 | try { 6 | chrome.bookmarks.search(text.text, (results) => { 7 | sendResponse(results) 8 | }) 9 | } catch (e) { 10 | /* nothing here */ 11 | } 12 | } 13 | 14 | message.addListener({ 15 | type: 'bookmark-search', 16 | callback: search, 17 | }) 18 | 19 | chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { 20 | message.sendTab(tabId, 'load-complete').then((result: any) => { 21 | // catch last error of messaging 22 | if (chrome.runtime.lastError) { 23 | logger.debug(chrome.runtime.lastError.message); 24 | } 25 | return result 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /src/background/browserIcon.ts: -------------------------------------------------------------------------------- 1 | import * as browserIconChange from '../core/browserIconChange' 2 | import * as grade from '../core/grade' 3 | import * as notification from '../core/notification' 4 | 5 | export async function changeBrowserIcon(result: grade.Result) { 6 | const level = result.classify() 7 | if (result.safe === false) { 8 | browserIconChange.danger() 9 | } else if (level === 3 || level === 5) { 10 | browserIconChange.gray() 11 | } else if (level === 2) { 12 | browserIconChange.normal() 13 | } else { 14 | browserIconChange.green() 15 | } 16 | } 17 | 18 | export function start() { 19 | chrome.tabs.onActivated.addListener((activeInfo) => { 20 | chrome.tabs.query({ active: true, currentWindow: true }, async (tabArray) => { 21 | if (!tabArray) { 22 | return 23 | } 24 | const result = await grade.website(tabArray.last().url) 25 | changeBrowserIcon(result) 26 | }) 27 | }) 28 | 29 | chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { 30 | if (changeInfo.status !== 'loading') { 31 | chrome.tabs.query({ active: true, currentWindow: true }, async (tabArray) => { 32 | if (!tabArray) { 33 | return 34 | } 35 | const result = await grade.website(tabArray.last().url) 36 | changeBrowserIcon(result) 37 | notification.labSafeTipsNoty(result.type) 38 | }) 39 | } 40 | }) 41 | } 42 | 43 | start() 44 | -------------------------------------------------------------------------------- /src/background/contextMenus.ts: -------------------------------------------------------------------------------- 1 | import * as i18n from '../core/i18n' 2 | import * as coreConfig from '../core/config' 3 | import * as notification from '../core/notification' 4 | import * as tabOp from '../core/tab' 5 | import * as utils from '../core/utils' 6 | import * as logger from '../core/logger' 7 | 8 | chrome.contextMenus.create({ 9 | id: 'cloudopt', 10 | title: 'Cloudopt', 11 | contexts: ['all'], 12 | onclick: () => undefined, 13 | }) 14 | 15 | chrome.contextMenus.create({ 16 | title: i18n.get('contextMenus1'), 17 | parentId: 'cloudopt', 18 | contexts: ['all'], 19 | onclick: async (info, tab) => { 20 | const result = await coreConfig.fastAddAllowList(tab.url) 21 | if (result === coreConfig.AddListResult.SUCCESS) { 22 | notification.success(i18n.get('optionTipsAddAllowListSuccess')) 23 | } else { 24 | coreConfig.fireAddListErrorNotification(result) 25 | } 26 | }, 27 | }) 28 | 29 | chrome.contextMenus.create({ 30 | title: i18n.get('contextMenus4'), 31 | parentId: 'cloudopt', 32 | contexts: ['all'], 33 | onclick: async (info, tab) => { 34 | const result = await coreConfig.fastAddBlockList(tab.url) 35 | if (result === coreConfig.AddListResult.SUCCESS) { 36 | notification.success(i18n.get('optionTipsAddBlockListSuccess')) 37 | } else { 38 | coreConfig.fireAddListErrorNotification(result) 39 | } 40 | }, 41 | }) 42 | 43 | chrome.contextMenus.create({ 44 | title: i18n.get('contextMenus2'), 45 | parentId: 'cloudopt', 46 | contexts: ['all'], 47 | onclick: (info, tab) => { 48 | if (tab.url.startsWith('http://') || tab.url.startsWith('https://')) { 49 | tabOp.open(`https://www.cloudopt.net/report/${utils.getHost(tab.url)}`) 50 | } 51 | }, 52 | }) 53 | 54 | chrome.contextMenus.create({ 55 | title: i18n.get('contextMenus3'), 56 | parentId: 'cloudopt', 57 | contexts: ['all'], 58 | onclick: (info, tab) => { 59 | logger.debug(`Opening Assistant UI for tab id=${tab.id} url= ${tab.url}`) 60 | const backgroundPage = chrome.extension.getBackgroundPage() 61 | const adguardApi = backgroundPage.adguardApi 62 | adguardApi.openAssistant(tab.id) 63 | }, 64 | }) 65 | -------------------------------------------------------------------------------- /src/background/defaultAllowAds.cn.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "host": "v.youku.com" 4 | }, 5 | { 6 | "host": "www.iqiyi.com" 7 | }, 8 | { 9 | "host": "v.qq.com" 10 | }, 11 | { 12 | "host": "video.tudou.com" 13 | }, 14 | { 15 | "host": "video.sina.com.cn" 16 | }, 17 | { 18 | "host": "v.ifeng.com" 19 | }, 20 | { 21 | "host": "tv.sohu.com" 22 | }, 23 | { 24 | "host": "v.pptv.com" 25 | }, 26 | { 27 | "host": "www.kankan.com" 28 | }, 29 | { 30 | "host": "www.cctv.com" 31 | }, 32 | { 33 | "host": "www.pps.tv" 34 | }, 35 | { 36 | "host": "www.mgtv.com" 37 | }, 38 | { 39 | "host": "www.wasu.cn" 40 | }, 41 | { 42 | "host":"www.le.com" 43 | }, 44 | { 45 | "host":"tv.le.com" 46 | }, 47 | { 48 | "host":"www.ku6.com" 49 | }, 50 | { 51 | "host":"www.acfun.cn" 52 | }, 53 | { 54 | "host":"www.bilibili.com" 55 | }, 56 | { 57 | "host":"www.pearvideo.com" 58 | }, 59 | { 60 | "host":"tv.cctv.com" 61 | }, 62 | { 63 | "host":"www.cctv.cn" 64 | }, 65 | { 66 | "host":"tv.people.com.cn" 67 | }, 68 | { 69 | "host":"www.news.cn" 70 | } 71 | ] 72 | -------------------------------------------------------------------------------- /src/background/dnt.ts: -------------------------------------------------------------------------------- 1 | import * as coreConfig from '../core/config' 2 | import * as logger from '../core/logger' 3 | 4 | const DNT_HEADER = {name: 'dnt', value: '1'} 5 | let dntEnabled: boolean = true 6 | let listenerAdded: boolean = false 7 | 8 | export async function start() { 9 | const config = await coreConfig.get() 10 | dntEnabled = config.safePrivacy 11 | /* tslint:disable */ 12 | if (chrome.privacy && chrome.privacy.websites['doNotTrackEnabled']) { 13 | chrome.privacy.websites['doNotTrackEnabled'].get({}, (details) => { 14 | if (details.levelOfControl === 'controllable_by_this_extension') { 15 | chrome.privacy.websites['doNotTrackEnabled'].set({value: dntEnabled}, () => { 16 | if (chrome.runtime.lastError) { 17 | logger.error(chrome.runtime.lastError.message) 18 | } 19 | }) 20 | } 21 | }) 22 | } else if (!listenerAdded) { 23 | chrome.webRequest.onBeforeSendHeaders.addListener((details) => { 24 | if (dntEnabled && !listenerAdded) { 25 | details.requestHeaders.push(DNT_HEADER) 26 | } 27 | }, 28 | {urls: ['']}, 29 | ['blocking', 'requestHeaders']) 30 | listenerAdded = true 31 | } 32 | /* tslint:enable */ 33 | } 34 | 35 | start() 36 | -------------------------------------------------------------------------------- /src/background/heuristics.ts: -------------------------------------------------------------------------------- 1 | import * as coreConfig from '../core/config' 2 | import message from '../core/message' 3 | import * as http from '../core/http' 4 | import * as logger from '../core/logger' 5 | import { Baize } from '../baize/baize' 6 | import { UrlParse } from '../baize/lib/urlparse' 7 | import { tabsBlockCount, updateBadgeText } from '../background/adguardEngine' 8 | import * as statistics from '../background/statistics' 9 | 10 | let heuristicsEnabled: boolean = false 11 | 12 | let allowList: string[] = [] 13 | 14 | let modelVersion = "1.0.0" 15 | 16 | let modelUrl = `https://cdn.cloudopt.net/baize/${modelVersion}/baize_model.json` 17 | 18 | let baize = new Baize() 19 | 20 | async function refreshConfig(): Promise { 21 | const config = await coreConfig.get() 22 | allowList = config.allowList 23 | heuristicsEnabled = config.safeHeuristics 24 | } 25 | 26 | export async function initialize() { 27 | refreshConfig() 28 | message.addListener({ 29 | type: 'refresh-config', 30 | callback: async (msg, sender, sendResponse) => { 31 | refreshConfig() 32 | sendResponse({}) 33 | return true 34 | }, 35 | }) 36 | 37 | if (chrome.webRequest) { 38 | chrome.webRequest.onBeforeRequest.addListener((details) => { 39 | if (!heuristicsEnabled) { 40 | return 41 | } 42 | let requestUrl = details.url 43 | try { 44 | let urlparse = new UrlParse(requestUrl) 45 | if (requestUrl.startsWith("http") || requestUrl.startsWith("https")) { 46 | if (urlparse.getRootDomain() == "cloudopt.net") { 47 | return 48 | } 49 | if (details.initiator && details.initiator.startsWith("chrome-extension://")) { 50 | return 51 | } 52 | if (allowList.indexOf(new UrlParse(details.initiator).getDomain()) > -1) { 53 | return 54 | } 55 | let requestThirdParty = 1 56 | if (details.initiator && new UrlParse(details.initiator).getDomain() != urlparse.getDomain()) { 57 | requestThirdParty = 3 58 | } 59 | let requestType = details.type 60 | let predictData = baize.preProcessing(requestUrl, requestThirdParty, requestType); 61 | let predictResult = baize.predict(predictData) 62 | if (predictResult > 0) { 63 | logger.info(`BAIZE Blocked: ${requestUrl}`) 64 | tabsBlockCount.get(details.tabId).count = tabsBlockCount.get(details.tabId).count + 1 65 | updateBadgeText(details.tabId) 66 | statistics.countEvent('adblock') 67 | return { cancel: true } 68 | } 69 | } 70 | } catch (error) { 71 | return 72 | } 73 | 74 | 75 | 76 | 77 | }, { urls: [""] }) 78 | } 79 | } 80 | 81 | http.get(modelUrl, { cache: true }).then((data) => { 82 | logger.info(`Download the model file successfully. Baize version: ${modelVersion}`) 83 | baize.load(JSON.stringify(data)) 84 | logger.info(`Loaded the model file successfully.`) 85 | }).then(() => { 86 | initialize() 87 | }) 88 | -------------------------------------------------------------------------------- /src/background/initialize.ts: -------------------------------------------------------------------------------- 1 | import message from '../core/message' 2 | import * as tabOp from '../core/tab' 3 | import * as grade from '../core/grade' 4 | import * as config from '../core/config' 5 | import * as store from '../core/store' 6 | import * as logger from '../core/logger' 7 | import * as utils from '../core/utils' 8 | import { geoIp } from '../core/api' 9 | import IAdblockEngine from '../adblockEngine/adblockerEngine' 10 | import adguardEngine from './adguardEngine' 11 | 12 | let adblockEngine: IAdblockEngine 13 | 14 | async function activateEvent() { 15 | const activate = await store.get('activate') 16 | const today = new Date().getDate() 17 | if (activate === today) { 18 | return 19 | } 20 | utils.sendGA('Daily Activate Units') 21 | store.set('activate', today) 22 | } 23 | 24 | export async function start() { 25 | config.loadFromCloud() 26 | 27 | message.addListener({ 28 | type: 'open-tab', 29 | callback: (msg, sender, sendResponse) => { 30 | tabOp.open(msg.text) 31 | sendResponse('') 32 | }, 33 | }) 34 | 35 | message.addListener({ 36 | type: 'grade-website', 37 | callback: async (msg, sender, sendResponse) => { 38 | sendResponse(await grade.website(msg.text)) 39 | }, 40 | }) 41 | 42 | let location = await store.get('location') 43 | if (location == null) { 44 | try { 45 | const result = await geoIp() 46 | location = result.result.countryCode.toLowerCase() 47 | store.set('location', location) 48 | } catch (error) { 49 | location = 'us' 50 | } 51 | logger.debug(`Init Cloudopt core , you location is ${location} (by online)`) 52 | } else { 53 | logger.debug(`Init Cloudopt core , you location is ${location} (by cache)`) 54 | } 55 | 56 | activateEvent() 57 | 58 | adblockEngine = new adguardEngine() 59 | adblockEngine.refresh() 60 | } 61 | 62 | start() 63 | -------------------------------------------------------------------------------- /src/background/moduleInterface.d.ts: -------------------------------------------------------------------------------- 1 | export interface ICloudoptBackgroundModule { 2 | refresh(param: string) 3 | start(param: string) 4 | stop(param: string): void 5 | } 6 | -------------------------------------------------------------------------------- /src/background/statistics.ts: -------------------------------------------------------------------------------- 1 | import * as store from '../core/store' 2 | import * as logger from '../core/logger' 3 | import message from '../core/message' 4 | import * as utils from '../core/utils' 5 | 6 | const eventCounts: Map = new Map() 7 | 8 | let startOfToday: number 9 | let adblockCountsInDays: Map 10 | let adblocksToday: number = 0 11 | let accelerationCountsInDays: Map 12 | let accelerationsToday: number = 0 13 | 14 | export function countEvent(event: string, count: number = 1) { 15 | if (!eventCounts.has(event)) { 16 | eventCounts.set(event, count) 17 | } else { 18 | eventCounts.set(event, eventCounts.get(event) + count) 19 | } 20 | 21 | switch (event) { 22 | case 'adblock': 23 | adblockCountsInDays.set(startOfToday, ++adblocksToday) 24 | break 25 | case 'web-accelerate': 26 | accelerationCountsInDays.set(startOfToday, ++accelerationsToday) 27 | break 28 | } 29 | 30 | logger.debug(`Statistics: count ${event} ${count} time(s).`) 31 | } 32 | 33 | export function getEventCount(event: string): number { 34 | return eventCounts.has(event) ? eventCounts.get(event) : 0 35 | } 36 | 37 | async function getCountFromStore(event: string): Promise { 38 | let count = await store.get(`statistics.${event}`) 39 | count = parseInt(count, 10) 40 | if (isNaN(count)) { 41 | count = 0 42 | } 43 | return count 44 | } 45 | 46 | function saveAll() { 47 | eventCounts.forEach((value, key) => { 48 | store.set(`statistics.${key}`, value) 49 | }) 50 | store.set('statisticFields', Array.from(eventCounts.keys()).join(',')) 51 | 52 | store.set('adblock-counts-indays', utils.serializeMapNumNum(adblockCountsInDays)) 53 | store.set('acceleration-counts-indays', utils.serializeMapNumNum(accelerationCountsInDays)) 54 | } 55 | 56 | function refreshTodayTimeStamp() { 57 | const now = Date.now() 58 | startOfToday = now - (now % 86400000); 59 | setTimeout(() => { 60 | refreshTodayTimeStamp() 61 | }, startOfToday + 86400000 - now); 62 | } 63 | 64 | export async function start() { 65 | const keys = await store.get('statisticFields') 66 | if (keys) { 67 | keys.split(',').map(async (key) => { 68 | const count = await getCountFromStore(key) 69 | eventCounts.set(key, count) 70 | }) 71 | } 72 | 73 | refreshTodayTimeStamp() 74 | store.get('adblock-counts-indays').then((recordString: string) => { 75 | const oldData = utils.deserializeMapNumNum(recordString) 76 | adblockCountsInDays = new Map() 77 | for (let i = 0, j = startOfToday; i < 7; i++, j -= 86400000) { 78 | if (!isNaN(oldData.get(j))) { 79 | adblockCountsInDays.set(j, oldData.get(j)) 80 | } else { 81 | adblockCountsInDays.set(j, 0) 82 | } 83 | } 84 | message.addListener({ 85 | type: 'getAdblockCountsInDays', 86 | callback(message, sender, sendResponse) { 87 | sendResponse(utils.serializeMapNumNum(adblockCountsInDays)) 88 | } 89 | }) 90 | }) 91 | store.get('acceleration-counts-indays').then((recordString: string) => { 92 | const oldData = utils.deserializeMapNumNum(recordString) 93 | accelerationCountsInDays = new Map() 94 | for (let i = 0, j = startOfToday; i < 7; i++, j -= 86400000) { 95 | if (!isNaN(oldData.get(j))) { 96 | accelerationCountsInDays.set(j, oldData.get(j)) 97 | } else { 98 | accelerationCountsInDays.set(j, 0) 99 | } 100 | } 101 | message.addListener({ 102 | type: 'getAccelerationCountsInDays', 103 | callback(message, sender, sendResponse) { 104 | sendResponse(utils.serializeMapNumNum(accelerationCountsInDays)) 105 | } 106 | }) 107 | }) 108 | 109 | chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { 110 | if (changeInfo.status !== 'loading') { 111 | saveAll() 112 | } 113 | }) 114 | 115 | message.addListener({type: 'countEvent', callback: (msg) => { 116 | countEvent(msg.text) 117 | }}) 118 | 119 | message.addListener({type: 'getEventCount', callback: (msg, sender, sendResponse) => { 120 | const count = getEventCount(msg.text) 121 | sendResponse(count.toString()) 122 | }}) 123 | } 124 | 125 | start() 126 | -------------------------------------------------------------------------------- /src/background/uninstall.ts: -------------------------------------------------------------------------------- 1 | import * as tabs from '../core/tab' 2 | chrome.runtime.onInstalled.addListener((details) => { 3 | tabs.open('/guide.html') 4 | }) 5 | chrome.runtime.setUninstallURL('https://wj.qq.com/s2/3072248/dc7c/') 6 | -------------------------------------------------------------------------------- /src/background/update.ts: -------------------------------------------------------------------------------- 1 | import * as utils from '../core/utils' 2 | import * as store from '../core/store' 3 | import * as i18n from '../core/i18n' 4 | import * as tab from '../core/tab' 5 | import * as coreConfig from '../core/config' 6 | import Push from 'push.js' 7 | import $ from 'jquery' 8 | 9 | async function checkUpdated() { 10 | const currentVersion = utils.getVersion() 11 | let beforeVersion = (await store.get('version')) as string|null 12 | if (beforeVersion == null) { 13 | beforeVersion = '0.0.0' 14 | } 15 | if (currentVersion === beforeVersion) { 16 | return 17 | } 18 | Push.create(i18n.get('updateTipsTitle'), { 19 | body: `${i18n.get('updateTips1')}${utils.getVersion()}${i18n.get('updateTips2')}`, 20 | icon: '/image/icon/default/icon128.png', 21 | timeout: 10000, 22 | }) 23 | utils.sendGA('Update Version') 24 | store.set('version', utils.getVersion()) 25 | $.getJSON('https://cdn.cloudopt.net/extensions/update/updates.json', (data) => { 26 | try { 27 | if (utils.language() === 'zh-CN') { 28 | tab.open(data[utils.getVersion()].cn) 29 | } else { 30 | tab.open(data[utils.getVersion()].us) 31 | } 32 | } catch (error) { 33 | /* nothing here */ 34 | } 35 | }) 36 | const config = await coreConfig.get() 37 | const intBeforeVersion = parseInt(beforeVersion.replace(/[&\|\\\*^%$#@\-,.,。\s]/g, '').substring(0, 3), 10) 38 | if (intBeforeVersion < 110) { 39 | config.customRule = [] 40 | coreConfig.set(config) 41 | } 42 | if (intBeforeVersion < 122) { 43 | config.safePotential = false 44 | coreConfig.set(config) 45 | } 46 | Push.create(i18n.get('adblockInitTitle'), { 47 | body: i18n.get('adblockInitTips'), 48 | icon: '/image/icon/default/icon128.png', 49 | timeout: 10000, 50 | }) 51 | } 52 | 53 | checkUpdated() 54 | -------------------------------------------------------------------------------- /src/content/block.ts: -------------------------------------------------------------------------------- 1 | import * as coreConfig from '../core/config' 2 | import * as utils from '../core/utils' 3 | import $ from 'jquery' 4 | 5 | $.ready.then(() => { 6 | const href = $('.block-page a>.block-pre').parent().attr('href') 7 | $('.block-page a>.block-pre').parent().attr('href', '#') 8 | $('.block-page a>.block-pre').parent().click(async () => { 9 | const config = await coreConfig.get() 10 | const hrefHost = utils.getHost(href) 11 | if (config.allowList.indexOf(hrefHost) < 0) { 12 | config.allowList.push(hrefHost) 13 | config.blockList.removeByValue(hrefHost) 14 | coreConfig.set(config).then(() => { 15 | location.href = href 16 | }) 17 | } else { 18 | location.href = href 19 | } 20 | }) 21 | 22 | const url = $('.block-button').parent().attr('href') 23 | $('.block-button').parent().attr('href', url) 24 | $('.block-button').parent().attr('target', '_blank') 25 | }) 26 | -------------------------------------------------------------------------------- /src/content/bookmark.ts: -------------------------------------------------------------------------------- 1 | import { getHost } from '../core/utils' 2 | import { refresh as refreshConfig } from '../core/config' 3 | import message from '../core/message' 4 | import $ from 'jquery' 5 | import {sanitize} from 'dompurify' 6 | 7 | class BookmarkSearch { 8 | private readonly host: string 9 | private readonly innerClass: string 10 | private getKeyword: () => string 11 | constructor() { 12 | this.host = getHost(window.location.href) 13 | 14 | if (this.host.endsWith('.baidu.com')) { 15 | this.getKeyword = () => ($('#kw')[0]['value'] as string) 16 | this.innerClass = '#content_left' 17 | let interval = null 18 | $('#kw').bind('input propertychange', () => { 19 | // Have to wait for the container to appear 20 | if (interval !== null) { 21 | clearInterval(interval) 22 | interval = null 23 | } 24 | interval = setInterval(() => { 25 | if ($(this.innerClass).length > 0) { 26 | clearInterval(interval) 27 | interval = null 28 | this.sendSearch(this.getKeyword()) 29 | } 30 | }, 100) 31 | }) 32 | 33 | message.addListener({ 34 | type: 'load-complete', 35 | callback: (message, sender, sendResponse) => { 36 | setTimeout(() => { 37 | this.sendSearch(this.getKeyword()) 38 | }, 500); 39 | sendResponse('') 40 | } 41 | }) 42 | } else if (this.host.startsWith('www.google.')) { 43 | this.getKeyword = () => ($('input.gsfi')[0]['value'] as string) 44 | this.innerClass = '#res' 45 | $('input.gsfi').bind('input propertychange', () => { 46 | this.sendSearch(this.getKeyword()) 47 | }) 48 | } else if (this.host.endsWith('.bing.com')) { 49 | $('li.b_algo:last').after('
') 50 | this.getKeyword = () => ($('#sb_form_q')[0]['value'] as string) 51 | this.innerClass = '#bookmarkCard' 52 | $('#sb_form_q').bind('input propertychange', () => { 53 | this.sendSearch(this.getKeyword()) 54 | }) 55 | } else if (this.host.endsWith('search.naver.com')) { 56 | this.getKeyword = () => ($('#nx_query').val() as string) 57 | this.innerClass = '#sub_pack' 58 | $('#nx_query').bind('input propertychange', () => { 59 | this.sendSearch(this.getKeyword()) 60 | }) 61 | } else { 62 | return 63 | } 64 | 65 | this.sendSearch(this.getKeyword()) 66 | } 67 | 68 | public sendSearch(keyword: string) { 69 | message.send('bookmark-search', keyword).then((response) => { 70 | $('#cloudopt-bookmark').remove() 71 | if (response.length > 0) { 72 | this.createCard(response as any[]) 73 | } 74 | }) 75 | } 76 | 77 | private createCard(response: any[]) { 78 | const card = '
BookMark Search
' 79 | if (response.length > 0) { 80 | $(card).appendTo(this.innerClass) 81 | response.forEach((item) => { 82 | if (item.url && item.url.indexOf('place:') < 0) { 83 | const a = [item.url, item.title, item.url].map(sanitize) 84 | const html = `
${a[1]}

${a[2]}

` 85 | $(html).appendTo('#cloudopt-bookmark-content') 86 | } 87 | }) 88 | } 89 | } 90 | } 91 | 92 | let bookmarkSearch: BookmarkSearch 93 | $.when($.ready).then(async () => { 94 | const config = await refreshConfig() 95 | if (!config.labBookmarkSearch) { 96 | return 97 | } 98 | bookmarkSearch = new BookmarkSearch() 99 | }) 100 | -------------------------------------------------------------------------------- /src/content/dnsPrefetch.ts: -------------------------------------------------------------------------------- 1 | import * as coreConfig from '../core/config' 2 | import * as utils from '../core/utils' 3 | import message from '../core/message' 4 | import $ from 'jquery' 5 | import {sanitize} from 'dompurify' 6 | 7 | coreConfig.get().then((config) => { 8 | if (!config.dnsSpeed) { 9 | return 10 | } 11 | 12 | const hosts = new Array() 13 | $('a').each((index, element: HTMLElement) => { 14 | const href = sanitize($(element).attr('href')) 15 | if (!href) { 16 | return 17 | } 18 | 19 | if (href.startsWith('http') || href.startsWith('https')) { 20 | const host = utils.getHost(href) 21 | if (['', ' ', 'javascript'].inArray(host)) { 22 | return 23 | } 24 | 25 | if (!hosts.inArray(host)) { 26 | hosts.push(host) 27 | } 28 | } 29 | }) 30 | 31 | if (hosts.length > 0) { 32 | const tags = hosts.map((host) => { 33 | const tag = document.createElement('link') 34 | tag.rel = 'dns-prefetch' 35 | tag.href = `//${host}` 36 | return tag 37 | }) 38 | 39 | $('head')[0].append(...tags) 40 | if (!window._cloudopt_accelerated) { 41 | message.send('countEvent', 'web-accelerate') 42 | window._cloudopt_accelerated = true 43 | } 44 | } 45 | }) 46 | -------------------------------------------------------------------------------- /src/content/memopt.ts: -------------------------------------------------------------------------------- 1 | import * as utils from '../core/utils' 2 | 3 | window.addEventListener('keydown', (event) => { 4 | function isPrintable(keyCode: number) { 5 | // https://stackoverflow.com/a/12467610 6 | return (keyCode > 47 && keyCode < 58) || // number keys 7 | keyCode === 32 || keyCode === 13 || // spacebar & return key(s) (if you want to allow carriage returns) 8 | (keyCode > 64 && keyCode < 91) || // letter keys 9 | (keyCode > 95 && keyCode < 112) || // numpad keys 10 | (keyCode > 185 && keyCode < 193) || // ;=,-./` (in order) 11 | (keyCode > 218 && keyCode < 223) // [\]' (in order) 12 | } 13 | 14 | if (!isPrintable(event.keyCode)) { 15 | return 16 | } 17 | 18 | /* tslint:disable:no-string-literal */ 19 | if (['input', 'textarea', 'form', 'pre'].indexOf(event.target['tagName'].toLowerCase()) >= 0) { 20 | window._co_cloudopt_formChanged = true 21 | } 22 | /* tslint:enable:no-string-literal */ 23 | }) 24 | 25 | window._co_cloudopt_getExtUrl = utils.getExtUrl 26 | -------------------------------------------------------------------------------- /src/content/prerender.ts: -------------------------------------------------------------------------------- 1 | import * as coreConfig from '../core/config' 2 | import * as utils from '../core/utils' 3 | import message from '../core/message' 4 | import $ from 'jquery' 5 | import { sanitize } from 'dompurify' 6 | 7 | coreConfig.get().then((config) => { 8 | if (!config.webPrereading) { 9 | return 10 | } 11 | 12 | $('a').map((index, domElement: HTMLElement) => { 13 | const href = sanitize($(domElement).attr('href')) 14 | if (!href) { 15 | return 16 | } 17 | 18 | if (!(href.startsWith('http') || href.startsWith('https'))) { 19 | return 20 | } 21 | 22 | const host = utils.getHost(href) 23 | if (['', ' ', 'javascript'].inArray(host)) { 24 | return 25 | } 26 | 27 | let timer: NodeJS.Timeout 28 | $(domElement).hover(() => { 29 | timer = setTimeout(() => { 30 | const linkTag = document.createElement('link') 31 | linkTag.rel = 'prerender' 32 | linkTag.href= href 33 | $('head')[0].append(linkTag) 34 | if (!window._cloudopt_accelerated) { 35 | message.send('countEvent', 'web-accelerate') 36 | window._cloudopt_accelerated = true 37 | } 38 | }, 2000) 39 | }, () => { 40 | window.clearTimeout(timer) 41 | }) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /src/content/website.ts: -------------------------------------------------------------------------------- 1 | import message from '../core/message' 2 | import * as coreConfig from '../core/config' 3 | import * as utils from '../core/utils' 4 | import * as logger from '../core/logger' 5 | import * as grade from '../core/grade' 6 | 7 | coreConfig.get().then((config) => { 8 | if (!config.safeCloud) { 9 | return 10 | } 11 | 12 | message.send('grade-website', window.location.href).then((result: grade.Result) => { 13 | if (result.safe) { 14 | return 15 | } 16 | message.send('countEvent', 'site-block') 17 | window.location.href = `https://www.cloudopt.net/block/${encodeURIComponent(utils.getHost(window.location.href))}` 18 | }).catch((reason) => { 19 | logger.error(reason) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /src/core/api.ts: -------------------------------------------------------------------------------- 1 | import {Result as GradeResult} from './grade' 2 | import * as http from './http' 3 | import * as coreConfig from './config' 4 | 5 | export const HOST = 'https://www.cloudopt.net/api/v2/' 6 | export const APIKEY = '11N9M530M667ZYW9KZHB0100JAX3XRGJ' 7 | 8 | export function gradeWebsite(website: string): Promise { 9 | return http.get(`${HOST}grade/website/${website}`, {timeout: 3000, data: {apikey: APIKEY}}).then((data) => { 10 | const result = new GradeResult() 11 | result.host = website 12 | result.type = data.result.type 13 | result.score = data.result.score 14 | if (data.result.host !== '') { 15 | result.date = new Date().getTime() 16 | } 17 | return result 18 | }) 19 | } 20 | 21 | export function saveConfig(config: coreConfig.Config): Promise { 22 | let dataObject: any = Object.assign({}, config) 23 | dataObject.whiteList = dataObject.allowList 24 | dataObject.whiteListAds = dataObject.allowListAds 25 | dataObject.blackList = dataObject.blockList 26 | delete dataObject.allowList 27 | delete dataObject.allowListAds 28 | delete dataObject.blockList 29 | delete dataObject.disabledCustomSubs 30 | return http.put(`${HOST}adblocker/config`, {data: JSON.stringify(dataObject)}) 31 | } 32 | 33 | export function downloadConfig() { 34 | return http.get(`${HOST}adblocker/config`) 35 | } 36 | 37 | export function statistics(data: any) { 38 | return http.get('https://www.google-analytics.com/collect', { 39 | timeout: 10000, 40 | async: true, 41 | data, 42 | }) 43 | } 44 | 45 | export function geoIp() { 46 | return http.get(`${HOST}ip`, {data: {apikey: APIKEY}}) 47 | } 48 | 49 | export function auth() { 50 | return http.get(`${HOST}account/auth`, {data: {apikey: APIKEY}}) 51 | } 52 | 53 | export function logout() { 54 | return http.del(`${HOST}account/auth`, {data: {apikey: APIKEY}}) 55 | } 56 | 57 | export function connectionCount() { 58 | return http.get(`${HOST}health/connections`) 59 | } 60 | -------------------------------------------------------------------------------- /src/core/browserIconChange.ts: -------------------------------------------------------------------------------- 1 | export function normal() { 2 | chrome.browserAction.setIcon({ 3 | path: 'image/icon/default/icon64.png', 4 | }) 5 | } 6 | 7 | export function gray() { 8 | chrome.browserAction.setIcon({ 9 | path: 'image/icon/gray/icon64.png', 10 | }) 11 | } 12 | 13 | export function green() { 14 | chrome.browserAction.setIcon({ 15 | path: 'image/icon/green/icon64.png', 16 | }) 17 | } 18 | 19 | export function danger() { 20 | chrome.browserAction.setIcon({ 21 | path: 'image/icon/red/icon64.png', 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /src/core/grade.ts: -------------------------------------------------------------------------------- 1 | import * as utils from './utils' 2 | import * as store from './store' 3 | import * as coreConfig from './config' 4 | import * as api from './api' 5 | 6 | const DEFAULT_EXPIREDAYS = 1 7 | 8 | export class Result { 9 | public safe: boolean = true 10 | public type: string = '' 11 | public date: number = new Date().getTime() 12 | public score: number = 0 13 | public host: string = '' 14 | 15 | public async isSafe(): Promise { 16 | const level = this.classify() 17 | const config = await coreConfig.get() 18 | let safe = false 19 | if (level <= 2) { 20 | safe = true 21 | } 22 | if (level === 3) { 23 | if (config.safePotential) { 24 | safe = false 25 | } else { 26 | safe = true 27 | } 28 | } 29 | if (level === 5) { 30 | safe = true 31 | } 32 | if (config.allowList.indexOf(this.host) > -1 || config.allowListAds.indexOf(this.host) > -1) { 33 | safe = true 34 | } 35 | if (config.blockList.some((e) => e === this.host)) { 36 | safe = false 37 | } 38 | 39 | return safe 40 | } 41 | 42 | public classify(): number { 43 | let level = 0 /* no level */ 44 | if (this.score >= 65) { 45 | level = 1 /* safe */ 46 | } else if (this.score >= 50) { 47 | level = 2 /* normal */ 48 | } else if (this.score >= 30) { 49 | level = 3 /* potential threat */ 50 | } else if (this.score === 0 && this.safe) { 51 | level = 5 /* unknown */ 52 | } else { 53 | level = 4 /* dangerous */ 54 | } 55 | return level 56 | } 57 | 58 | public toString(): string { 59 | return `${this.host}_${this.score}_${this.date}_${this.type}` 60 | } 61 | 62 | public fromString(str: string): Result { 63 | let host: string, type: string, score: string, date: string 64 | try { 65 | [host, score, date, type] = str.split('_') 66 | if (!host || !type) { 67 | throw new Error('invalid cache format') 68 | } 69 | this.host = host 70 | this.score = parseInt(score, 10) 71 | this.date = parseInt(date, 10) 72 | this.type = type 73 | } catch (error) { 74 | return this 75 | } 76 | return this 77 | } 78 | } 79 | 80 | function couldGrade(url: string): boolean { 81 | if (!url.startsWith('http://') && !url.startsWith('https://')) { 82 | return false 83 | } 84 | 85 | const host = utils.getHost(url) 86 | if (/^([0-9]{1,3}\.){3}[0-9]{1,3}$/.test(host) 87 | || 'localhost' === host) { 88 | return false 89 | } 90 | 91 | return true 92 | } 93 | 94 | export async function website(url: string): Promise { 95 | const result = new Result() 96 | if (!couldGrade(url)) { 97 | return Promise.resolve(result) 98 | } 99 | const cacheSuffix = '_grade_1.0' 100 | const host = utils.getHost(url) 101 | result.host = host 102 | const cacheKey = host + cacheSuffix 103 | 104 | const recordStr = await store.get(cacheKey) 105 | let record = (new Result()).fromString(recordStr) 106 | if (recordStr && utils.compareDate(record.date, DEFAULT_EXPIREDAYS)) { 107 | record.safe = await record.isSafe() 108 | } else { 109 | try { 110 | record = await api.gradeWebsite(host) 111 | record.safe = await record.isSafe() 112 | store.set(cacheKey, record.toString()) 113 | } catch (error) { 114 | record = new Result() 115 | record.score = 60 116 | record.safe = true 117 | // When there is no record on cloudopt.net yet 118 | if (error.error === 404) { 119 | record.host = 'UNKNOWN' 120 | record.type = 'UNKNOWN' 121 | store.set(cacheKey, record.toString()) 122 | } 123 | } 124 | } 125 | 126 | return record 127 | } 128 | -------------------------------------------------------------------------------- /src/core/http.ts: -------------------------------------------------------------------------------- 1 | import $ from 'jquery' 2 | import * as logger from './logger' 3 | 4 | class RequestObject { 5 | private options = { 6 | url: '', 7 | /** Whether to allow browser caching */ 8 | cache: true, 9 | /** What type of content is sent to the server */ 10 | contentType: 'application/json;charset=UTF-8', 11 | /** parameter */ 12 | data: {}, 13 | /** 'GET' | ''POST' | 'PUT' */ 14 | method: 'GET', 15 | } 16 | constructor(options: object) { 17 | this.options = Object.assign(this.options, options) 18 | } 19 | 20 | public exec(isResolveError: boolean = true): Promise { 21 | return this.getAjaxRequest(isResolveError) 22 | } 23 | 24 | private handleHttpErrorStatus(response: {error: number}) { 25 | const status = response.error 26 | switch (status) { 27 | /** No permission */ 28 | case 401: 29 | // todo... 30 | break 31 | /** access denied */ 32 | case 403: 33 | // todo... 34 | break 35 | /** No resources */ 36 | case 404: 37 | // todo... 38 | break 39 | default: break 40 | } 41 | } 42 | 43 | private getAjaxRequest(isResolveError: boolean): Promise { 44 | return new Promise(async (resolve, reject) => { 45 | try { 46 | const result = await $.ajax(this.options) 47 | resolve(result) 48 | } catch (error) { 49 | let response: {error: number} 50 | if (error && error.status && error.status >= 400 && error.status < 500 && (typeof error.responseText === 'string') && error.responseText.startsWith('{')) { 51 | response = JSON.parse(error.responseText) 52 | } else if (error && error.status !== 200) { 53 | response = { 54 | error: error.status, 55 | } 56 | } 57 | if (isResolveError && response && response.error) { 58 | this.handleHttpErrorStatus(response) 59 | } 60 | reject(response) 61 | logger.debug(`Ajax request to ${this.options.url} error: ${JSON.stringify(error)}`) 62 | } 63 | }) 64 | } 65 | } 66 | 67 | export function get(url: string, options: object = {}): Promise { 68 | return new RequestObject({ 69 | url, 70 | method: 'GET', 71 | ...options, 72 | }).exec() 73 | } 74 | 75 | export function put(url: string, options: object = {}): Promise { 76 | return new RequestObject({ 77 | url, 78 | method: 'PUT', 79 | ...options, 80 | }).exec() 81 | } 82 | 83 | export function post(url: string, options: object = {}): Promise { 84 | return new RequestObject({ 85 | url, 86 | method: 'POST', 87 | ...options, 88 | }).exec() 89 | } 90 | 91 | export function del(url: string, options: object = {}): Promise { 92 | return new RequestObject({ 93 | url, 94 | method: 'DELETE', 95 | ...options, 96 | }).exec() 97 | } 98 | -------------------------------------------------------------------------------- /src/core/i18n.ts: -------------------------------------------------------------------------------- 1 | import $ from 'jquery' 2 | 3 | export function get(name: string): string { 4 | return chrome.i18n.getMessage(name) 5 | } 6 | 7 | export function replace(name: string, text: string): string { 8 | const newText = chrome.i18n.getMessage(name) 9 | return newText.replace('##', text) 10 | } 11 | 12 | export function translateCurrentPage() { 13 | translateComponent(document.documentElement) 14 | } 15 | 16 | export function translateComponent(component: HTMLElement) { 17 | $(component).find('*[i18n]').each((i, element) => { 18 | const i18nOnly = $(element).attr('i18n-only') 19 | if ($(element).val() != null && $(element).val() !== '') { 20 | if (i18nOnly == null || i18nOnly === '' || i18nOnly === 'value') { 21 | if(get($(element).attr('i18n'))){ 22 | $(element).val(get($(element).attr('i18n'))) 23 | } 24 | } 25 | } 26 | if ($(element).html() != null && $(element).html() !== '') { 27 | if (i18nOnly == null || i18nOnly === '' || i18nOnly === 'html') { 28 | if(get($(element).attr('i18n'))){ 29 | $(element).html(get($(element).attr('i18n'))) 30 | } 31 | } 32 | } 33 | if ($(element).attr('placeholder') != null && $(element).attr('placeholder') !== '') { 34 | if (i18nOnly == null || i18nOnly === '' || i18nOnly === 'placeholder') { 35 | if(get($(element).attr('i18n'))){ 36 | $(element).attr(get($(element).attr('i18n'))) 37 | } 38 | } 39 | } 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /src/core/logger.ts: -------------------------------------------------------------------------------- 1 | enum LogLevel { 2 | DEBUG = 0, 3 | INFORMATION, 4 | WARNING, 5 | ERROR, 6 | } 7 | 8 | const logLevel: LogLevel = LogLevel.INFORMATION 9 | 10 | /* tslint:disable:no-console */ 11 | function print(level: LogLevel, text: string, color: string) { 12 | console.log(`%c[CLOUDOPT-${LogLevel[level]}]${(new Date()).toLocaleDateString()}:${text}`, `color:${color}`) 13 | } 14 | /* tslint:enable:no-console */ 15 | 16 | export function debug(text: string) { 17 | if (logLevel <= LogLevel.DEBUG) { 18 | print(LogLevel.DEBUG, text, '#00BCD4') 19 | } 20 | } 21 | 22 | export function info(text: string) { 23 | if (logLevel <= LogLevel.INFORMATION) { 24 | print(LogLevel.INFORMATION, text, '#3F51B5') 25 | } 26 | } 27 | 28 | export function warning(text: string) { 29 | if (logLevel <= LogLevel.WARNING) { 30 | print(LogLevel.WARNING, text, '#E91E63') 31 | } 32 | } 33 | 34 | export function error(text: string) { 35 | if (logLevel <= LogLevel.ERROR) { 36 | print(LogLevel.ERROR, text, '#E91E63') 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/core/loginState.ts: -------------------------------------------------------------------------------- 1 | import * as api from './api' 2 | 3 | let _loggedIn: boolean = false 4 | let _loginData: any = null 5 | 6 | export async function updateLoginStatus(): Promise { 7 | try { 8 | _loginData = (await api.auth()).result 9 | _loggedIn = true 10 | } catch (error) { 11 | _loginData = {} 12 | _loggedIn = false 13 | } 14 | return _loggedIn 15 | } 16 | 17 | export function loggedIn(): boolean { 18 | return _loggedIn 19 | } 20 | 21 | export async function getLoginData(): Promise { 22 | if (!_loginData) { 23 | await updateLoginStatus() 24 | } 25 | return _loginData 26 | } 27 | -------------------------------------------------------------------------------- /src/core/message.ts: -------------------------------------------------------------------------------- 1 | import * as logger from './logger' 2 | 3 | type IMessageCallback = (message: any, sender: any, sendResponse: (message: any) => void) => any 4 | 5 | interface IMessageRecord { 6 | type: string, 7 | callback: IMessageCallback, 8 | } 9 | 10 | class Message { 11 | private registry: IMessageRecord[] = [] 12 | 13 | constructor() { 14 | chrome.runtime.onMessage.addListener((message: any, sender: any, sendResponse: (response: any) => void) => { 15 | this.registry 16 | .filter((record) => record.type === message.type) 17 | .map((record) => record.callback(message, sender, sendResponse)) 18 | }) 19 | } 20 | 21 | public addListener(messageRecord: IMessageRecord) { 22 | logger.debug(`Add message listener ${messageRecord.type}`) 23 | this.registry.push(messageRecord) 24 | } 25 | 26 | public async send(type: string, text: any = null): Promise { 27 | return new Promise((resolve: (something) => any) => chrome.runtime.sendMessage({type, text}, resolve)) 28 | } 29 | 30 | public async sendTab(tabId: number, type: string, text: any = null): Promise { 31 | return new Promise((resolve: (something) => any) => chrome.tabs.sendMessage(tabId, {type, text}, resolve)) 32 | } 33 | } 34 | 35 | const message = new Message() 36 | export default message 37 | -------------------------------------------------------------------------------- /src/core/notification.ts: -------------------------------------------------------------------------------- 1 | import * as coreConfig from './config' 2 | import * as i18n from './i18n' 3 | 4 | function notyFactory(iconUrl: string): (message: string) => void { 5 | return (message: string) => { 6 | if (chrome && chrome.notifications) { 7 | chrome.notifications.create('', { 8 | type: 'basic', 9 | title: 'Cloudopt AdBlocker', 10 | message, 11 | iconUrl, 12 | }, (id) => { 13 | setTimeout(() => { chrome.notifications.clear(id, () => undefined) }, 6000) 14 | }) 15 | } 16 | } 17 | } 18 | 19 | export function info(message: string) { 20 | notyFactory('/image/icon/default/icon512.png')(message) 21 | } 22 | 23 | export function success(message: string) { 24 | notyFactory('/image/icon/green/icon512.png')(message) 25 | } 26 | 27 | export function error(message: string) { 28 | notyFactory('/image/icon/red/icon512.png')(message) 29 | } 30 | 31 | let suppressBankNoty = false 32 | let suppressShopNoty = false 33 | let suppressGameNoty = false 34 | let suppressHospitalNoty = false 35 | 36 | const noRepeatNotyTime = 1000 * 60 * 5 37 | export async function labSafeTipsNoty(type: string) { 38 | const config = await coreConfig.get() 39 | if (!config.labSafeTips) { 40 | return 41 | } 42 | 43 | switch (type) { 44 | case 'BANK': 45 | if (!suppressBankNoty) { 46 | info(i18n.get('bankSafeTips')) 47 | suppressBankNoty = true 48 | setTimeout(() => { 49 | suppressBankNoty = false 50 | }, noRepeatNotyTime); 51 | } 52 | break 53 | case 'SHOP': 54 | if (!suppressShopNoty) { 55 | info(i18n.get('shopSafeTips')) 56 | suppressShopNoty = true 57 | setTimeout(() => { 58 | suppressShopNoty = false 59 | }, noRepeatNotyTime); 60 | } 61 | break 62 | case 'GAME': 63 | if (!suppressGameNoty) { 64 | info(i18n.get('gameSafeTips')) 65 | suppressGameNoty = true 66 | setTimeout(() => { 67 | suppressGameNoty = false 68 | }, noRepeatNotyTime); 69 | } 70 | break 71 | case 'HOSPITAL': 72 | if (!suppressHospitalNoty) { 73 | info(i18n.get('hospitalSafeTips')) 74 | suppressHospitalNoty = true 75 | setTimeout(() => { 76 | suppressHospitalNoty = false 77 | }, noRepeatNotyTime); 78 | } 79 | break 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/core/store.ts: -------------------------------------------------------------------------------- 1 | 2 | const storeDriver = chrome.storage.local 3 | 4 | export async function set(key: string, value: any): Promise { 5 | return new Promise((resolve: () => void) => { storeDriver.set({[key]: value}, resolve) }) 6 | } 7 | 8 | export function get(key: string): Promise { 9 | return new Promise((resolve: (something: {[key: string]: any}) => void) => { 10 | storeDriver.get(key, (items) => {resolve(items[key])}) 11 | }) 12 | } 13 | 14 | export async function all(): Promise { 15 | return new Promise((resolve: (something: any) => any) => storeDriver.get(null, resolve)) 16 | } 17 | 18 | export async function remove(key: string): Promise { 19 | return new Promise((resolve: () => void) => storeDriver.remove(key, resolve)) 20 | } 21 | 22 | export function clear() { 23 | storeDriver.clear() 24 | } 25 | -------------------------------------------------------------------------------- /src/core/tab.ts: -------------------------------------------------------------------------------- 1 | export function open(url: string) { 2 | chrome.tabs.create({ 3 | url, 4 | active: true, 5 | pinned: false, 6 | }) 7 | } 8 | 9 | export function closeCurrentTab() { 10 | chrome.tabs.query({ currentWindow: true, active: true }, (tabs) => { 11 | if (tabs[0] && tabs[0].id) { 12 | chrome.tabs.remove(tabs[0].id) 13 | } 14 | }) 15 | } 16 | 17 | export function closeTabByTitle(title: string) { 18 | chrome.tabs.query({ title: title }, (tabs) => { 19 | if (tabs[0] && tabs[0].id) { 20 | chrome.tabs.remove(tabs[0].id) 21 | } 22 | }) 23 | } -------------------------------------------------------------------------------- /src/guide/common/switchInfo/index.scss: -------------------------------------------------------------------------------- 1 | .switch-item { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: center; 5 | max-width: 620px; 6 | margin: 35px 0; 7 | .left { 8 | display: flex; 9 | align-items: center; 10 | .icon { 11 | margin-right: 20px; 12 | width: 30px; 13 | } 14 | .description { 15 | display: flex; 16 | flex-direction: column; 17 | text-align: left; 18 | .title { 19 | font-size: 16px; 20 | font-weight: bold; 21 | line-height: 26px; 22 | color: rgba(36, 43, 51, 1); 23 | } 24 | .content { 25 | font-size: 13px; 26 | font-weight: 400; 27 | color: rgba(138, 145, 153, 1); 28 | } 29 | } 30 | } 31 | .custom-toggle .custom-control-input:checked ~ .custom-control-label::before { 32 | border-color: #0078ff; 33 | background-color: #0078ff; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/guide/common/switchInfo/index.ts: -------------------------------------------------------------------------------- 1 | import "bootstrap/dist/css/bootstrap.min.css"; 2 | import "shards-ui/dist/css/shards.min.css"; 3 | import "./index.scss"; 4 | import { get as getCoreConfig, set as setCoreConfig } from '../../../core/config' 5 | import message from '../../../core/message' 6 | import { renderTemplate } from '../../../core/utils' 7 | 8 | export interface ISwitchInfo { 9 | icon: string; 10 | title: string; 11 | content?: string; 12 | key: string; 13 | on: boolean; 14 | i18n?: string; 15 | } 16 | 17 | export class SwitchInfo { 18 | public divElement: HTMLDivElement 19 | constructor(private options: ISwitchInfo) { 20 | this.divElement = document.createElement('div') 21 | this.init(); 22 | } 23 | 24 | private init(): void { 25 | this.divElement.className = "switch-item" 26 | let data = this.options 27 | if (data.on) { 28 | data["checked"] = "checked" 29 | } else { 30 | data["checked"] = "" 31 | } 32 | this.divElement.innerHTML = renderTemplate(` 33 |
34 |
35 |
36 | {{ title }} 37 | {{ content }} 38 |
39 |
40 |
41 |
42 | 49 | 50 |
51 |
52 | `, data) 53 | this.divElement.querySelector('.right input').addEventListener('click', async (ev: MouseEvent) => { 54 | this.options.on = !this.options.on 55 | const newConfig = await getCoreConfig() 56 | newConfig[this.options.key] = this.options.on 57 | setCoreConfig(newConfig).then(() => message.send('refresh-config')) 58 | }) 59 | } 60 | } 61 | 62 | export const createSwitchInfoDom = (options: ISwitchInfo): SwitchInfo => { 63 | return new SwitchInfo(options); 64 | } 65 | -------------------------------------------------------------------------------- /src/guide/common/utils.ts: -------------------------------------------------------------------------------- 1 | export const getHash = (hash: string = window.location.hash): string => { 2 | return hash.replace("#", ""); 3 | }; 4 | -------------------------------------------------------------------------------- /src/guide/guide.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Cloudopt AdBlocker Guidance 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/guide/guide.scss: -------------------------------------------------------------------------------- 1 | #guidePages { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | height: 100vh; 6 | background-color: rgb(245, 246, 247); 7 | .btn { 8 | outline: none; 9 | border: none; 10 | width: 200px; 11 | height: 30px; 12 | box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.02); 13 | border-radius: 3px; 14 | font-size: 13px; 15 | font-weight: bold; 16 | line-height: 0; 17 | font-size: 13px; 18 | font-family: Noto Sans, Helvetica, 'Hiragino Sans GB', 'Microsoft Yahei', '微软雅黑', Arial, sans-serif; 19 | font-weight: bold; 20 | color: rgba(88, 143, 255, 1); 21 | border: 1px solid rgba(30, 111, 255, 1); 22 | text-transform: uppercase; 23 | &.btn-primary { 24 | background: rgba(19, 116, 255, 1); 25 | color: rgba(255, 255, 255, 1); 26 | border: none; 27 | } 28 | } 29 | .container { 30 | width: 800px; 31 | height: 558px; 32 | background: rgba(255, 255, 255, 1); 33 | box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.1); 34 | position: relative; 35 | display: flex; 36 | align-items: center; 37 | justify-content: center; 38 | .switch-item { 39 | max-width: 570px; 40 | } 41 | >span.title { 42 | font-size: 16px; 43 | font-weight: bold; 44 | line-height: 22px; 45 | color: rgba(66, 80, 99, 1); 46 | position: absolute; 47 | left: 30px; 48 | top: 30px; 49 | } 50 | .content-block { 51 | text-align: center; 52 | &#step1 { 53 | max-width: 424px; 54 | .footer { 55 | button { 56 | margin-top: 68px; 57 | } 58 | } 59 | } 60 | &#step2 { 61 | .footer { 62 | button { 63 | margin-top: 42px; 64 | } 65 | } 66 | } 67 | &#step5 { 68 | .description { 69 | max-width: 420px; 70 | } 71 | } 72 | &#step6 { 73 | max-width: 442px; 74 | button { 75 | margin-top: 68px; 76 | } 77 | } 78 | .body { 79 | h3 { 80 | font-size: 18px; 81 | font-family: Noto Sans, Helvetica, 'Hiragino Sans GB', 'Microsoft Yahei', '微软雅黑', Arial, sans-serif; 82 | font-weight: 400; 83 | color: rgba(38, 38, 38, 1); 84 | margin: 25px 0 8px 0; 85 | } 86 | span.text { 87 | font-size: 13px; 88 | font-family: Noto Sans, Helvetica, 'Hiragino Sans GB', 'Microsoft Yahei', '微软雅黑', Arial, sans-serif; 89 | font-weight: 400; 90 | color: rgba(89, 98, 106, 1); 91 | max-width: 424px; 92 | margin-bottom: 60px; 93 | display: block; 94 | } 95 | .guide-content { 96 | width: 100%; 97 | margin-bottom: 40px; 98 | } 99 | } 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as options from './option/option'; -------------------------------------------------------------------------------- /src/libs/icon/MaterialIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudoptlab/cloudopt-adblocker/b6f21f452ca66d73e0574fa4642db9ac18710b27/src/libs/icon/MaterialIcons-Regular.woff2 -------------------------------------------------------------------------------- /src/libs/icon/icon.css: -------------------------------------------------------------------------------- 1 | /* fallback */ 2 | @font-face { 3 | font-family: 'Material Icons'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Material Icons'), local('MaterialIcons-Regular'), url(/lib/icon/MaterialIcons-Regular.woff2) format('woff2'); 7 | } 8 | 9 | .material-icons { 10 | font-family: 'Material Icons'; 11 | font-weight: normal; 12 | font-style: normal; 13 | font-size: 24px; 14 | line-height: 1; 15 | letter-spacing: normal; 16 | text-transform: none; 17 | display: inline-block; 18 | white-space: nowrap; 19 | word-wrap: normal; 20 | direction: ltr; 21 | -webkit-font-feature-settings: 'liga'; 22 | -webkit-font-smoothing: antialiased; 23 | } -------------------------------------------------------------------------------- /src/option/common/switchInfo/index.scss: -------------------------------------------------------------------------------- 1 | .switch-item { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: center; 5 | max-width: 620px; 6 | margin: 35px 0; 7 | .left { 8 | display: flex; 9 | align-items: center; 10 | .icon { 11 | margin-right: 20px; 12 | width: 30px; 13 | } 14 | .description { 15 | display: flex; 16 | flex-direction: column; 17 | text-align: left; 18 | .title { 19 | font-size: 16px; 20 | font-weight: bold; 21 | line-height: 26px; 22 | color: rgba(36, 43, 51, 1); 23 | } 24 | .content { 25 | font-size: 13px; 26 | font-weight: 400; 27 | color: rgba(138, 145, 153, 1); 28 | max-width: 460px; 29 | line-height: 1.5; 30 | } 31 | } 32 | } 33 | .custom-toggle .custom-control-input:checked ~ .custom-control-label::before { 34 | border-color: #0078ff; 35 | background-color: #0078ff; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/option/common/switchInfo/index.ts: -------------------------------------------------------------------------------- 1 | import "bootstrap/dist/css/bootstrap.min.css"; 2 | import "shards-ui/dist/css/shards.min.css"; 3 | import "./index.scss"; 4 | import { get as getCoreConfig, set as setCoreConfig } from '../../../core/config' 5 | import message from '../../../core/message' 6 | import { renderTemplate } from '../../../core/utils' 7 | 8 | export interface ISwitchInfo { 9 | icon: string; 10 | title: string; 11 | content?: string; 12 | key: string; 13 | on: boolean; 14 | i18n?: string; 15 | } 16 | 17 | export class SwitchInfo { 18 | public divElement: HTMLDivElement 19 | constructor(private options: ISwitchInfo) { 20 | this.divElement = document.createElement('div') 21 | this.init(); 22 | } 23 | 24 | private init(): void { 25 | this.divElement.className = "switch-item"; 26 | let data = this.options 27 | if(data.on){ 28 | data["checked"] = "checked" 29 | }else{ 30 | data["checked"] = "" 31 | } 32 | if(data.i18n && data.i18n != ""){ 33 | data.content = data.i18n 34 | } 35 | this.divElement.innerHTML = renderTemplate(` 36 |
37 |
38 |
39 | {{ title }} 40 | {{ content }} 41 |
42 |
43 |
44 |
45 | 52 | 53 |
54 |
55 | `, data) 56 | this.divElement.querySelector('.right input').addEventListener('click', async (ev: MouseEvent) => { 57 | this.options.on = !this.options.on 58 | const newConfig = await getCoreConfig() 59 | newConfig[this.options.key] = this.options.on 60 | setCoreConfig(newConfig).then(() => message.send('refresh-config')) 61 | }) 62 | } 63 | } 64 | 65 | export const createSwitchInfoDom = (options: ISwitchInfo): SwitchInfo => { 66 | return new SwitchInfo(options); 67 | }; 68 | -------------------------------------------------------------------------------- /src/option/common/utils.ts: -------------------------------------------------------------------------------- 1 | export const getHash = (hash: string = window.location.hash): string => { 2 | return hash.replace("#", ""); 3 | }; 4 | -------------------------------------------------------------------------------- /src/option/component/aboutUs/index.scss: -------------------------------------------------------------------------------- 1 | #aboutUsPages { 2 | padding-top: 50px; 3 | &>.title { 4 | font-size: 40px; 5 | font-weight: 400; 6 | color: rgba(38, 38, 38, 1); 7 | margin-bottom: 38px; 8 | } 9 | &>.description { 10 | display: flex; 11 | flex-direction: column; 12 | .content { 13 | font-size: 18px; 14 | font-weight: 400; 15 | color: rgba(38, 38, 38, 1); 16 | } 17 | .detaile { 18 | font-size: 14px; 19 | font-weight: 400; 20 | color: rgba(89, 98, 106, 1); 21 | margin: 20px 0; 22 | max-width: 610px; 23 | line-height: 21px; 24 | } 25 | .link-info { 26 | font-size: 14px; 27 | font-weight: 400; 28 | color: rgba(175, 175, 175, 1); 29 | text-decoration: underline; 30 | } 31 | } 32 | &>.table-info-container { 33 | .table-item { 34 | margin: 12px 0 30px 0; 35 | .title { 36 | font-size: 20px; 37 | font-weight: 500; 38 | color: rgba(23, 43, 77, 1); 39 | margin-bottom: 12px; 40 | display: inline-block; 41 | } 42 | .table { 43 | max-width: 623px; 44 | background: rgba(255, 255, 255, 1); 45 | box-shadow: 0px 2px 3px rgba(44, 40, 40, 0.11); 46 | border-radius: 5px; 47 | .hard { 48 | width: 623px; 49 | height: 45px; 50 | background: #f1f3f9; 51 | line-height: 45px; 52 | padding: 0 20px; 53 | display: flex; 54 | justify-content: space-between; 55 | .text { 56 | font-size: 13px; 57 | font-weight: 500; 58 | color: rgba(136, 152, 170, 1); 59 | } 60 | } 61 | .body-list { 62 | ul li:nth-child(2n) { 63 | background: rgba(247, 250, 252, 1); 64 | } 65 | li { 66 | display: flex; 67 | justify-content: space-between; 68 | padding: 14px 20px; 69 | .left { 70 | span, 71 | a { 72 | font-size: 13px; 73 | font-weight: 400; 74 | color: rgba(23, 43, 77, 1) !important; 75 | text-decoration-line: none !important; 76 | } 77 | } 78 | .right {} 79 | } 80 | } 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/option/component/adBlock/index.scss: -------------------------------------------------------------------------------- 1 | #adBlockPages { 2 | padding-top: 50px; 3 | &>.title { 4 | font-size: 40px; 5 | font-weight: 400; 6 | color: rgba(38, 38, 38, 1); 7 | margin-bottom: 38px; 8 | } 9 | &>.description { 10 | display: flex; 11 | flex-direction: column; 12 | .content { 13 | font-size: 18px; 14 | font-weight: 400; 15 | color: rgba(38, 38, 38, 1); 16 | } 17 | .detaile { 18 | font-size: 14px; 19 | font-weight: 400; 20 | color: rgba(89, 98, 106, 1); 21 | margin: 20px 0; 22 | max-width: 610px; 23 | line-height: 21px; 24 | } 25 | .link-info { 26 | font-size: 14px; 27 | font-weight: 400; 28 | color: rgba(175, 175, 175, 1); 29 | text-decoration: underline; 30 | cursor: pointer; 31 | } 32 | } 33 | &>.table-info-container { 34 | .table-item { 35 | margin: 10px 0 30px 0; 36 | .title { 37 | font-size: 20px; 38 | font-weight: 500; 39 | color: rgba(23, 43, 77, 1); 40 | margin-bottom: 12px; 41 | display: inline-block; 42 | } 43 | .add-item { 44 | float: right; 45 | margin-right: 10px; 46 | width: 25px; 47 | height: 25px; 48 | } 49 | .table { 50 | max-width: 623px; 51 | background: rgba(255, 255, 255, 1); 52 | box-shadow: 0px 2px 3px rgba(44, 40, 40, 0.11); 53 | border-radius: 5px; 54 | .hard { 55 | width: 623px; 56 | height: 45px; 57 | background: rgba(241, 243, 249, 1); 58 | line-height: 45px; 59 | padding-left: 20px; 60 | .text { 61 | font-size: 12px; 62 | font-weight: 500; 63 | color: rgba(136, 152, 170, 1); 64 | } 65 | } 66 | .body-list { 67 | ul li:nth-child(2n) { 68 | background: rgba(247, 250, 252, 1); 69 | } 70 | li { 71 | display: flex; 72 | justify-content: space-between; 73 | padding: 14px 20px; 74 | .left { 75 | span { 76 | font-size: 10px; 77 | font-weight: 400; 78 | color: rgba(23, 43, 77, 1); 79 | } 80 | } 81 | .right { 82 | .delete-icon { 83 | width: 18px; 84 | height: 18px; 85 | background: rgba(247, 250, 252, 1); 86 | box-shadow: 0px 2px 3px rgba(44, 40, 40, 0.11); 87 | border-radius: 50%; 88 | cursor: pointer; 89 | display: flex; 90 | align-items: center; 91 | justify-content: center; 92 | } 93 | } 94 | .rule-icon { 95 | background-color: #0078ff; 96 | width: 15px; 97 | height: 15px; 98 | padding: 4px; 99 | &.disabled { 100 | background-color: #becad6; 101 | } 102 | } 103 | } 104 | } 105 | } 106 | } 107 | } 108 | button { 109 | &.close { 110 | outline: none; 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /src/option/component/auxiliary/index.scss: -------------------------------------------------------------------------------- 1 | #auxiliaryPages { 2 | padding-top: 50px; 3 | & > .title { 4 | font-size: 40px; 5 | font-weight: 400; 6 | color: rgba(38, 38, 38, 1); 7 | margin-bottom: 38px; 8 | } 9 | & > .description { 10 | display: flex; 11 | flex-direction: column; 12 | .content { 13 | font-size: 18px; 14 | font-weight: 400; 15 | color: rgba(38, 38, 38, 1); 16 | } 17 | .detaile { 18 | font-size: 14px; 19 | font-weight: 400; 20 | color: rgba(89, 98, 106, 1); 21 | margin: 20px 0; 22 | max-width: 610px; 23 | line-height: 21px; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/option/component/auxiliary/index.ts: -------------------------------------------------------------------------------- 1 | import { createSwitchInfoDom } from "../../common/switchInfo"; 2 | import "./index.scss"; 3 | import { IBaseHTMLPages } from "../types"; 4 | import { Config } from "../../../core/config" 5 | import * as i18n from '../../../core/i18n' 6 | import { renderTemplate } from '../../../core/utils' 7 | 8 | export default class AuxiliaryPages implements IBaseHTMLPages { 9 | private mainDOM = document.createElement("div") 10 | static ID: string = "auxiliaryPages"; 11 | 12 | constructor() { 13 | this.mainDOM.id = AuxiliaryPages.ID 14 | } 15 | 16 | private renderSwitchInfoComponent(config: Config): HTMLElement[] { 17 | const getIconPath = (i: string) => `image/icon/option/${i}.svg`; 18 | const list = [ 19 | { 20 | title: i18n.get('optionBookmarkSearchTitle'), 21 | key: "labBookmarkSearch", 22 | content: i18n.get('optionBookmarkSearchContent'), 23 | icon: getIconPath("icons-book"), 24 | on: config.labBookmarkSearch, 25 | }, 26 | { 27 | title: i18n.get('optionDataCollectionTitle'), 28 | key: "dataCollection", 29 | i18n: 'optionDataCollectionContent', 30 | icon: getIconPath("icons-bug"), 31 | on: config.dataCollection, 32 | } 33 | ]; 34 | return list.map(e => createSwitchInfoDom(e).divElement) 35 | } 36 | 37 | public render(config: Config): HTMLElement { 38 | this.mainDOM.innerHTML = renderTemplate(` 39 |
40 |
41 |

42 | 43 |
44 |
45 |
46 |
47 | `) 48 | this.mainDOM.querySelector('.switch-info-container').children[0].replaceWith(...this.renderSwitchInfoComponent(config)) 49 | i18n.translateComponent(this.mainDOM) 50 | return this.mainDOM; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/option/component/optimization/index.scss: -------------------------------------------------------------------------------- 1 | #optimizationPages { 2 | padding-top: 50px; 3 | & > .title { 4 | font-size: 40px; 5 | font-weight: 400; 6 | color: rgba(38, 38, 38, 1); 7 | margin-bottom: 38px; 8 | } 9 | & > .description { 10 | display: flex; 11 | flex-direction: column; 12 | .content { 13 | font-size: 18px; 14 | font-weight: 400; 15 | color: rgba(38, 38, 38, 1); 16 | } 17 | .detaile { 18 | font-size: 14px; 19 | font-weight: 400; 20 | color: rgba(89, 98, 106, 1); 21 | margin: 20px 0; 22 | max-width: 610px; 23 | line-height: 21px; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/option/component/optimization/index.ts: -------------------------------------------------------------------------------- 1 | import { createSwitchInfoDom} from "../../common/switchInfo"; 2 | import "./index.scss"; 3 | import { IBaseHTMLPages } from "../types"; 4 | import { Config } from "../../../core/config" 5 | import * as i18n from '../../../core/i18n' 6 | import { renderTemplate } from '../../../core/utils' 7 | 8 | export default class OptimizationPages implements IBaseHTMLPages { 9 | private mainDOM = document.createElement("div") 10 | static ID: string = "optimizationPages"; 11 | 12 | constructor() { 13 | this.mainDOM.id = OptimizationPages.ID 14 | } 15 | 16 | private renderSwitchInfoComponent(config: Config): HTMLElement[] { 17 | const getIconPath = (i: string) => `image/icon/option/${i}.svg`; 18 | const list = [ 19 | { 20 | title: i18n.get('optionDNSSpeedTitle'), 21 | key: "dnsSpeed", 22 | content: i18n.get('optionDNSSpeedContent'), 23 | icon: getIconPath("icons-dns"), 24 | on: config.dnsSpeed, 25 | }, 26 | { 27 | title: i18n.get('optionWebPrereadingTitle'), 28 | key: "webPrereading", 29 | content: i18n.get('optionWebPrereadingContent'), 30 | icon: getIconPath("icons-in_progress"), 31 | on: config.webPrereading, 32 | }, 33 | { 34 | title: i18n.get('optionMemoryOptimizeTitle'), 35 | key: "memoryOptimize", 36 | content: i18n.get('optionMemoryOptimizeContent'), 37 | icon: getIconPath("icons-smartphone_ram"), 38 | on: config.memoryOptimize, 39 | } 40 | ]; 41 | return list.map(e => createSwitchInfoDom(e).divElement) 42 | } 43 | 44 | public render(config: Config): HTMLElement { 45 | this.mainDOM.innerHTML = renderTemplate(` 46 |
47 |
48 |

49 | 50 |
51 |
52 |
53 |
54 | `) 55 | this.mainDOM.querySelector('.switch-info-container').children[0].replaceWith(...this.renderSwitchInfoComponent(config)) 56 | i18n.translateComponent(this.mainDOM) 57 | return this.mainDOM; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/option/component/types.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '../../core/config' 2 | 3 | export interface IBaseHTMLPages { 4 | render(config: Config): HTMLElement | Promise; 5 | } 6 | -------------------------------------------------------------------------------- /src/option/component/userAside/index.scss: -------------------------------------------------------------------------------- 1 | #userAside { 2 | background-color: #fff; 3 | margin-right: 54px; 4 | .container { 5 | margin: auto; 6 | padding: 0; 7 | position: relative; 8 | height: 700px; 9 | width: 233px; 10 | .logo { 11 | width: 100px; 12 | } 13 | .user-info { 14 | margin-top: 40px; 15 | .thumb { 16 | margin-bottom: 15px; 17 | img { 18 | width: 59px; 19 | height: 59px; 20 | background: rgba(0, 0, 0, 0); 21 | box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.16); 22 | border-radius: 50%; 23 | } 24 | } 25 | .name { 26 | font-size: 16px; 27 | font-weight: bold; 28 | color: rgba(36, 43, 51, 1); 29 | margin-bottom: 4px; 30 | display: inline-block; 31 | } 32 | .description { 33 | font-size: 14px; 34 | font-weight: 400; 35 | color: rgba(138, 145, 153, 1); 36 | } 37 | } 38 | .menu { 39 | margin-top: 12px; 40 | ul.menu-container { 41 | li { 42 | min-width: 194px; 43 | &.other-title { 44 | font-size: 12px; 45 | font-weight: 600; 46 | color: rgba(138, 143, 156, 1); 47 | margin: 30px 0 10px 0; 48 | } 49 | &.options { 50 | cursor: pointer; 51 | transition:all 800ms ease 0s; 52 | & * { 53 | pointer-events: none; 54 | } 55 | &.active { 56 | background: rgba(0, 120, 255, 1); 57 | box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.1); 58 | span { 59 | color: #fff !important; 60 | } 61 | .cls-1 { 62 | fill: #fff; 63 | } 64 | margin-top: 33px; 65 | margin-bottom: 33px; 66 | } 67 | border-radius: 3px; 68 | display: flex; 69 | align-items: center; 70 | padding: 10px 15px; 71 | & .icon { 72 | margin-right: 18px; 73 | } 74 | & .name { 75 | font-size: 14px; 76 | font-weight: 400; 77 | color: rgba(138, 145, 153, 1); 78 | } 79 | } 80 | } 81 | } 82 | } 83 | .logout { 84 | width: 115px; 85 | height: 38px; 86 | border: 1px solid rgba(19, 116, 255, 1); 87 | box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.1); 88 | opacity: 1; 89 | border-radius: 5px; 90 | position: relative; 91 | cursor:pointer; 92 | span { 93 | position: absolute; 94 | left: 50%; 95 | top: 50%; 96 | transform: translateY(-50%) translateX(-50%); 97 | font-size: 14px; 98 | font-family: Noto Sans, Helvetica, 'Hiragino Sans GB', 'Microsoft Yahei', '微软雅黑', Arial, sans-serif; 99 | font-weight: 400; 100 | line-height: 29px; 101 | color: rgba(19, 116, 255, 1); 102 | opacity: 1; 103 | } 104 | position: absolute; 105 | bottom: 0; 106 | } 107 | .bottom { 108 | position: absolute; 109 | bottom: 0; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/option/option.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Cloudopt AdBlocker 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/option/option.scss: -------------------------------------------------------------------------------- 1 | #option-container { 2 | display: flex; 3 | padding-top: 27px; 4 | width: 886px; 5 | margin: 0 auto; 6 | ul.scroll-auto { 7 | max-height: 1200px; 8 | overflow-y: auto; 9 | } 10 | } -------------------------------------------------------------------------------- /src/option/option.ts: -------------------------------------------------------------------------------- 1 | import UserAside, { TOptionsType } from './component/userAside' 2 | import RouterManager from './routerManager' 3 | 4 | import '../../css/reset.css' 5 | import './option.scss' 6 | 7 | class OptionsManager { 8 | private static ID: string = 'option-container' 9 | private userAside: UserAside = new UserAside() 10 | private routerManager: RouterManager = new RouterManager() 11 | private currentRouterType: TOptionsType = 'safe' 12 | 13 | constructor() { 14 | this.init() 15 | } 16 | 17 | private init(): void { 18 | const main = document.createElement('div') 19 | main.id = OptionsManager.ID 20 | 21 | // 渲染左侧用户组件 22 | main.appendChild(this.userAside.render()) 23 | // 渲染右侧路由组件 24 | main.appendChild(this.routerManager.render()) 25 | 26 | // 默认显示智能安全 27 | this.userAside.setCurrentCheckOptions('safe') 28 | this.routerManager.renderByType('safe') 29 | 30 | document.body.appendChild(main) 31 | 32 | this.userAside.addEventListener('optionsChange', (type: TOptionsType) => { 33 | this.routerManager.renderByType(type) 34 | this.currentRouterType = type 35 | }) 36 | } 37 | } 38 | 39 | export default new OptionsManager() 40 | -------------------------------------------------------------------------------- /src/option/routerManager.ts: -------------------------------------------------------------------------------- 1 | import SafePages from './component/safe' 2 | import AdBlockPages from './component/adBlock' 3 | import OptimizationPages from './component/optimization' 4 | import AuxiliaryPages from './component/auxiliary' 5 | import AboutUsPages from './component/aboutUs' 6 | import { TOptionsType } from './component/userAside' 7 | import { IBaseHTMLPages } from './component/types' 8 | import { get as getCoreConfig, set as setCoreConfig } from '../core/config' 9 | import { renderTemplate } from '../core/utils' 10 | 11 | export default class RouterManager { 12 | private static ID: string = 'routerAside' 13 | private safePages: SafePages = new SafePages() 14 | private adBlockPages: AdBlockPages = new AdBlockPages() 15 | private optimizationPages: OptimizationPages = new OptimizationPages() 16 | private auxiliaryPages: AuxiliaryPages = new AuxiliaryPages() 17 | private aboutUsPages: AboutUsPages = new AboutUsPages() 18 | private mainDOM = document.createElement('div') 19 | 20 | constructor() { 21 | this.mainDOM.id = RouterManager.ID 22 | this.mainDOM.innerHTML = renderTemplate('
') 23 | } 24 | 25 | public async renderByType(type: TOptionsType): Promise { 26 | const config = await getCoreConfig() 27 | const map: { [key in typeof type]: IBaseHTMLPages } = { 28 | safe: this.safePages, 29 | adBlock: this.adBlockPages, 30 | optimization: this.optimizationPages, 31 | auxiliary: this.auxiliaryPages, 32 | aboutUs: this.aboutUsPages, 33 | } 34 | 35 | this.mainDOM.children[0].replaceWith(await map[type].render(config)) 36 | } 37 | 38 | public render(): HTMLElement { 39 | return this.mainDOM 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/suspend/suspend.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |

内存占用已被优化

14 |

Cloudopt Adblocker 会智能优化长时间未被访问的标签页并释放内存

15 | 16 |
17 |
18 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/suspend/suspend.ts: -------------------------------------------------------------------------------- 1 | import * as utils from '../core/utils' 2 | import * as i18n from '../core/i18n' 3 | import $ from 'jquery' 4 | 5 | $(document).ready(() => { 6 | i18n.translateCurrentPage() 7 | try { 8 | const paramStr = utils.getQueryString('p') 9 | const paramObj = JSON.parse(decodeURIComponent(atob(paramStr))) 10 | 11 | let favicon 12 | if (!paramObj.i) { 13 | favicon = (new URL(paramObj.u)).origin + '/favicon.ico' 14 | } else if (paramObj.i.startsWith('/')) { 15 | favicon = (new URL(paramObj.u)).origin + paramObj.i 16 | } else { 17 | favicon = paramObj.i 18 | } 19 | 20 | $('', { 21 | href: favicon, 22 | rel: 'shortcut icon', 23 | }).appendTo('head') 24 | 25 | document.title = $('text[i18n="memoryOptimizedIcon"]').attr('placeholder') + paramObj.t 26 | 27 | $('#return').click(() => { 28 | window.location.replace(paramObj.u) 29 | }) 30 | 31 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 32 | if (message !== 'wake-up') { 33 | return 34 | } 35 | 36 | window.location.replace(paramObj.u) 37 | }) 38 | } catch (e) { 39 | // Can't do anything if the params were broken 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "outDir": "dist", 5 | "sourceMap": false, 6 | "lib": ["es2015", "dom"], 7 | "esModuleInterop": true, 8 | "resolveJsonModule": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "quotemark": [true, "single"], 9 | "semicolon": [true, "never"], 10 | "object-literal-sort-keys": false, 11 | "ordered-imports": [true, { 12 | "import-sources-order": "any" 13 | }] 14 | }, 15 | "rulesDirectory": [] 16 | } --------------------------------------------------------------------------------