├── .eslintignore ├── .eslintrc.json ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .gitlab-ci.yml ├── .gitlab ├── CODEOWNERS └── issue_templates │ ├── bug.md │ └── change.md ├── .gitmodules ├── .npmrc ├── COPYING ├── README.md ├── build ├── .eslintrc.json ├── config │ ├── base.mjs │ ├── chrome.mjs │ ├── devenv.mjs │ ├── firefox.mjs │ ├── index.mjs │ └── webpack.config.mjs ├── configParser.mjs ├── manifest.json ├── tasks │ ├── dependencies.mjs │ ├── devenv.mjs │ ├── index.mjs │ ├── manifest.mjs │ ├── mapping.mjs │ ├── sourceDistribution.mjs │ ├── translations.mjs │ └── webpack.mjs ├── templates │ ├── info.chrome.js.tmpl │ ├── info.gecko.js.tmpl │ └── testIndex.html.tmpl └── utils │ ├── git.mjs │ ├── gulp-change-path.mjs │ ├── gulp-merge-translations.mjs │ └── wp-template-loader.js ├── composer.postload.js ├── devtools.html ├── devtools.js ├── ext ├── background.js ├── common.js ├── content.js └── devtools.js ├── gulpfile.mjs ├── icons └── detailed │ ├── abp-48.png │ └── abp-64.png ├── include.preload.js ├── inject.preload.js ├── jsdoc.conf ├── lib ├── allowlisting.js ├── browserAction.js ├── contentFiltering.js ├── csp.js ├── debug.js ├── devenvPoller.js ├── devtools.js ├── filterComposer.js ├── filterConfiguration.js ├── hitLogger.js ├── icon.js ├── io.js ├── messageResponder.js ├── messaging.js ├── ml.js ├── notificationHelper.js ├── options.js ├── popupBlocker.js ├── prefs.js ├── requestBlocker.js ├── stats.js ├── subscriptionInit.js ├── uninstall.js └── url.js ├── managed-storage-schema.json ├── options.html ├── options.js ├── package-lock.json ├── package.json ├── polyfill.js ├── qunit ├── .eslintrc.json ├── qunit.css ├── qunit.js ├── subscriptions.json └── tests │ ├── cssEscaping.js │ ├── prefs.js │ ├── subscriptionInit.js │ ├── uninstall.js │ └── url.js ├── subscriptionLink.postload.js └── test ├── .eslintrc.json ├── bin └── downloadBrowsers.mjs ├── browsers ├── chromium.mjs ├── edge.mjs └── firefox.mjs ├── entrypoint.mjs ├── helper-extension ├── background.js └── manifest.json ├── misc └── utils.mjs └── suites ├── pages ├── index.mjs ├── specialized.mjs ├── subscribe.mjs ├── uninstall.mjs └── utils.mjs └── qunit.mjs /.eslintignore: -------------------------------------------------------------------------------- 1 | lib/publicSuffixList.js 2 | qunit/qunit.js 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-eyeo", 3 | "root": true, 4 | "env": { 5 | "browser": true, 6 | "webextensions": true 7 | }, 8 | "globals": { 9 | "exports": true, 10 | "ext": true, 11 | "module": true, 12 | "require": true 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 11 16 | }, 17 | "rules": { 18 | "curly": ["error", "multi-or-nest", "consistent"] 19 | }, 20 | "overrides": [ 21 | { 22 | "files": ["*.mjs"], 23 | "parserOptions": { 24 | "sourceType": "module" 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Hi, 2 | 3 | You tried to submit a pull request for adblockpluschrome. 4 | 5 | While we love GitHub, the project is hosted on GitLab, therefore we do 6 | not monitor pull request submitted on GitHub. 7 | 8 | Please visit: https://gitlab.com/eyeo/adblockplus/adblockpluschrome 9 | 10 | Thank you. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /devenv.*/ 2 | /node_modules/ 3 | /docs/ 4 | /test/screenshots/ 5 | /.last_ui_build 6 | /adblockpluschrome-*.zip 7 | /adblockplusfirefox-*.xpi 8 | /adblockplus-*.tar.gz 9 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # This file is part of Adblock Plus , 2 | # Copyright (C) 2006-present eyeo GmbH 3 | # 4 | # Adblock Plus is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License version 3 as 6 | # published by the Free Software Foundation. 7 | # 8 | # Adblock Plus is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with Adblock Plus. If not, see . 15 | 16 | --- 17 | 18 | default: 19 | image: "registry.gitlab.com/eyeo/docker/adblockplus-ci:node12" 20 | 21 | stages: 22 | - "prepare" 23 | - "build" 24 | - "publish" 25 | - "test" 26 | 27 | variables: 28 | GIT_SUBMODULE_STRATEGY: "recursive" 29 | npm_config_audit: "false" 30 | npm_config_prefer_offline: "true" 31 | npm_config_unsafe_perm: "true" 32 | 33 | .cache: 34 | cache: &cache 35 | key: "global-cache" 36 | untracked: true 37 | paths: 38 | - "adblockpluscore/*-snapshots/download-cache/" 39 | - "adblockpluscore/chromium-snapshots/chromedriver/" 40 | - "adblockplusui/node_modules/" 41 | 42 | .default_template: 43 | needs: 44 | - job: "prepare-dependencies" 45 | before_script: 46 | - "npm install" 47 | retry: 48 | max: 2 49 | when: "stuck_or_timeout_failure" 50 | except: 51 | - "schedules" 52 | cache: 53 | <<: *cache 54 | policy: "pull" 55 | interruptible: true 56 | 57 | prepare-dependencies: 58 | stage: "prepare" 59 | script: 60 | - "git clean -x -d -ff" 61 | - "git -C adblockpluscore clean -e '/*-snapshots' -x -d -ff" 62 | - "git -C adblockplusui clean -x -d -ff" 63 | - "npm install" 64 | - "npm run download-test-browsers" 65 | except: 66 | - "schedules" 67 | cache: *cache 68 | interruptible: true 69 | 70 | ############################################################################### 71 | # Build & document # 72 | ############################################################################### 73 | 74 | .build: 75 | extends: ".default_template" 76 | stage: "build" 77 | script: 78 | - "npx gulp build -t $TARGET -c development" 79 | - "npx gulp devenv -t $TARGET" 80 | 81 | build:chrome: 82 | extends: ".build" 83 | variables: 84 | TARGET: "chrome" 85 | artifacts: 86 | paths: 87 | - "adblockpluschrome-*.zip" 88 | - "devenv.chrome/qunit/" 89 | 90 | build:firefox: 91 | extends: ".build" 92 | variables: 93 | TARGET: "firefox" 94 | artifacts: 95 | paths: 96 | - "adblockplusfirefox-*.xpi" 97 | - "devenv.firefox/qunit/" 98 | 99 | build:source: 100 | extends: ".build" 101 | script: 102 | - "npx gulp source -c development" 103 | artifacts: 104 | paths: 105 | - "adblockplus-*.tar.gz" 106 | 107 | docs: 108 | extends: ".default_template" 109 | stage: "build" 110 | script: 111 | - "npm run docs" 112 | artifacts: 113 | paths: 114 | - "docs/" 115 | expire_in: "1 week" 116 | 117 | ############################################################################### 118 | # Tests & checks # 119 | ############################################################################### 120 | 121 | .test_template: 122 | extends: ".default_template" 123 | stage: "test" 124 | before_script: 125 | - "npm install" 126 | - "unzip -q $EXTENSION_FILE -d devenv.*" 127 | variables: 128 | SKIP_BUILD: "true" 129 | artifacts: 130 | paths: 131 | - "test/screenshots/" 132 | when: "on_failure" 133 | expire_in: "1 mo" 134 | 135 | .test_template_chromium: 136 | extends: ".test_template" 137 | needs: 138 | - job: "build:chrome" 139 | artifacts: true 140 | variables: 141 | EXTENSION_FILE: "adblockpluschrome-*.zip" 142 | 143 | .test_template_firefox: 144 | extends: ".test_template" 145 | needs: 146 | - job: "build:firefox" 147 | artifacts: true 148 | variables: 149 | EXTENSION_FILE: "adblockplusfirefox-*.xpi" 150 | 151 | lint:yaml: 152 | image: "registry.gitlab.com/eyeo/docker/yamllint:1.14.0-configured" 153 | stage: "test" 154 | needs: [] 155 | script: 156 | - "yamllint .gitlab-ci.yml" 157 | except: 158 | - "schedules" 159 | interruptible: true 160 | 161 | lint:js: 162 | extends: ".default_template" 163 | stage: "test" 164 | script: 165 | - "npm run lint" 166 | 167 | audit: 168 | extends: ".default_template" 169 | stage: "test" 170 | script: 171 | - "npm audit" 172 | allow_failure: true 173 | 174 | test:firefox:oldest: 175 | extends: ".test_template_firefox" 176 | script: 177 | - "npm run test-only -- -g 'Firefox \\(oldest\\)'" 178 | 179 | test:firefox:latest: 180 | extends: ".test_template_firefox" 181 | script: 182 | - "npm run test-only -- -g 'Firefox \\(latest\\)'" 183 | 184 | test:chromium:oldest: 185 | extends: ".test_template_chromium" 186 | script: 187 | - "xvfb-run -a npm run test-only -- -g 'Chromium \\(oldest\\)'" 188 | 189 | test:chromium:latest: 190 | extends: ".test_template_chromium" 191 | script: 192 | - "xvfb-run -a npm run test-only -- -g 'Chromium \\(latest\\)'" 193 | 194 | test:edge: 195 | extends: ".test_template_chromium" 196 | before_script: 197 | - "Expand-Archive -Path $EXTENSION_FILE -DestinationPath devenv.chrome" 198 | - "choco upgrade -y nodejs --version=12.17.0" 199 | - "choco install -y microsoft-edge --version=79.0.309.71" 200 | - "npm install" 201 | script: 202 | - "npm run test-only -- -g 'Edge'" 203 | tags: 204 | - shared-windows 205 | - windows 206 | - windows-1809 207 | # https://gitlab.com/gitlab-org/gitlab-runner/-/issues/25980 208 | cache: null 209 | 210 | ############################################################################### 211 | # Public pages # 212 | ############################################################################### 213 | 214 | .pages: 215 | stage: "publish" 216 | needs: 217 | - job: "docs" 218 | artifacts: true 219 | except: 220 | - "schedules" 221 | 222 | include: 223 | project: "eyeo/adblockplus/adblockpluscore" 224 | file: ".gitlab-pages.yml" 225 | -------------------------------------------------------------------------------- /.gitlab/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * sebastian@adblockplus.org geo@adblockplus.org t.feliu@eyeo.com 2 | 3 | # Build and CI 4 | /build/ sebastian@adblockplus.org tristan@adblockplus.org t.feliu@eyeo.com 5 | /.gitlab-ci.yml sebastian@adblockplus.org tristan@adblockplus.org t.feliu@eyeo.com 6 | /gulpfile.mjs sebastian@adblockplus.org tristan@adblockplus.org t.feliu@eyeo.com 7 | /package.json sebastian@adblockplus.org tristan@adblockplus.org t.feliu@eyeo.com 8 | /package-lock.json sebastian@adblockplus.org tristan@adblockplus.org t.feliu@eyeo.com 9 | 10 | # Test automation 11 | /test/ sebastian@adblockplus.org t.feliu@eyeo.com 12 | -------------------------------------------------------------------------------- /.gitlab/issue_templates/bug.md: -------------------------------------------------------------------------------- 1 | ## Environment 2 | 3 | Replace this text with the Version of your operating system, your exact browser version, the used Adblock Plus version and the enabled Filter lists. 4 | 5 | ## How to reproduce 6 | 7 | 1. Step one of your reproduction process (e.g: Go to http://example.com/ 8 | 2. Step two of your reproduction process (e.g: Click the big red button labelled "click me" at the left) 9 | 3. Step three of your reproduction process 10 | 4. Step four of your reproduction process 11 | ... 12 | 13 | ## Observed behavior 14 | 15 | Replace this text with your detailed observations. 16 | 17 | ## Expected behavior 18 | 19 | Replace this text with your detailed expectations. 20 | 21 | /label ~bug 22 | -------------------------------------------------------------------------------- /.gitlab/issue_templates/change.md: -------------------------------------------------------------------------------- 1 | ## Background 2 | 3 | Replace this text with your reasoning that lead to wanting the change. 4 | 5 | ## What to change 6 | 7 | Replace this text with the detailed description of what exactly shall be changed/added and where. 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "adblockpluscore"] 2 | path = adblockpluscore 3 | url = https://gitlab.com/eyeo/adblockplus/adblockpluscore.git 4 | [submodule "adblockplusui"] 5 | path = adblockplusui 6 | url = https://gitlab.com/eyeo/adblockplus/abpui/adblockplusui.git 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | edgechromiumdriver_skip_download=true 2 | chromedriver_skip_download=true 3 | engine-strict=true 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Adblock Plus for Chrome, Opera, Microsoft Edge and Firefox (DEPRECATED!) 2 | ======================================================================== 3 | 4 | IMPORTANT: Deprecation notice 5 | ----------------------------- 6 | 7 | This codebase is deprecated. As of Adblock Plus 3.11, Adblock Plus for Chrome, 8 | Firefox, Microsoft Edge and Opera is based on the 9 | [adblockplusui repository](https://gitlab.com/eyeo/adblockplus/abpui/adblockplusui/). 10 | 11 | Development of the core ad blocking integration for web extensions has moved to 12 | the [webext-sdk repository](https://gitlab.com/eyeo/adblockplus/webext-sdk). 13 | 14 | --- 15 | 16 | This repository contains the platform-specific Adblock Plus source code for 17 | Chrome, Opera, Microsoft Edge and Firefox. It can be used to build 18 | Adblock Plus for these platforms. 19 | 20 | Building 21 | --------- 22 | 23 | ### Requirements 24 | 25 | - [Node.js](https://nodejs.org/) (>= 12.17.0) 26 | 27 | ### Building on Windows 28 | 29 | On Windows, you need a [Linux environment running on WSL](https://docs.microsoft.com/windows/wsl/install-win10). 30 | Then install the above requirements and run the commands below from within Bash. 31 | 32 | ### Updating the dependencies 33 | 34 | Clone the external repositories: 35 | 36 | git submodule update --init --recursive 37 | 38 | _Note: when building from a source archive, this step must be skipped._ 39 | 40 | Install the required npm packages: 41 | 42 | npm install 43 | 44 | Rerun the above commands when the dependencies might have changed, 45 | e.g. after checking out a new revison. 46 | 47 | ### Building the extension 48 | 49 | Run the following command in the project directory: 50 | 51 | npx gulp build -t {chrome|firefox} [-c development] 52 | 53 | This will create a build with a name in the form 54 | _adblockpluschrome-n.n.n.zip_ or _adblockplusfirefox-n.n.n.xpi_. These builds 55 | are unsigned. They can be submitted as-is to the extension stores, or if 56 | unpacked loaded in development mode for testing (same as devenv builds below). 57 | 58 | ### Development environment 59 | 60 | To simplify the process of testing your changes you can create an unpacked 61 | development environment. For that run one of the following command: 62 | 63 | npx gulp devenv -t {chrome|firefox} 64 | 65 | This will create a _devenv.*_ directory in the project directory. You can load 66 | the directory as an unpacked extension under _chrome://extensions_ in 67 | Chromium-based browsers, and under _about:debugging_ in Firefox. After making 68 | changes to the source code re-run the command to update the development 69 | environment, and the extension should reload automatically after a few seconds. 70 | 71 | ### Customization 72 | 73 | If you wish to create an extension based on our code and use the same 74 | build tools, we offer some customization options. 75 | 76 | This can be done by: 77 | 78 | - Specifying a path to a new configuration file relative to `gulpfile.mjs` 79 | (it should match the structure found in `build/config/`). 80 | 81 | npx gulp {build|devenv} -t {chrome|firefox} --config config.mjs 82 | 83 | - Specifying a path to a new `manifest.json` file relative to `gulpfile.mjs`. 84 | You should check `build/manifest.json` and `build/tasks/manifest.mjs` to see 85 | how we modify it. 86 | 87 | npx gulp {build|devenv} -t {chrome|firefox} -m manifest.json 88 | 89 | Running tests 90 | ------------- 91 | 92 | ### Unit tests 93 | 94 | To verify your changes you can use the unit test suite located in the _qunit_ 95 | directory of the repository. In order to run the unit tests go to the 96 | extension's Options page, open the JavaScript Console and type in: 97 | 98 | location.href = "qunit/index.html"; 99 | 100 | The unit tests will run automatically once the page loads. 101 | 102 | ### External test runner 103 | 104 | There is also an external test runner that can be invoked from the 105 | command line in order to run the unit tests along some integration 106 | tests on different browsers, and automatically run the linter as well. 107 | 108 | On Windows, in order to use the test runner, in addition to setting up a Linux 109 | environment as outlined above, you need to have Node.js installed in your native 110 | Windows environment. Then run the commands below from within PowerShell or 111 | cmd.exe (unlike when building the extension which needs to be done from Bash). 112 | 113 | On Linux, newer versions of Chromium require `libgbm`. 114 | 115 | Make sure the required packages are installed and up-to-date: 116 | 117 | npm install 118 | 119 | Start the testing process for all browsers: 120 | 121 | npm test 122 | 123 | Start the testing process in one browser only: 124 | 125 | npm test -- -g 126 | 127 | In order to run other test subsets, please check `-g` option on 128 | [Mocha's documentation](https://mochajs.org/#-grep-regexp-g-regexp). 129 | 130 | By default it downloads (and caches) and runs the tests against the 131 | oldest compatible version and the latest release version of each browser. 132 | In order to run the tests against a different version set the `CHROMIUM_BINARY`, 133 | `FIREFOX_BINARY` or `EDGE_BINARY` environment variables. Following values are 134 | accepted: 135 | 136 | * `installed` 137 | * Uses the version installed on the system. 138 | * `path:` 139 | * Uses the binary located at the given path. 140 | * `download:` 141 | * Downloads the given version (for Firefox the version must be in the 142 | form `.`, for Chromium this must be the revision number). 143 | This option is not available for Edge. 144 | 145 | Filter tests subset uses [ABP Test pages](https://testpages.adblockplus.org/). 146 | In order to run those tests on a different version of the test pages, set 147 | the _TEST_PAGES_URL_ environment variable. Additionally, in order to accept 148 | insecure `https` certificates set the _TEST_PAGES_INSECURE_ environment variable 149 | to `"true"`. 150 | 151 | [Edge Chromium](https://www.microsoft.com/en-us/edge/business/download) needs to 152 | be installed before running the Edge tests. 153 | 154 | Linting 155 | ------- 156 | 157 | You can lint the code using [ESLint](http://eslint.org). 158 | 159 | You will need to setup first. This will install our configuration 160 | [eslint-config-eyeo](https://gitlab.com/eyeo/auxiliary/eyeo-coding-style/-/tree/master/eslint-config-eyeo) 161 | and everything needed after you run: 162 | 163 | npm install 164 | 165 | Then you can run to lint the code: 166 | 167 | npm run lint 168 | -------------------------------------------------------------------------------- /build/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "rules": { 6 | "no-console": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /build/config/base.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | export default { 19 | basename: "adblockplus", 20 | version: "3.10.2", 21 | webpack: { 22 | bundles: [ 23 | { 24 | dest: "background.js", 25 | src: [ 26 | "lib/devtools.js", 27 | "lib/debug.js", 28 | "lib/requestBlocker.js", 29 | "lib/popupBlocker.js", 30 | "lib/subscriptionInit.js", 31 | "adblockplusui/lib/init.js", 32 | "lib/filterComposer.js", 33 | "lib/stats.js", 34 | "lib/uninstall.js", 35 | "lib/csp.js", 36 | "lib/contentFiltering.js", 37 | "lib/messageResponder.js", 38 | "lib/filterConfiguration.js", 39 | "lib/ml.js" 40 | ] 41 | }, 42 | { 43 | dest: "include.preload.js", 44 | src: [ 45 | "include.preload.js", 46 | "inject.preload.js" 47 | ] 48 | }, 49 | { 50 | dest: "composer.postload.js", 51 | src: [ 52 | "composer.postload.js" 53 | ] 54 | }, 55 | { 56 | dest: "subscriptionLink.postload.js", 57 | src: [ 58 | "subscriptionLink.postload.js" 59 | ] 60 | } 61 | ] 62 | }, 63 | mapping: { 64 | copy: [ 65 | { 66 | dest: "skin", 67 | src: [ 68 | "adblockplusui/skin/**", 69 | "!adblockplusui/skin/fonts/*00/**", 70 | "!adblockplusui/skin/icons/toolbar/**", 71 | "!adblockplusui/skin/icons/abp-128.png", 72 | "!adblockplusui/skin/icons/arrow.svg", 73 | "!adblockplusui/skin/icons/iconClose.svg", 74 | "!adblockplusui/skin/icons/iconCritical.svg", 75 | "!adblockplusui/skin/icons/mobile/**", 76 | "!adblockplusui/skin/mobile-options.css" 77 | ] 78 | }, 79 | { 80 | dest: "icons/detailed", 81 | src: [ 82 | "icons/detailed/*.png", 83 | "adblockplusui/skin/icons/abp-128.png" 84 | ] 85 | }, 86 | { 87 | dest: "data", 88 | src: "adblockplusui/data/*.json" 89 | }, 90 | { 91 | dest: "data/mlHideIfGraphMatches", 92 | src: [ 93 | "adblockpluscore/data/mlHideIfGraphMatches/model.json", 94 | "adblockpluscore/data/mlHideIfGraphMatches/group1-shard1of1.dat" 95 | ] 96 | }, 97 | { 98 | dest: "ext", 99 | src: [ 100 | "ext/**" 101 | ] 102 | }, 103 | { 104 | dest: "", 105 | src: [ 106 | "adblockplusui/*.js", 107 | "adblockplusui/*.html", 108 | "adblockpluscore/lib/content/snippets.js", 109 | "options.*", 110 | "devtools.*", 111 | "managed-storage-schema.json", 112 | "polyfill.js", 113 | "!adblockplusui/polyfill.js", 114 | "!adblockplusui/mobile-options.*" 115 | ] 116 | } 117 | ], 118 | rename: [ 119 | { 120 | dest: "icons/abp-16-notification.png", 121 | src: "adblockplusui/skin/icons/toolbar/notification-16.png" 122 | }, 123 | { 124 | dest: "icons/abp-16-allowlisted.png", 125 | src: "adblockplusui/skin/icons/toolbar/disabled-16.png" 126 | }, 127 | { 128 | dest: "icons/abp-16.png", 129 | src: "adblockplusui/skin/icons/toolbar/default-16.png" 130 | }, 131 | { 132 | dest: "icons/abp-20-notification.png", 133 | src: "adblockplusui/skin/icons/toolbar/notification-20.png" 134 | }, 135 | { 136 | dest: "icons/abp-20-allowlisted.png", 137 | src: "adblockplusui/skin/icons/toolbar/disabled-20.png" 138 | }, 139 | { 140 | dest: "icons/abp-20.png", 141 | src: "adblockplusui/skin/icons/toolbar/default-20.png" 142 | }, 143 | { 144 | dest: "icons/abp-32-notification.png", 145 | src: "adblockplusui/skin/icons/toolbar/notification-32.png" 146 | }, 147 | { 148 | dest: "icons/abp-32-allowlisted.png", 149 | src: "adblockplusui/skin/icons/toolbar/disabled-32.png" 150 | }, 151 | { 152 | dest: "icons/abp-32.png", 153 | src: "adblockplusui/skin/icons/toolbar/default-32.png" 154 | }, 155 | { 156 | dest: "icons/abp-40-notification.png", 157 | src: "adblockplusui/skin/icons/toolbar/notification-40.png" 158 | }, 159 | { 160 | dest: "icons/abp-40-allowlisted.png", 161 | src: "adblockplusui/skin/icons/toolbar/disabled-40.png" 162 | }, 163 | { 164 | dest: "icons/abp-40.png", 165 | src: "adblockplusui/skin/icons/toolbar/default-40.png" 166 | } 167 | ] 168 | }, 169 | translations: { 170 | dest: "_locales", 171 | src: [ 172 | "adblockplusui/locale/**/*.json", 173 | "!adblockplusui/locale/*/mobile-options.json" 174 | ] 175 | } 176 | }; 177 | -------------------------------------------------------------------------------- /build/config/chrome.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | export default { 19 | extends: "base", 20 | webpack: { 21 | alias: { 22 | info$: "info.chrome.js.tmpl" 23 | } 24 | }, 25 | translations: { 26 | dest: "_locales", 27 | src: [ 28 | "!adblockplusui/locale/es_AR/*.json", 29 | "!adblockplusui/locale/es_CL/*.json" 30 | ] 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /build/config/devenv.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | let common = { 19 | webpack: { 20 | bundles: [ 21 | { 22 | dest: "qunit/tests.js", 23 | src: ["qunit/tests/*"] 24 | }, 25 | { 26 | dest: "background.js", 27 | src: ["lib/devenvPoller.js"] 28 | } 29 | ] 30 | }, 31 | mapping: { 32 | copy: [ 33 | { 34 | dest: "qunit", 35 | src: ["qunit/qunit.*"] 36 | } 37 | ] 38 | }, 39 | tests: { 40 | scripts: [ 41 | "qunit.js", 42 | "../polyfill.js", 43 | " ../ext/common.js", 44 | "../ext/background.js", 45 | "tests.js" 46 | ] 47 | } 48 | }; 49 | 50 | export let chromeDev = {...common, extends: "chrome"}; 51 | export let firefoxDev = {...common, extends: "firefox"}; 52 | -------------------------------------------------------------------------------- /build/config/firefox.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | export default { 19 | extends: "base", 20 | webpack: { 21 | alias: { 22 | info$: "info.gecko.js.tmpl" 23 | } 24 | }, 25 | mapping: { 26 | copy: [ 27 | { 28 | dest: "skin", 29 | src: [ 30 | "adblockplusui/skin/icons/mobile/**", 31 | "adblockplusui/skin/mobile-options.css" 32 | ] 33 | }, 34 | { 35 | dest: "", 36 | src: ["adblockplusui/mobile-options.*"] 37 | } 38 | ] 39 | }, 40 | translations: { 41 | src: ["adblockplusui/locale/*/mobile-options.json"] 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /build/config/index.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | export {default as base} from "./base.mjs"; 19 | export {default as chrome} from "./chrome.mjs"; 20 | export {default as firefox} from "./firefox.mjs"; 21 | export {default as webpack} from "./webpack.config.mjs"; 22 | export {chromeDev, firefoxDev} from "./devenv.mjs"; 23 | -------------------------------------------------------------------------------- /build/config/webpack.config.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import path from "path"; 19 | 20 | let tmplLoaderPath = path.resolve("build", "utils", "wp-template-loader.js"); 21 | 22 | export default { 23 | optimization: { 24 | minimize: false 25 | }, 26 | output: { 27 | path: path.resolve("") 28 | }, 29 | node: { 30 | global: false 31 | }, 32 | resolve: { 33 | modules: [ 34 | path.resolve("lib"), 35 | path.resolve("adblockpluscore/lib"), 36 | path.resolve("adblockplusui/lib"), 37 | path.resolve("build/templates"), 38 | "node_modules" 39 | ] 40 | }, 41 | resolveLoader: { 42 | alias: { 43 | "wp-template-loader": tmplLoaderPath 44 | } 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /build/configParser.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | let configs = {}; 19 | let parsedConfigs = {}; 20 | 21 | function mergeObjectArray(base, override = []) 22 | { 23 | let result = base.slice(0); 24 | 25 | override.forEach(elem => 26 | { 27 | let commonDest = base.findIndex(baseElem => baseElem.dest == elem.dest); 28 | 29 | if (commonDest != -1) 30 | { 31 | result[commonDest] = { 32 | ...result[commonDest], 33 | src: removeDuplicates(result[commonDest].src, elem.src) 34 | }; 35 | } 36 | else 37 | { 38 | result.push(elem); 39 | } 40 | }); 41 | 42 | return result; 43 | } 44 | 45 | function removeDuplicates(base, override = []) 46 | { 47 | let unique = base.filter(elem => 48 | { 49 | let duplicate = override 50 | .find(value => value.replace(/!/, "") == elem.replace(/!/, "")); 51 | return !duplicate; 52 | }); 53 | 54 | return unique.concat(override); 55 | } 56 | 57 | function mergeWebpack(base, override) 58 | { 59 | return { 60 | alias: {...base.alias, ...override.alias}, 61 | bundles: mergeObjectArray(base.bundles, override.bundles) 62 | }; 63 | } 64 | 65 | function mergeTranslations(base, override = {}) 66 | { 67 | return { 68 | dest: override.dest || base.dest, 69 | src: removeDuplicates(base.src, override.src) 70 | }; 71 | } 72 | 73 | function mergeTests(base = {}, override = {}) 74 | { 75 | let result = {}; 76 | let baseScripts = base.scripts || []; 77 | let overrideScripts = override.scripts || []; 78 | 79 | result.scripts = [...new Set([...baseScripts, ...overrideScripts])]; 80 | 81 | return result; 82 | } 83 | 84 | function mergeMapping(base, override = {}) 85 | { 86 | let result = {}; 87 | 88 | result.copy = mergeObjectArray(base.copy, override.copy); 89 | result.rename = mergeObjectArray(base.rename, override.rename); 90 | 91 | return result; 92 | } 93 | 94 | function mergeConfig(target) 95 | { 96 | if (parsedConfigs[target]) 97 | return parsedConfigs[target]; 98 | 99 | let config = configs[target]; 100 | 101 | if (!config.extends) 102 | { 103 | parsedConfigs[target] = {...config}; 104 | parsedConfigs[target].webpack.baseConfig = configs.webpack; 105 | 106 | return parsedConfigs[target]; 107 | } 108 | 109 | let baseConfig = mergeConfig(config.extends); 110 | 111 | let version = config.version || baseConfig.version; 112 | let webpack = mergeWebpack(baseConfig.webpack, config.webpack); 113 | let translations = mergeTranslations( 114 | baseConfig.translations, 115 | config.translations); 116 | 117 | webpack.baseConfig = configs.webpack; 118 | 119 | parsedConfigs[target] = { 120 | basename: baseConfig.basename, 121 | version, 122 | webpack, 123 | mapping: mergeMapping(baseConfig.mapping, config.mapping), 124 | translations, 125 | tests: mergeTests(baseConfig.tests, config.tests) 126 | }; 127 | 128 | return parsedConfigs[target]; 129 | } 130 | 131 | export function getSection(target, section) 132 | { 133 | return mergeConfig(target)[section]; 134 | } 135 | 136 | export function setConfig(config) 137 | { 138 | configs = config; 139 | } 140 | 141 | export function hasTarget(name) 142 | { 143 | return !!configs[name]; 144 | } 145 | -------------------------------------------------------------------------------- /build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "eyeo GmbH", 3 | "applications": { 4 | "gecko": { 5 | "strict_min_version": "52.0", 6 | "app_id_release": "{d10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d}", 7 | "app_id_devbuild": "devbuild@adblockplus.org", 8 | "devbuildUpdateURL": "https://downloads.adblockplus.org/devbuilds/adblockplusfirefox/updates.json" 9 | } 10 | }, 11 | "background": { 12 | "persistent": true, 13 | "scripts": [ 14 | "polyfill.js", 15 | "ext/common.js", 16 | "ext/background.js", 17 | "background.js" 18 | ] 19 | }, 20 | "content_scripts": [ 21 | { 22 | "all_frames": true, 23 | "js": [ 24 | "polyfill.js", 25 | "ext/common.js", 26 | "ext/content.js", 27 | "include.preload.js" 28 | ], 29 | "match_about_blank": true, 30 | "matches": [ 31 | "http://*/*", 32 | "https://*/*" 33 | ], 34 | "run_at": "document_start" 35 | }, 36 | { 37 | "all_frames": true, 38 | "js": [ 39 | "composer.postload.js" 40 | ], 41 | "match_about_blank": true, 42 | "matches": [ 43 | "http://*/*", 44 | "https://*/*" 45 | ], 46 | "run_at": "document_end" 47 | }, 48 | { 49 | "all_frames": true, 50 | "js": [ 51 | "subscriptionLink.postload.js" 52 | ], 53 | "matches": [ 54 | "https://*.abpchina.org/*", 55 | "https://*.abpindo.blogspot.com/*", 56 | "https://*.abpvn.com/*", 57 | "https://*.adblock.ee/*", 58 | "https://*.adblock.gardar.net/*", 59 | "https://*.adblockplus.me/*", 60 | "https://*.adblockplus.org/*", 61 | "https://*.commentcamarche.net/*", 62 | "https://*.droit-finances.commentcamarche.com/*", 63 | "https://*.easylist.to/*", 64 | "https://*.eyeo.com/*", 65 | "https://*.fanboy.co.nz/*", 66 | "https://*.filterlists.com/*", 67 | "https://*.forums.lanik.us/*", 68 | "https://*.gitee.com/*", 69 | "https://*.gitee.io/*", 70 | "https://*.github.com/*", 71 | "https://*.github.io/*", 72 | "https://*.gitlab.com/*", 73 | "https://*.gitlab.io/*", 74 | "https://*.gurud.ee/*", 75 | "https://*.hugolescargot.com/*", 76 | "https://*.i-dont-care-about-cookies.eu/*", 77 | "https://*.journaldesfemmes.fr/*", 78 | "https://*.journaldunet.com/*", 79 | "https://*.linternaute.com/*", 80 | "https://*.spam404.com/*", 81 | "https://*.stanev.org/*", 82 | "https://*.void.gr/*", 83 | "https://*.xfiles.noads.it/*", 84 | "https://*.zoso.ro/*" 85 | ], 86 | "run_at": "document_end" 87 | } 88 | ], 89 | "manifest_version": 2, 90 | "minimum_chrome_version": "60.0", 91 | "minimum_opera_version": "47.0", 92 | "name": "__MSG_name_releasebuild__", 93 | "short_name": "__MSG_name__", 94 | "description": "__MSG_description__", 95 | "browser_action": { 96 | "default_icon": { 97 | "16": "icons/abp-16.png", 98 | "20": "icons/abp-20.png", 99 | "32": "icons/abp-32.png", 100 | "40": "icons/abp-40.png" 101 | }, 102 | "default_popup": "popup.html", 103 | "default_title": "__MSG_name__" 104 | }, 105 | "default_locale": "en_US", 106 | "devtools_page": "devtools.html", 107 | "icons": { 108 | "128": "icons/detailed/abp-128.png", 109 | "16": "icons/abp-16.png", 110 | "32": "icons/abp-32.png", 111 | "48": "icons/detailed/abp-48.png", 112 | "64": "icons/detailed/abp-64.png" 113 | }, 114 | "options_ui": { 115 | "open_in_tab": true, 116 | "page": "options.html" 117 | }, 118 | "permissions": [ 119 | "tabs", 120 | "", 121 | "contextMenus", 122 | "webRequest", 123 | "webRequestBlocking", 124 | "webNavigation", 125 | "storage", 126 | "unlimitedStorage", 127 | "notifications" 128 | ], 129 | "optional_permissions": [ 130 | "contentSettings", 131 | "management" 132 | ], 133 | "storage": { 134 | "managed_schema": "managed-storage-schema.json" 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /build/tasks/dependencies.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | 19 | import gulp from "gulp"; 20 | import fs from "fs"; 21 | import {promisify} from "util"; 22 | import glob from "glob"; 23 | import {exec} from "child_process"; 24 | import {Readable} from "stream"; 25 | import Vinyl from "vinyl"; 26 | 27 | async function getMTime(file) 28 | { 29 | return (await fs.promises.stat(file)).mtimeMs; 30 | } 31 | 32 | function createBuild() 33 | { 34 | return (promisify(exec))("bash -c \"npm run --prefix adblockplusui/ dist\""); 35 | } 36 | 37 | async function mustBuildUI(lastUIBuildTime) 38 | { 39 | let matches = await (promisify(glob))( 40 | "adblockplusui/**", 41 | { 42 | ignore: ["**/node_modules/**"] 43 | } 44 | ); 45 | 46 | return await new Promise((resolve, reject) => 47 | { 48 | Promise.all(matches.map(filename => 49 | getMTime(filename).then(mtime => 50 | { 51 | if (mtime > lastUIBuildTime) 52 | resolve(true); 53 | }) 54 | )).then(() => { resolve(false); }, reject); 55 | }); 56 | } 57 | 58 | function updateLastUIBuildTime() 59 | { 60 | return fs.promises.utimes(".last_ui_build", new Date(), new Date()); 61 | } 62 | 63 | function createLastUIBuildTime() 64 | { 65 | return new Readable.from([ 66 | new Vinyl({ 67 | path: ".last_ui_build", 68 | contents: Buffer.from("") 69 | }) 70 | ]).pipe(gulp.dest(".")); 71 | } 72 | 73 | export async function buildUI(cb) 74 | { 75 | let lastUIBuildTime; 76 | 77 | try 78 | { 79 | lastUIBuildTime = await getMTime(".last_ui_build"); 80 | } 81 | catch (e) 82 | { 83 | await createBuild(); 84 | return createLastUIBuildTime(); 85 | } 86 | 87 | if (await mustBuildUI(lastUIBuildTime)) 88 | { 89 | await createBuild(); 90 | return updateLastUIBuildTime(); 91 | } 92 | 93 | return cb(); 94 | } 95 | 96 | -------------------------------------------------------------------------------- /build/tasks/devenv.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import handlebars from "handlebars"; 19 | import fs from "fs"; 20 | import {Readable} from "stream"; 21 | import Vinyl from "vinyl"; 22 | 23 | export function addDevEnvVersion() 24 | { 25 | let randNumber = Number(new Date()).toString(); 26 | 27 | return new Readable.from([ 28 | new Vinyl({ 29 | contents: Buffer.from(randNumber), 30 | path: "devenvVersion__" 31 | }) 32 | ]); 33 | } 34 | 35 | export async function addTestsPage(templateData) 36 | { 37 | let file = await fs.promises.readFile("build/templates/testIndex.html.tmpl"); 38 | let template = handlebars.compile(file.toString()); 39 | let data = template(templateData); 40 | 41 | return new Readable.from([ 42 | new Vinyl({ 43 | contents: Buffer.from(data), 44 | path: "qunit/index.html" 45 | }) 46 | ]); 47 | } 48 | -------------------------------------------------------------------------------- /build/tasks/index.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | export {createManifest, getManifestContent} from "./manifest.mjs"; 19 | export {default as webpack} from "./webpack.mjs"; 20 | export {default as mapping} from "./mapping.mjs"; 21 | export {translations, chromeTranslations} from "./translations.mjs"; 22 | export {addDevEnvVersion, addTestsPage} from "./devenv.mjs"; 23 | export {buildUI} from "./dependencies.mjs"; 24 | export {default as sourceDistribution} from "./sourceDistribution.mjs"; 25 | -------------------------------------------------------------------------------- /build/tasks/manifest.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import {resolve} from "path"; 19 | import fs from "fs"; 20 | import {Readable} from "stream"; 21 | import Vinyl from "vinyl"; 22 | 23 | let manifest; 24 | 25 | function editManifest(data, version, channel, target) 26 | { 27 | data.version = version; 28 | data.name = `__MSG_name_${channel == "development" ? "dev" : channel}build__`; 29 | 30 | if (target == "chrome") 31 | delete data.applications; 32 | 33 | if (target == "firefox") 34 | { 35 | let gecko = { 36 | strict_min_version: data.applications.gecko.strict_min_version 37 | }; 38 | 39 | if (channel == "development") 40 | { 41 | gecko.id = data.applications.gecko.app_id_devbuild; 42 | gecko.update_url = data.applications.gecko.devbuildUpdateURL; 43 | } 44 | else 45 | { 46 | gecko.id = data.applications.gecko.app_id_release; 47 | } 48 | 49 | let composerScriptIndex = data.content_scripts.findIndex( 50 | script => script.js.includes("composer.postload.js") 51 | ); 52 | let preloadScript = data.content_scripts.find( 53 | script => script.run_at == "document_start" 54 | ); 55 | 56 | preloadScript.js.push(...data.content_scripts[composerScriptIndex].js); 57 | data.content_scripts.splice(composerScriptIndex, 1); 58 | 59 | delete data.minimum_chrome_version; 60 | delete data.minimum_opera_version; 61 | delete data.browser_action.default_popup; 62 | delete data.optional_permissions; 63 | 64 | data.applications.gecko = gecko; 65 | } 66 | 67 | return data; 68 | } 69 | 70 | export function createManifest(contents) 71 | { 72 | return new Readable.from([ 73 | new Vinyl({ 74 | contents: Buffer.from(JSON.stringify(contents, null, 2)), 75 | path: "manifest.json" 76 | }) 77 | ]); 78 | } 79 | 80 | export async function getManifestContent({target, version, channel, path}) 81 | { 82 | if (manifest) 83 | return manifest; 84 | 85 | let raw = JSON.parse( 86 | await fs.promises.readFile(resolve(path || "build/manifest.json")) 87 | ); 88 | 89 | manifest = editManifest(raw, version, channel, target); 90 | 91 | return manifest; 92 | } 93 | -------------------------------------------------------------------------------- /build/tasks/mapping.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import gulp from "gulp"; 19 | import merge from "merge-stream"; 20 | import changePath from "../utils/gulp-change-path.mjs"; 21 | 22 | export default function mapping(bundles) 23 | { 24 | return merge( 25 | bundles.copy.map(bundle => 26 | gulp.src(bundle.src) 27 | .pipe(changePath(bundle.dest)) 28 | ), 29 | bundles.rename.map(bundle => 30 | gulp.src(bundle.src) 31 | .pipe(changePath(bundle.dest, {rename: true})) 32 | ) 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /build/tasks/sourceDistribution.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import gulp from "gulp"; 19 | import tar from "gulp-tar"; 20 | import gzip from "gulp-gzip"; 21 | import {lsFiles} from "../utils/git.mjs"; 22 | 23 | export default async function sourceDistribution(filename) 24 | { 25 | let sourceFiles = await lsFiles(); 26 | return gulp.src(sourceFiles, {base: process.cwd()}) 27 | .pipe(tar(`${filename}.tar`)) 28 | .pipe(gzip()) 29 | .pipe(gulp.dest(process.cwd())); 30 | } 31 | -------------------------------------------------------------------------------- /build/tasks/translations.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import gulp from "gulp"; 19 | import mergeTranslations from "../utils/gulp-merge-translations.mjs"; 20 | import changePath from "../utils/gulp-change-path.mjs"; 21 | 22 | export function translations(locales) 23 | { 24 | return gulp.src(locales.src) 25 | .pipe(mergeTranslations( 26 | { 27 | fileName: "messages.json" 28 | })) 29 | .pipe(changePath(locales.dest)); 30 | } 31 | 32 | function getRequiredInfo(manifest) 33 | { 34 | let result = {}; 35 | let limits = { 36 | name: 12, 37 | name_releasebuild: 45, 38 | name_devbuild: 45, 39 | description: 132 40 | }; 41 | 42 | result.fields = Object.values(manifest) 43 | .filter(value => typeof value == "string" && value.match("__MSG")) 44 | .map(name => 45 | { 46 | let parsed = name.replace(/(__MSG_)|(__)/g, ""); 47 | return { 48 | name: parsed, 49 | limit: limits[parsed] 50 | }; 51 | }); 52 | 53 | result.locale = manifest["default_locale"]; 54 | 55 | return result; 56 | } 57 | 58 | export function chromeTranslations(locales, manifest) 59 | { 60 | return gulp.src(locales.src) 61 | .pipe(mergeTranslations( 62 | { 63 | fileName: "messages.json", 64 | defaults: getRequiredInfo(manifest) 65 | })) 66 | .pipe(changePath( 67 | locales.dest, 68 | { 69 | match: /es_MX/g, 70 | replace: "es_419" 71 | } 72 | )); 73 | } 74 | -------------------------------------------------------------------------------- /build/tasks/webpack.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import gulp from "gulp"; 19 | import merge from "merge-stream"; 20 | import webpackStream from "webpack-stream"; 21 | import webpackMerge from "webpack-merge"; 22 | 23 | export default function webpack({webpackInfo, addonName, addonVersion, 24 | sourceMapType}) 25 | { 26 | return merge(webpackInfo.bundles.map(bundle => 27 | gulp.src(bundle.src) 28 | .pipe(webpackStream( 29 | { 30 | quiet: true, 31 | config: webpackMerge.merge( 32 | webpackInfo.baseConfig, 33 | { 34 | devtool: sourceMapType, 35 | output: { 36 | filename: bundle.dest 37 | }, 38 | resolve: { 39 | alias: webpackInfo.alias, 40 | symlinks: false 41 | }, 42 | module: { 43 | rules: [ 44 | { 45 | test: /info.?/, 46 | loader: "wp-template-loader", 47 | options: { 48 | data: {addonName, addonVersion} 49 | } 50 | } 51 | ] 52 | } 53 | }) 54 | })) 55 | )); 56 | } 57 | -------------------------------------------------------------------------------- /build/templates/info.chrome.js.tmpl: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | let platformVersion = null; 21 | let application = null; 22 | let applicationVersion; 23 | 24 | let regexp = /(\S+)\/(\S+)(?:\s*\(.*?\))?/g; 25 | let match; 26 | 27 | while (match = regexp.exec(navigator.userAgent)) 28 | { 29 | let app = match[1]; 30 | let ver = match[2]; 31 | 32 | if (app == "Chrome") 33 | { 34 | platformVersion = ver; 35 | } 36 | else if (app != "Mozilla" && app != "AppleWebKit" && app != "Safari") 37 | { 38 | // For compatibility with legacy websites, Chrome's UA 39 | // also includes a Mozilla, AppleWebKit and Safari token. 40 | // Any further name/version pair indicates a fork. 41 | application = app == "OPR" ? "opera" : app.toLowerCase(); 42 | applicationVersion = ver; 43 | } 44 | } 45 | 46 | // not a Chromium-based UA, probably modifed by the user 47 | if (!platformVersion) 48 | { 49 | application = "unknown"; 50 | applicationVersion = platformVersion = "0"; 51 | } 52 | 53 | // no additional name/version, so this is upstream Chrome 54 | if (!application) 55 | { 56 | application = "chrome"; 57 | applicationVersion = platformVersion; 58 | } 59 | 60 | 61 | exports.addonName = "{{addonName}}"; 62 | exports.addonVersion = "{{addonVersion}}"; 63 | 64 | exports.application = application; 65 | exports.applicationVersion = applicationVersion; 66 | 67 | exports.platform = "chromium"; 68 | exports.platformVersion = platformVersion; 69 | -------------------------------------------------------------------------------- /build/templates/info.gecko.js.tmpl: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | exports.addonName = "{{addonName}}"; 21 | exports.addonVersion = "{{addonVersion}}"; 22 | 23 | exports.application = "unknown"; 24 | exports.applicationVersion = "0"; 25 | 26 | exports.platform = "gecko"; 27 | exports.platformVersion = "0"; 28 | 29 | let match = /\brv:(\d+(?:\.\d+)?)\b/.exec(navigator.userAgent); 30 | if (match) 31 | exports.platformVersion = match[1]; 32 | 33 | browser.runtime.getBrowserInfo().then(browserInfo => 34 | { 35 | exports.application = browserInfo.name.toLowerCase(); 36 | exports.applicationVersion = browserInfo.version; 37 | }); 38 | -------------------------------------------------------------------------------- /build/templates/testIndex.html.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 21 | 22 | 23 | 24 | {{#each scripts }} 25 | 26 | {{/each}} 27 | 28 | 29 |

{{ addonName }} unit tests

30 |

31 |
32 |

33 |
    34 | 35 | 36 | -------------------------------------------------------------------------------- /build/utils/git.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import argparse from "argparse"; 19 | import {pathToFileURL} from "url"; 20 | import {execFile} from "child_process"; 21 | import {promisify} from "util"; 22 | import {EOL} from "os"; 23 | 24 | const BUILDNUM_OFFSET = 10000; 25 | 26 | export async function getBuildnum(revision = "HEAD") 27 | { 28 | let until = (await promisify(execFile)("git", ["log", "--pretty=%ct", "-n1", 29 | revision])).stdout.trim(); 30 | 31 | return BUILDNUM_OFFSET + 32 | parseInt((await promisify(execFile)("git", ["rev-list", "--count", 33 | "--until", until, 34 | "origin/next", 35 | "origin/master", 36 | revision])).stdout, 10); 37 | } 38 | 39 | export async function lsFiles() 40 | { 41 | let {stdout} = await promisify(execFile)( 42 | "git", ["ls-files", "--recurse-submodules"] 43 | ); 44 | return stdout.trim().split(EOL); 45 | } 46 | 47 | if (import.meta.url == pathToFileURL(process.argv[1])) 48 | { 49 | let parser = argparse.ArgumentParser(); 50 | parser.addArgument(["-r", "--revision"], 51 | {required: false, defaultValue: "HEAD"}); 52 | let args = parser.parseArgs(); 53 | 54 | (async() => 55 | { 56 | try 57 | { 58 | console.log(await getBuildnum(args.revision)); 59 | } 60 | catch (err) 61 | { 62 | console.error(err); 63 | process.exit(1); 64 | } 65 | })(); 66 | } 67 | -------------------------------------------------------------------------------- /build/utils/gulp-change-path.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import {Transform} from "stream"; 19 | import path from "path"; 20 | 21 | function changePath(destination, custom = {}) 22 | { 23 | let transform = new Transform({objectMode: true}); 24 | 25 | transform._transform = (file, encoding, cb) => 26 | { 27 | if (custom.match && file.path.match(custom.match)) 28 | { 29 | file.path = path.join(destination, 30 | file.relative.replace(custom.match, custom.replace) 31 | ); 32 | } 33 | else if (custom.rename) 34 | { 35 | file.path = destination; 36 | } 37 | else 38 | { 39 | file.path = path.join(destination, file.relative); 40 | } 41 | 42 | file.base = null; 43 | cb(null, file); 44 | }; 45 | 46 | return transform; 47 | } 48 | 49 | export default changePath; 50 | -------------------------------------------------------------------------------- /build/utils/gulp-merge-translations.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import through from "through"; 19 | import Vinyl from "vinyl"; 20 | import path from "path"; 21 | 22 | const PLUGIN_NAME = "gulp-merge-translations"; 23 | 24 | function mergeTranslations(options = {}) 25 | { 26 | let merged = {}; 27 | let info; 28 | let mandatoryInfo = {}; 29 | let fields = options.defaults ? options.defaults.fields : []; 30 | 31 | function getLocaleName(fullPath) 32 | { 33 | let parts = fullPath.split(path.sep); 34 | 35 | return parts[parts.length - 2]; 36 | } 37 | 38 | function truncate(text, limit) 39 | { 40 | if (text.length <= limit) 41 | return text; 42 | return text.slice(0, limit - 1).concat("\u2026"); 43 | } 44 | 45 | function groupByLocale(file) 46 | { 47 | if (file.isBuffer()) 48 | { 49 | try 50 | { 51 | let locale = getLocaleName(file.path); 52 | let content = JSON.parse(file.contents.toString()); 53 | 54 | info = info || { 55 | cwd: file.cwd, 56 | base: file.base 57 | }; 58 | 59 | if (options.defaults) 60 | { 61 | fields.forEach(field => 62 | { 63 | if (content[field.name]) 64 | { 65 | content[field.name] = { 66 | message: field.limit ? 67 | truncate(content[field.name].message, field.limit) : 68 | content[field.name].message, 69 | description: content[field.name].description 70 | }; 71 | 72 | if (locale == options.defaults.locale) 73 | mandatoryInfo[field.name] = content[field.name]; 74 | } 75 | }); 76 | } 77 | 78 | merged[locale] = merged[locale] || {}; 79 | merged[locale] = {...merged[locale], ...content}; 80 | } 81 | catch (error) 82 | { 83 | let msg = `${PLUGIN_NAME} parsing: ${file.path} : ${error.message}`; 84 | this.emit("error", msg); 85 | } 86 | } 87 | } 88 | 89 | function emitByLocale() 90 | { 91 | Object.keys(merged).forEach(localeName => 92 | { 93 | let mergedFile = merged[localeName]; 94 | 95 | if (options.defaults) 96 | mergedFile = {...mandatoryInfo, ...mergedFile}; 97 | 98 | this.emit("data", new Vinyl({ 99 | contents: Buffer.from(JSON.stringify(mergedFile, null, 2)), 100 | cwd: info.cwd, 101 | base: info.base, 102 | path: path.join(info.base, localeName, options.fileName) 103 | })); 104 | }); 105 | 106 | this.emit("end"); 107 | } 108 | 109 | return through(groupByLocale, emitByLocale); 110 | } 111 | 112 | export default mergeTranslations; 113 | -------------------------------------------------------------------------------- /build/utils/wp-template-loader.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | const handlebars = require("handlebars"); 21 | 22 | module.exports = function wpTemplateLoader(source) 23 | { 24 | let template = handlebars.compile(source); 25 | 26 | return template(this.query.data); 27 | }; 28 | -------------------------------------------------------------------------------- /devtools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /devtools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | let panelWindow = null; 21 | 22 | // Versions of Firefox before 54 do not support the devtools.panels API; on 23 | // these platforms, even when the option is enabled, we cannot show the 24 | // devtools panel. 25 | if ("panels" in browser.devtools) 26 | { 27 | browser.runtime.sendMessage({type: "prefs.get", 28 | key: "show_devtools_panel"}).then(enabled => 29 | { 30 | if (enabled) 31 | { 32 | browser.devtools.panels.create("Adblock Plus", 33 | "icons/abp-32.png", 34 | "devtools-panel.html").then(panel => 35 | { 36 | panel.onShown.addListener(window => 37 | { 38 | panelWindow = window; 39 | }); 40 | 41 | panel.onHidden.addListener(window => 42 | { 43 | panelWindow = null; 44 | }); 45 | 46 | if (panel.onSearch) 47 | { 48 | panel.onSearch.addListener((eventName, queryString) => 49 | { 50 | if (panelWindow) 51 | panelWindow.postMessage({type: eventName, queryString}, "*"); 52 | }); 53 | } 54 | }); 55 | } 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /ext/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | { 21 | self.ext = {}; 22 | 23 | let EventTarget = ext._EventTarget = function() 24 | { 25 | this._listeners = new Set(); 26 | }; 27 | EventTarget.prototype = { 28 | addListener(listener) 29 | { 30 | this._listeners.add(listener); 31 | }, 32 | removeListener(listener) 33 | { 34 | this._listeners.delete(listener); 35 | }, 36 | _dispatch(...args) 37 | { 38 | let results = []; 39 | 40 | for (let listener of this._listeners) 41 | results.push(listener(...args)); 42 | 43 | return results; 44 | } 45 | }; 46 | 47 | 48 | /* Message passing */ 49 | 50 | ext.onMessage = new ext._EventTarget(); 51 | } 52 | -------------------------------------------------------------------------------- /ext/content.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1408996 4 | let ext = window.ext; // eslint-disable-line no-redeclare 5 | 6 | // Firefox 55 erroneously sends messages from the content script to the 7 | // devtools panel: 8 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1383310 9 | // As a workaround, listen for messages only if this isn't the devtools panel. 10 | // Note that Firefox processes API access lazily, so browser.devtools will 11 | // always exist but will have undefined as its value on other pages. 12 | if (!browser.devtools) 13 | { 14 | // Listen for messages from the background page. 15 | browser.runtime.onMessage.addListener((message, sender, sendResponse) => 16 | { 17 | return ext.onMessage._dispatch(message, {}, sendResponse).includes(true); 18 | }); 19 | } 20 | 21 | { 22 | let port = null; 23 | let registeredListeners = null; 24 | 25 | ext.onExtensionUnloaded = { 26 | addListener(listener) 27 | { 28 | if (!port) 29 | { 30 | port = browser.runtime.connect(); 31 | registeredListeners = 0; 32 | } 33 | 34 | if (!port.onDisconnect.hasListener(listener)) 35 | { 36 | // When the extension is reloaded, disabled or uninstalled the 37 | // background page dies and automatically disconnects all ports 38 | port.onDisconnect.addListener(listener); 39 | registeredListeners++; 40 | } 41 | }, 42 | removeListener(listener) 43 | { 44 | if (port) 45 | { 46 | if (port.onDisconnect.hasListener(listener)) 47 | { 48 | port.onDisconnect.removeListener(listener); 49 | registeredListeners--; 50 | } 51 | 52 | if (registeredListeners == 0) 53 | { 54 | port.disconnect(); 55 | port = null; 56 | registeredListeners = null; 57 | } 58 | } 59 | } 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /ext/devtools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | { 21 | let inspectedTabId = browser.devtools.inspectedWindow.tabId; 22 | let port = browser.runtime.connect({name: "devtools-" + inspectedTabId}); 23 | 24 | ext.onMessage = port.onMessage; 25 | ext.devtools = browser.devtools; 26 | } 27 | -------------------------------------------------------------------------------- /gulpfile.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import gulp from "gulp"; 19 | import argparse from "argparse"; 20 | import merge from "merge-stream"; 21 | import zip from "gulp-vinyl-zip"; 22 | import del from "del"; 23 | import * as tasks from "./build/tasks/index.mjs"; 24 | import * as config from "./build/config/index.mjs"; 25 | import * as configParser from "./build/configParser.mjs"; 26 | import * as gitUtils from "./build/utils/git.mjs"; 27 | import url from "url"; 28 | 29 | let argumentParser = new argparse.ArgumentParser({ 30 | description: "Build the extension" 31 | }); 32 | 33 | argumentParser.addArgument( 34 | ["-t", "--target"], 35 | {choices: ["chrome", "firefox"]} 36 | ); 37 | argumentParser.addArgument( 38 | ["-c", "--channel"], 39 | { 40 | choices: ["development", "release"], 41 | defaultValue: "release" 42 | } 43 | ); 44 | argumentParser.addArgument(["-b", "--build-num"]); 45 | argumentParser.addArgument("--config"); 46 | argumentParser.addArgument(["-m", "--manifest"]); 47 | 48 | let args = argumentParser.parseKnownArgs()[0]; 49 | 50 | let targetDir = `devenv.${args.target}`; 51 | 52 | async function getBuildSteps(options) 53 | { 54 | let translations = options.target == "chrome" ? 55 | tasks.chromeTranslations : 56 | tasks.translations; 57 | let buildSteps = []; 58 | let addonName = `${options.basename}${options.target}`; 59 | 60 | if (options.isDevenv) 61 | { 62 | buildSteps.push( 63 | tasks.addDevEnvVersion(), 64 | await tasks.addTestsPage({scripts: options.tests.scripts, addonName}) 65 | ); 66 | } 67 | 68 | buildSteps.push( 69 | tasks.mapping(options.mapping), 70 | tasks.webpack({ 71 | webpackInfo: options.webpackInfo, 72 | addonName, 73 | addonVersion: options.version, 74 | sourceMapType: options.sourceMapType 75 | }), 76 | tasks.createManifest(options.manifest), 77 | translations(options.translations, options.manifest) 78 | ); 79 | 80 | return buildSteps; 81 | } 82 | 83 | async function getBuildOptions(isDevenv, isSource) 84 | { 85 | if (!isSource && !args.target) 86 | argumentParser.error("Argument \"-t/--target\" is required"); 87 | 88 | let opts = { 89 | isDevenv, 90 | target: args.target, 91 | channel: args.channel, 92 | archiveType: args.target == "chrome" ? ".zip" : ".xpi" 93 | }; 94 | 95 | opts.sourceMapType = opts.target == "chrome" ? 96 | isDevenv == true ? "inline-cheap-source-maps" : "none" : 97 | "source-maps"; 98 | 99 | if (args.config) 100 | configParser.setConfig(await import(url.pathToFileURL(args.config))); 101 | else 102 | configParser.setConfig(config); 103 | 104 | let configName; 105 | if (isSource) 106 | configName = "base"; 107 | else if (isDevenv && configParser.hasTarget(`${opts.target}Dev`)) 108 | configName = `${opts.target}Dev`; 109 | else 110 | configName = opts.target; 111 | 112 | opts.webpackInfo = configParser.getSection(configName, "webpack"); 113 | opts.mapping = configParser.getSection(configName, "mapping"); 114 | opts.tests = configParser.getSection(configName, "tests"); 115 | opts.basename = configParser.getSection(configName, "basename"); 116 | opts.version = configParser.getSection(configName, "version"); 117 | opts.translations = configParser.getSection(configName, "translations"); 118 | 119 | if (isDevenv) 120 | { 121 | opts.output = gulp.dest(targetDir); 122 | } 123 | else 124 | { 125 | if (opts.channel == "development") 126 | { 127 | opts.version = args["build_num"] ? 128 | opts.version.concat(".", args["build_num"]) : 129 | opts.version.concat(".", await gitUtils.getBuildnum()); 130 | } 131 | 132 | opts.output = zip.dest( 133 | `${opts.basename}${opts.target}-${opts.version}${opts.archiveType}` 134 | ); 135 | } 136 | 137 | opts.manifest = await tasks.getManifestContent({ 138 | target: opts.target, 139 | version: opts.version, 140 | channel: opts.channel, 141 | path: args.manifest 142 | }); 143 | 144 | return opts; 145 | } 146 | 147 | async function buildDevenv() 148 | { 149 | let options = await getBuildOptions(true); 150 | 151 | return merge(await getBuildSteps(options)) 152 | .pipe(options.output); 153 | } 154 | 155 | async function buildPacked() 156 | { 157 | let options = await getBuildOptions(false); 158 | 159 | return merge(await getBuildSteps(options)) 160 | .pipe(options.output); 161 | } 162 | 163 | function cleanDir() 164 | { 165 | return del(targetDir); 166 | } 167 | 168 | export let devenv = gulp.series( 169 | cleanDir, 170 | tasks.buildUI, 171 | buildDevenv 172 | ); 173 | 174 | export let build = gulp.series( 175 | tasks.buildUI, 176 | buildPacked 177 | ); 178 | 179 | export async function source() 180 | { 181 | let options = await getBuildOptions(false, true); 182 | return tasks.sourceDistribution(`${options.basename}-${options.version}`); 183 | } 184 | 185 | function startWatch() 186 | { 187 | gulp.watch( 188 | [ 189 | "*.js", 190 | "*.html", 191 | "qunit/**", 192 | "lib/*", 193 | "ext/*", 194 | "adblockpluscore/lib/*", 195 | "adblockplusui/*.js", 196 | "!gulpfile.js" 197 | ], 198 | { 199 | ignoreInitial: false 200 | }, 201 | gulp.series( 202 | cleanDir, 203 | buildDevenv 204 | ) 205 | ); 206 | } 207 | 208 | export let watch = gulp.series( 209 | tasks.buildUI, 210 | startWatch 211 | ); 212 | -------------------------------------------------------------------------------- /icons/detailed/abp-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adblockplus/adblockpluschrome/1affa87724a7334e589c9a7bb197da8d5e5bf878/icons/detailed/abp-48.png -------------------------------------------------------------------------------- /icons/detailed/abp-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adblockplus/adblockpluschrome/1affa87724a7334e589c9a7bb197da8d5e5bf878/icons/detailed/abp-64.png -------------------------------------------------------------------------------- /jsdoc.conf: -------------------------------------------------------------------------------- 1 | { 2 | "opts": { 3 | "access": "all" 4 | }, 5 | "templates": { 6 | "default": { 7 | "useLongnameInNav": true 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/browserAction.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | /** @module browserAction */ 19 | 20 | "use strict"; 21 | 22 | let changesByTabId = new Map(); 23 | let badgeStateByPage = new ext.PageMap(); 24 | 25 | function setBadgeState(tabId, key, value) 26 | { 27 | let page = new ext.Page(tabId); 28 | let badgeState = badgeStateByPage.get(page); 29 | 30 | if (!badgeState) 31 | { 32 | badgeState = { 33 | hiddenState: "visible", 34 | text: "" 35 | }; 36 | badgeStateByPage.set(page, badgeState); 37 | } 38 | 39 | // We need to ignore any text changes while we're hiding the badge 40 | if (!(badgeState.hiddenState == "hiding" && key == "text")) 41 | badgeState[key] = value; 42 | 43 | return badgeState; 44 | } 45 | 46 | function applyChanges(tabId, changes) 47 | { 48 | return Promise.all(Object.keys(changes).map(change => 49 | { 50 | // Firefox for Android displays the browser action not as an icon but 51 | // as a menu item. There is no icon, but such an option may be added 52 | // in the future. 53 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1331746 54 | if (change == "iconPath" && "setIcon" in browser.browserAction) 55 | { 56 | return browser.browserAction.setIcon({ 57 | tabId, 58 | path: { 59 | 16: changes.iconPath.replace("$size", "16"), 60 | 20: changes.iconPath.replace("$size", "20"), 61 | 32: changes.iconPath.replace("$size", "32"), 62 | 40: changes.iconPath.replace("$size", "40") 63 | } 64 | }); 65 | } 66 | 67 | if (change == "iconImageData" && "setIcon" in browser.browserAction) 68 | { 69 | return browser.browserAction.setIcon({ 70 | tabId, 71 | imageData: changes.iconImageData 72 | }); 73 | } 74 | 75 | // There is no badge on Firefox for Android; the browser action is 76 | // simply a menu item. 77 | if (change == "badgeText" && "setBadgeText" in browser.browserAction) 78 | { 79 | // Remember changes to the badge text but don't apply them yet 80 | // as long as the badge is hidden. 81 | let badgeState = setBadgeState(tabId, "text", changes.badgeText); 82 | if (badgeState.hiddenState == "hidden") 83 | return; 84 | 85 | return browser.browserAction.setBadgeText({ 86 | tabId, 87 | text: changes.badgeText 88 | }); 89 | } 90 | 91 | // There is no badge on Firefox for Android; the browser action is 92 | // simply a menu item. 93 | if (change == "badgeColor" && 94 | "setBadgeBackgroundColor" in browser.browserAction) 95 | { 96 | return browser.browserAction.setBadgeBackgroundColor({ 97 | tabId, 98 | color: changes.badgeColor 99 | }); 100 | } 101 | })); 102 | } 103 | 104 | function addChange(tabId, name, value) 105 | { 106 | let changes = changesByTabId.get(tabId); 107 | if (!changes) 108 | { 109 | changes = {}; 110 | changesByTabId.set(tabId, changes); 111 | } 112 | changes[name] = value; 113 | 114 | function cleanup() 115 | { 116 | changesByTabId.delete(tabId); 117 | } 118 | 119 | function onReplaced(addedTabId, removedTabId) 120 | { 121 | if (addedTabId == tabId) 122 | { 123 | browser.tabs.onReplaced.removeListener(onReplaced); 124 | applyChanges(tabId, changes) 125 | .then(cleanup) 126 | .catch(cleanup); 127 | } 128 | } 129 | 130 | if (!browser.tabs.onReplaced.hasListener(onReplaced)) 131 | { 132 | applyChanges(tabId, changes) 133 | .then(cleanup) 134 | .catch(() => 135 | { 136 | // If the tab is prerendered, browser.browserAction.set* fails 137 | // and we have to delay our changes until the currently visible tab 138 | // is replaced with the prerendered tab. 139 | browser.tabs.onReplaced.addListener(onReplaced); 140 | }); 141 | } 142 | } 143 | 144 | /** 145 | * Sets icon badge for given tab. 146 | * 147 | * @param {number} tabId 148 | * @param {object} badge 149 | * @param {string} badge.color 150 | * @param {string} badge.number 151 | */ 152 | exports.setBadge = (tabId, badge) => 153 | { 154 | if (!badge) 155 | { 156 | addChange(tabId, "badgeText", ""); 157 | } 158 | else 159 | { 160 | if ("number" in badge) 161 | addChange(tabId, "badgeText", badge.number.toString()); 162 | 163 | if ("color" in badge) 164 | addChange(tabId, "badgeColor", badge.color); 165 | } 166 | }; 167 | 168 | /** 169 | * Sets icon image for given tab using image data. 170 | * 171 | * @param {number} tabId 172 | * @param {object} imageData 173 | */ 174 | exports.setIconImageData = (tabId, imageData) => 175 | { 176 | addChange(tabId, "iconImageData", imageData); 177 | }; 178 | 179 | /** 180 | * Sets icon image for given tab using file path. 181 | * 182 | * @param {number} tabId 183 | * @param {string} path - expected to include "$size" placeholder 184 | */ 185 | exports.setIconPath = (tabId, path) => 186 | { 187 | addChange(tabId, "iconPath", path); 188 | }; 189 | 190 | /** 191 | * Toggles icon badge for given tab. 192 | * 193 | * @param {number} tabId 194 | * @param {boolean} shouldHide 195 | */ 196 | exports.toggleBadge = (tabId, shouldHide) => 197 | { 198 | if (shouldHide) 199 | { 200 | setBadgeState(tabId, "hiddenState", "hiding"); 201 | addChange(tabId, "badgeText", ""); 202 | setBadgeState(tabId, "hiddenState", "hidden"); 203 | } 204 | else 205 | { 206 | let badgeState = setBadgeState(tabId, "hiddenState", "visible"); 207 | addChange(tabId, "badgeText", badgeState.text); 208 | } 209 | }; 210 | -------------------------------------------------------------------------------- /lib/csp.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | const {defaultMatcher} = require("../adblockpluscore/lib/matcher"); 21 | const {AllowingFilter} = require("../adblockpluscore/lib/filterClasses"); 22 | const {contentTypes} = require("../adblockpluscore/lib/contentTypes"); 23 | const {parseURL} = require("../adblockpluscore/lib/url"); 24 | const {extractHostFromFrame} = require("./url"); 25 | const {checkAllowlisted} = require("./allowlisting"); 26 | const {filterNotifier} = require("filterNotifier"); 27 | const {logRequest} = require("./hitLogger"); 28 | const {recordBlockedRequest} = require("./stats"); 29 | 30 | browser.webRequest.onHeadersReceived.addListener(details => 31 | { 32 | let url = parseURL(details.url); 33 | let parentFrame = ext.getFrame(details.tabId, details.parentFrameId); 34 | let hostname = extractHostFromFrame(parentFrame) || url.hostname; 35 | 36 | let cspMatch = defaultMatcher.match(url, contentTypes.CSP, 37 | hostname, null, false); 38 | if (cspMatch) 39 | { 40 | let page = new ext.Page({id: details.tabId, url: details.url}); 41 | let frame = ext.getFrame(details.tabId, details.frameId); 42 | 43 | if (checkAllowlisted(page, frame)) 44 | return; 45 | 46 | // To avoid an extra match for the common case we assumed no 47 | // $genericblock filters applied when searching for a matching $csp filter. 48 | // We must now pay the price by first checking for a $genericblock filter 49 | // and if necessary that our $csp filter is specific. 50 | let specificOnly = !!checkAllowlisted(page, frame, null, 51 | contentTypes.GENERICBLOCK); 52 | if (specificOnly && !(cspMatch instanceof AllowingFilter)) 53 | { 54 | cspMatch = defaultMatcher.match(url, contentTypes.CSP, 55 | hostname, null, specificOnly); 56 | if (!cspMatch) 57 | return; 58 | } 59 | 60 | if (cspMatch instanceof AllowingFilter) 61 | { 62 | logRequest([details.tabId], { 63 | url: details.url, type: "CSP", docDomain: hostname, 64 | specificOnly 65 | }, cspMatch); 66 | recordBlockedRequest(cspMatch, [details.tabId]); 67 | return; 68 | } 69 | 70 | let {blocking} = defaultMatcher.search(url, contentTypes.CSP, hostname, 71 | null, specificOnly, "blocking"); 72 | for (cspMatch of blocking) 73 | { 74 | logRequest([details.tabId], { 75 | url: details.url, type: "CSP", docDomain: hostname, 76 | specificOnly 77 | }, cspMatch); 78 | recordBlockedRequest(cspMatch, [details.tabId]); 79 | 80 | details.responseHeaders.push({ 81 | name: "Content-Security-Policy", 82 | value: cspMatch.csp 83 | }); 84 | } 85 | 86 | return {responseHeaders: details.responseHeaders}; 87 | } 88 | }, { 89 | urls: ["http://*/*", "https://*/*"], 90 | types: ["main_frame", "sub_frame"] 91 | }, ["blocking", "responseHeaders"]); 92 | -------------------------------------------------------------------------------- /lib/debug.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | const {port} = require("messaging"); 21 | 22 | let lastError = null; 23 | 24 | function safeToString(value) 25 | { 26 | try 27 | { 28 | return String(value); 29 | } 30 | catch (e) 31 | { 32 | return ""; 33 | } 34 | } 35 | 36 | self.addEventListener("error", event => 37 | { 38 | lastError = safeToString(event.error); 39 | }); 40 | 41 | self.addEventListener("unhandledrejection", event => 42 | { 43 | lastError = safeToString(event.reason); 44 | }); 45 | 46 | let consoleError = console.error; 47 | console.error = function error(...args) 48 | { 49 | lastError = args.map(safeToString).join(" "); 50 | consoleError.apply(this, args); 51 | }; 52 | 53 | port.on("debug.getLastError", () => 54 | { 55 | let error = lastError; 56 | lastError = null; 57 | return error; 58 | }); 59 | -------------------------------------------------------------------------------- /lib/devenvPoller.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | let version = null; 21 | 22 | function doPoll() 23 | { 24 | fetch(browser.extension.getURL("devenvVersion__")) 25 | .then(response => response.text()) 26 | .then(text => 27 | { 28 | if (version == null) 29 | version = text; 30 | 31 | if (text != version) 32 | browser.runtime.reload(); 33 | else 34 | self.setTimeout(doPoll, 5000); 35 | }); 36 | } 37 | 38 | doPoll(); 39 | -------------------------------------------------------------------------------- /lib/hitLogger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | /** @module hitLogger */ 19 | 20 | "use strict"; 21 | 22 | const {extractHostFromFrame} = require("./url"); 23 | const {EventEmitter} = require("../adblockpluscore/lib/events"); 24 | const {filterStorage} = require("../adblockpluscore/lib/filterStorage"); 25 | const {port} = require("./messaging"); 26 | const {Filter, 27 | ElemHideFilter} = require("../adblockpluscore/lib/filterClasses"); 28 | const {contentTypes} = require("../adblockpluscore/lib/contentTypes"); 29 | 30 | const nonRequestTypes = exports.nonRequestTypes = [ 31 | "DOCUMENT", "ELEMHIDE", "SNIPPET", "GENERICBLOCK", "GENERICHIDE", "CSP" 32 | ]; 33 | 34 | let eventEmitter = new EventEmitter(); 35 | 36 | /** 37 | * @namespace 38 | * @static 39 | */ 40 | let HitLogger = exports.HitLogger = { 41 | /** 42 | * Adds a listener for requests, filter hits etc related to the tab. 43 | * 44 | * Note: Calling code is responsible for removing the listener again, 45 | * it will not be automatically removed when the tab is closed. 46 | * 47 | * @param {number} tabId 48 | * @param {function} listener 49 | */ 50 | addListener: eventEmitter.on.bind(eventEmitter), 51 | 52 | /** 53 | * Removes a listener for the tab. 54 | * 55 | * @param {number} tabId 56 | * @param {function} listener 57 | */ 58 | removeListener: eventEmitter.off.bind(eventEmitter), 59 | 60 | /** 61 | * Checks whether a tab is being inspected by anything. 62 | * 63 | * @param {number} tabId 64 | * @return {boolean} 65 | */ 66 | hasListener: eventEmitter.hasListeners.bind(eventEmitter) 67 | }; 68 | 69 | /** 70 | * Logs a request associated with a tab or multiple tabs. 71 | * 72 | * @param {number[]} tabIds 73 | * The tabIds associated with the request 74 | * @param {Object} request 75 | * The request to log 76 | * @param {string} request.url 77 | * The URL of the request 78 | * @param {string} request.type 79 | * The request type 80 | * @param {string} request.docDomain 81 | * The hostname of the document 82 | * @param {boolean} request.thirdParty 83 | * Whether the origin of the request and document differs 84 | * @param {?string} request.sitekey 85 | * The active sitekey if there is any 86 | * @param {?boolean} request.specificOnly 87 | * Whether generic filters should be ignored 88 | * @param {?BlockingFilter} filter 89 | * The matched filter or null if there is no match 90 | */ 91 | exports.logRequest = (tabIds, request, filter) => 92 | { 93 | for (let tabId of tabIds) 94 | eventEmitter.emit(tabId, request, filter); 95 | }; 96 | 97 | function logHiddenElements(tabId, selectors, filters, docDomain) 98 | { 99 | if (HitLogger.hasListener(tabId)) 100 | { 101 | for (let subscription of filterStorage.subscriptions()) 102 | { 103 | if (subscription.disabled) 104 | continue; 105 | 106 | for (let text of subscription.filterText()) 107 | { 108 | let filter = Filter.fromText(text); 109 | 110 | // We only know the exact filter in case of element hiding emulation. 111 | // For regular element hiding filters, the content script only knows 112 | // the selector, so we have to find a filter that has an identical 113 | // selector and is active on the domain the match was reported from. 114 | let isActiveElemHideFilter = filter instanceof ElemHideFilter && 115 | selectors.includes(filter.selector) && 116 | filter.isActiveOnDomain(docDomain); 117 | 118 | if (isActiveElemHideFilter || filters.includes(text)) 119 | eventEmitter.emit(tabId, {type: "ELEMHIDE", docDomain}, filter); 120 | } 121 | } 122 | } 123 | } 124 | 125 | /** 126 | * Logs an allowing filter that disables (some kind of) 127 | * blocking for a particular document. 128 | * 129 | * @param {number} tabId The tabId the allowlisting is active for 130 | * @param {string} url The url of the allowlisted document 131 | * @param {number} typeMask The bit mask of allowing types checked 132 | * for 133 | * @param {string} docDomain The hostname of the parent document 134 | * @param {AllowingFilter} filter The matched allowing filter 135 | */ 136 | exports.logAllowlistedDocument = (tabId, url, typeMask, docDomain, filter) => 137 | { 138 | if (HitLogger.hasListener(tabId)) 139 | { 140 | for (let type of nonRequestTypes) 141 | { 142 | if (typeMask & filter.contentType & contentTypes[type]) 143 | eventEmitter.emit(tabId, {url, type, docDomain}, filter); 144 | } 145 | } 146 | }; 147 | 148 | /** 149 | * Logs active element hiding filters for a tab. 150 | * 151 | * @event "hitLogger.traceElemHide" 152 | * @property {string[]} selectors The selectors of applied ElemHideFilters 153 | * @property {string[]} filters The text of applied ElemHideEmulationFilters 154 | */ 155 | port.on("hitLogger.traceElemHide", (message, sender) => 156 | { 157 | logHiddenElements( 158 | sender.page.id, message.selectors, message.filters, 159 | extractHostFromFrame(sender.frame) 160 | ); 161 | }); 162 | -------------------------------------------------------------------------------- /lib/io.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | const keyPrefix = "file:"; 21 | 22 | function fileToKey(fileName) 23 | { 24 | return keyPrefix + fileName; 25 | } 26 | 27 | function loadFile(fileName) 28 | { 29 | let key = fileToKey(fileName); 30 | return browser.storage.local.get(key).then(items => 31 | { 32 | let entry = items[key]; 33 | if (entry) 34 | return entry; 35 | throw {type: "NoSuchFile"}; 36 | }); 37 | } 38 | 39 | function saveFile(fileName, data) 40 | { 41 | return browser.storage.local.set({ 42 | [fileToKey(fileName)]: { 43 | content: Array.from(data), 44 | lastModified: Date.now() 45 | } 46 | }); 47 | } 48 | 49 | exports.IO = 50 | { 51 | /** 52 | * Reads text lines from a file. 53 | * @param {string} fileName 54 | * Name of the file to be read 55 | * @param {TextSink} listener 56 | * Function that will be called for each line in the file 57 | * @return {Promise} 58 | * Promise to be resolved or rejected once the operation is completed 59 | */ 60 | readFromFile(fileName, listener) 61 | { 62 | return loadFile(fileName).then(entry => 63 | { 64 | for (let line of entry.content) 65 | listener(line); 66 | }); 67 | }, 68 | 69 | /** 70 | * Writes text lines to a file. 71 | * @param {string} fileName 72 | * Name of the file to be written 73 | * @param {Iterable.} data 74 | * An array-like or iterable object containing the lines (without line 75 | * endings) 76 | * @return {Promise} 77 | * Promise to be resolved or rejected once the operation is completed 78 | */ 79 | writeToFile(fileName, data) 80 | { 81 | return saveFile(fileName, data); 82 | }, 83 | 84 | /** 85 | * Renames a file. 86 | * @param {string} fromFile 87 | * Name of the file to be renamed 88 | * @param {string} newName 89 | * New file name, will be overwritten if exists 90 | * @return {Promise} 91 | * Promise to be resolved or rejected once the operation is completed 92 | */ 93 | renameFile(fromFile, newName) 94 | { 95 | return loadFile(fromFile) 96 | .then(entry => browser.storage.local.set({[fileToKey(newName)]: entry})) 97 | .then(() => browser.storage.local.remove(fileToKey(fromFile))); 98 | }, 99 | 100 | /** 101 | * Retrieves file metadata. 102 | * @param {string} fileName 103 | * Name of the file to be looked up 104 | * @return {Promise.} 105 | * Promise to be resolved with file metadata once the operation is 106 | * completed 107 | */ 108 | statFile(fileName) 109 | { 110 | return loadFile(fileName).then(entry => 111 | { 112 | return { 113 | exists: true, 114 | lastModified: entry.lastModified 115 | }; 116 | }).catch(error => 117 | { 118 | if (error.type == "NoSuchFile") 119 | return {exists: false}; 120 | throw error; 121 | }); 122 | } 123 | }; 124 | -------------------------------------------------------------------------------- /lib/messageResponder.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | /** @module messageResponder */ 19 | 20 | "use strict"; 21 | 22 | const {port} = require("messaging"); 23 | const info = require("info"); 24 | 25 | function forward(type, message, sender) 26 | { 27 | return new Promise(resolve => 28 | { 29 | port._onMessage(Object.assign({}, message, {type}), sender, resolve); 30 | }); 31 | } 32 | 33 | /** 34 | * @deprecated Please send the "filters.getTypes" message instead. 35 | * 36 | * @event "types.get" 37 | */ 38 | port.on("types.get", 39 | (message, sender) => forward("filters.getTypes", message, sender)); 40 | 41 | /** 42 | * @deprecated Please send the "options.open" message instead. 43 | * 44 | * @event "app.open" 45 | */ 46 | port.on("app.open", (message, sender) => 47 | { 48 | if (message.what == "options") 49 | return forward("options.open", message, sender); 50 | }); 51 | 52 | /** 53 | * @deprecated Please send the "subscriptions.getInitIssues", 54 | * "prefs.getDocLink", "subscriptions.getRecommendations", 55 | * "devtools.supported" or "info.get" messages, or call the 56 | * browser.tabs.getCurrent(), browser.i18n.getUILanguage(), 57 | * browser.i18n.getMessage("@@bidi_dir") APIs instead. 58 | * 59 | * @event "app.get" 60 | */ 61 | port.on("app.get", (message, sender) => 62 | { 63 | if (message.what == "localeInfo") 64 | { 65 | return { 66 | locale: browser.i18n.getUILanguage(), 67 | bidiDir: browser.i18n.getMessage("@@bidi_dir") 68 | }; 69 | } 70 | 71 | if (message.what == "senderId") 72 | return sender.page.id; 73 | 74 | if (message.what == "doclink") 75 | return forward("prefs.getDocLink", message, sender); 76 | 77 | if (message.what == "recommendations") 78 | return forward("subscriptions.getRecommendations", message, sender); 79 | 80 | if (message.what == "features") 81 | { 82 | return forward("devtools.supported", message, sender) 83 | .then(devToolsPanel => ({devToolsPanel})); 84 | } 85 | 86 | return info[message.what]; 87 | }); 88 | 89 | /** 90 | * @typedef {object} infoGetResult 91 | * @property {string} addonName 92 | * The extension's name, e.g. "adblockpluschrome". 93 | * @property {string} addonVersion 94 | * The extension's version, e.g. "3.6.3". 95 | * @property {string} application 96 | * The browser's name, e.g. "chrome". 97 | * @property {string} applicationVersion 98 | * The browser's version, e.g. "77.0.3865.90". 99 | * @property {string} platform 100 | * The browser platform, e.g. "chromium". 101 | * @property {string} platformVersion 102 | * The browser platform's version, e.g. "77.0.3865.90". 103 | */ 104 | 105 | /** 106 | * Returns the browser platform information. 107 | * 108 | * @event "info.get" 109 | * @returns {infoGetResult} 110 | */ 111 | port.on("info.get", (message, sender) => info); 112 | -------------------------------------------------------------------------------- /lib/messaging.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | /** @module messaging */ 19 | 20 | "use strict"; 21 | 22 | const {EventEmitter} = require("../adblockpluscore/lib/events"); 23 | 24 | /** 25 | * Communication port wrapping ext.onMessage to receive messages. 26 | * 27 | * @constructor 28 | */ 29 | function Port() 30 | { 31 | this._eventEmitter = new EventEmitter(); 32 | this._onMessage = this._onMessage.bind(this); 33 | ext.onMessage.addListener(this._onMessage); 34 | } 35 | 36 | Port.prototype = { 37 | _onMessage(message, sender, sendResponse) 38 | { 39 | let async = false; 40 | let callbacks = this._eventEmitter.listeners(message.type); 41 | 42 | for (let callback of callbacks) 43 | { 44 | let response = callback(message, sender); 45 | 46 | if (response && typeof response.then == "function") 47 | { 48 | response.then( 49 | sendResponse, 50 | reason => 51 | { 52 | console.error(reason); 53 | sendResponse(); 54 | } 55 | ); 56 | async = true; 57 | } 58 | else 59 | { 60 | sendResponse(response); 61 | } 62 | } 63 | 64 | return async; 65 | }, 66 | 67 | /** 68 | * Function to be called when a particular message is received. 69 | * 70 | * @callback Port~messageCallback 71 | * @param {object} message 72 | * @param {object} sender 73 | * @return The callback can return undefined (no response), 74 | * a value (response to be sent to sender immediately) 75 | * or a promise (asynchronous response). 76 | */ 77 | 78 | /** 79 | * Adds a callback for the specified message. 80 | * 81 | * The return value of the callback (if not undefined) is sent as response. 82 | * @param {string} name 83 | * @param {Port~messageCallback} callback 84 | */ 85 | on(name, callback) 86 | { 87 | this._eventEmitter.on(name, callback); 88 | }, 89 | 90 | /** 91 | * Removes a callback for the specified message. 92 | * 93 | * @param {string} name 94 | * @param {Port~messageCallback} callback 95 | */ 96 | off(name, callback) 97 | { 98 | this._eventEmitter.off(name, callback); 99 | }, 100 | 101 | /** 102 | * Disables the port and makes it stop listening to incoming messages. 103 | */ 104 | disconnect() 105 | { 106 | ext.onMessage.removeListener(this._onMessage); 107 | } 108 | }; 109 | 110 | /** 111 | * The default port to receive messages. 112 | * 113 | * @type {Port} 114 | */ 115 | exports.port = new Port(); 116 | -------------------------------------------------------------------------------- /lib/ml.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | /** @module */ 19 | 20 | "use strict"; 21 | 22 | const {port} = require("./messaging"); 23 | const {ML} = require("../adblockpluscore/lib/ml"); 24 | 25 | const tfCore = require("@tensorflow/tfjs-core"); 26 | const tfConverter = require("@tensorflow/tfjs-converter"); 27 | 28 | let tf = {}; 29 | 30 | for (let object of [tfCore, tfConverter]) 31 | { 32 | for (let property in object) 33 | { 34 | if (!Object.prototype.hasOwnProperty.call(tf, property)) 35 | tf[property] = object[property]; 36 | } 37 | } 38 | 39 | let mlByModel = new Map([ 40 | ["mlHideIfGraphMatches", new ML(tf)] 41 | ]); 42 | 43 | for (let [key, value] of mlByModel) 44 | value.modelURL = browser.runtime.getURL(`data/${key}/model.json`); 45 | 46 | /** 47 | * Returns the inference on a ML model. 48 | * 49 | * @event "ml.inference" 50 | * @property {string} model Name of the model to use 51 | * @returns {Array.} 52 | */ 53 | port.on("ml.inference", (message, sender) => 54 | { 55 | let ml = mlByModel.get(message.model); 56 | if (!ml) 57 | return Promise.reject(new Error("Model not found.")); 58 | 59 | return ml.predict(message.inputs); 60 | }); 61 | -------------------------------------------------------------------------------- /lib/options.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | /** @module options */ 19 | 20 | "use strict"; 21 | 22 | const {checkAllowlisted} = require("./allowlisting"); 23 | const info = require("info"); 24 | const {port} = require("./messaging"); 25 | 26 | const optionsUrl = browser.runtime.getManifest().options_ui.page; 27 | 28 | const openOptionsPageAPISupported = ( 29 | // Some versions of Firefox for Android before version 57 do have a 30 | // runtime.openOptionsPage but it doesn't do anything. 31 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1364945 32 | (info.application != "fennec" || 33 | parseInt(info.applicationVersion, 10) >= 57) 34 | ); 35 | 36 | function findOptionsPage() 37 | { 38 | return browser.tabs.query({}).then(tabs => 39 | { 40 | return new Promise((resolve, reject) => 41 | { 42 | // Firefox won't let us query for moz-extension:// pages, though 43 | // starting with Firefox 56 an extension can query for its own URLs: 44 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1271354 45 | let fullOptionsUrl = browser.extension.getURL(optionsUrl); 46 | let optionsTab = tabs.find(tab => tab.url == fullOptionsUrl); 47 | if (optionsTab) 48 | { 49 | resolve(optionsTab); 50 | return; 51 | } 52 | 53 | // Newly created tabs might have about:blank as their URL in Firefox or 54 | // an empty string on Chrome (80) rather than the final options page URL, 55 | // we need to wait for those to finish loading. 56 | let potentialOptionTabIds = new Set( 57 | tabs.filter(tab => (tab.url == "about:blank" || !tab.url) && 58 | tab.status == "loading").map(tab => tab.id) 59 | ); 60 | if (potentialOptionTabIds.size == 0) 61 | { 62 | resolve(); 63 | return; 64 | } 65 | let removeListener; 66 | let updateListener = (tabId, changeInfo, tab) => 67 | { 68 | if (potentialOptionTabIds.has(tabId) && 69 | changeInfo.status == "complete") 70 | { 71 | potentialOptionTabIds.delete(tabId); 72 | let urlMatch = tab.url == fullOptionsUrl; 73 | if (urlMatch || potentialOptionTabIds.size == 0) 74 | { 75 | browser.tabs.onUpdated.removeListener(updateListener); 76 | browser.tabs.onRemoved.removeListener(removeListener); 77 | resolve(urlMatch ? tab : null); 78 | } 79 | } 80 | }; 81 | browser.tabs.onUpdated.addListener(updateListener); 82 | removeListener = removedTabId => 83 | { 84 | potentialOptionTabIds.delete(removedTabId); 85 | if (potentialOptionTabIds.size == 0) 86 | { 87 | browser.tabs.onUpdated.removeListener(updateListener); 88 | browser.tabs.onRemoved.removeListener(removeListener); 89 | } 90 | }; 91 | browser.tabs.onRemoved.addListener(removeListener); 92 | }); 93 | }); 94 | } 95 | 96 | function openOptionsPage() 97 | { 98 | if (openOptionsPageAPISupported) 99 | return browser.runtime.openOptionsPage(); 100 | return browser.tabs.create({url: optionsUrl}); 101 | } 102 | 103 | function waitForOptionsPage(tab) 104 | { 105 | return new Promise(resolve => 106 | { 107 | function onMessage(message, optionsPort) 108 | { 109 | if (message.type != "app.listen") 110 | return; 111 | 112 | optionsPort.onMessage.removeListener(onMessage); 113 | resolve([tab, optionsPort]); 114 | } 115 | 116 | function onConnect(optionsPort) 117 | { 118 | if (optionsPort.name != "ui" || optionsPort.sender.tab.id != tab.id) 119 | return; 120 | 121 | browser.runtime.onConnect.removeListener(onConnect); 122 | optionsPort.onMessage.addListener(onMessage); 123 | } 124 | 125 | browser.runtime.onConnect.addListener(onConnect); 126 | }); 127 | } 128 | 129 | function focusOptionsPage(tab) 130 | { 131 | if (openOptionsPageAPISupported) 132 | return browser.runtime.openOptionsPage(); 133 | 134 | let focusTab = () => browser.tabs.update(tab.id, {active: true}); 135 | 136 | if ("windows" in browser) 137 | return browser.windows.update(tab.windowId, {focused: true}).then(focusTab); 138 | 139 | // Firefox for Android before version 57 does not support 140 | // runtime.openOptionsPage, nor does it support the windows API. 141 | // Since there is effectively only one window on the mobile browser, 142 | // we can just bring the tab to focus instead. 143 | return focusTab(); 144 | } 145 | 146 | let showOptions = 147 | /** 148 | * Opens the options page, or switches to its existing tab. 149 | * @returns {Promise.} 150 | * Promise resolving to an Array containg the tab Object of the options page 151 | * and sometimes (when the page was just opened) a messaging port. 152 | */ 153 | exports.showOptions = () => 154 | { 155 | return findOptionsPage().then(existingTab => 156 | { 157 | if (existingTab) 158 | return focusOptionsPage(existingTab).then(() => existingTab); 159 | 160 | return openOptionsPage().then(findOptionsPage).then(waitForOptionsPage); 161 | }); 162 | }; 163 | 164 | // We need to clear the popup URL on Firefox for Android in order for the 165 | // options page to open instead of the bubble. Unfortunately there's a bug[1] 166 | // which prevents us from doing that, so we must avoid setting the URL on 167 | // Firefox from the manifest at all, instead setting it here only for 168 | // non-mobile. 169 | // [1] - https://bugzilla.mozilla.org/show_bug.cgi?id=1414613 170 | if ("getBrowserInfo" in browser.runtime) 171 | { 172 | Promise.all([browser.browserAction.getPopup({}), 173 | browser.runtime.getBrowserInfo()]).then( 174 | ([popup, browserInfo]) => 175 | { 176 | if (!popup && browserInfo.name != "Fennec") 177 | browser.browserAction.setPopup({popup: "popup.html"}); 178 | } 179 | ); 180 | } 181 | 182 | // On Firefox for Android, open the options page directly when the browser 183 | // action is clicked. 184 | browser.browserAction.onClicked.addListener(() => 185 | { 186 | browser.tabs.query({active: true, lastFocusedWindow: true}).then( 187 | ([tab]) => 188 | { 189 | let currentPage = new ext.Page(tab); 190 | 191 | showOptions().then(([optionsTab, optionsPort]) => 192 | { 193 | if (!/^https?:$/.test(currentPage.url.protocol)) 194 | return; 195 | 196 | optionsPort.postMessage({ 197 | type: "app.respond", 198 | action: "showPageOptions", 199 | args: [ 200 | { 201 | host: currentPage.url.hostname.replace(/^www\./, ""), 202 | whitelisted: !!checkAllowlisted(currentPage) 203 | } 204 | ] 205 | }); 206 | }); 207 | } 208 | ); 209 | }); 210 | 211 | /** 212 | * Opens the options page in a new tab and waits for it to load, or switches to 213 | * the existing tab if the options page is already open. 214 | * 215 | * @event "options.open" 216 | * @returns {object} optionsTab 217 | */ 218 | port.on("options.open", (message, sender) => 219 | showOptions().then(([optionsTab, optionsPort]) => optionsTab) 220 | ); 221 | -------------------------------------------------------------------------------- /lib/popupBlocker.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | /** @module popupBlocker */ 19 | 20 | "use strict"; 21 | 22 | const {defaultMatcher} = require("../adblockpluscore/lib/matcher"); 23 | const {BlockingFilter} = require("../adblockpluscore/lib/filterClasses"); 24 | const {contentTypes} = require("../adblockpluscore/lib/contentTypes"); 25 | const {parseURL} = require("../adblockpluscore/lib/url"); 26 | const {extractHostFromFrame} = require("./url"); 27 | const {checkAllowlisted} = require("./allowlisting"); 28 | const {logRequest} = require("./hitLogger"); 29 | const info = require("info"); 30 | 31 | let loadingPopups = new Map(); 32 | 33 | function forgetPopup(tabId) 34 | { 35 | loadingPopups.delete(tabId); 36 | 37 | if (loadingPopups.size == 0) 38 | { 39 | browser.webRequest.onBeforeRequest.removeListener(onPopupURLChanged); 40 | browser.webNavigation.onCommitted.removeListener(onPopupURLChanged); 41 | browser.webNavigation.onCompleted.removeListener(onCompleted); 42 | browser.tabs.onRemoved.removeListener(forgetPopup); 43 | } 44 | } 45 | 46 | function checkPotentialPopup(tabId, popup) 47 | { 48 | let url = popup.url || "about:blank"; 49 | let documentHost = extractHostFromFrame(popup.sourceFrame); 50 | 51 | let specificOnly = !!checkAllowlisted( 52 | popup.sourcePage, popup.sourceFrame, null, 53 | contentTypes.GENERICBLOCK 54 | ); 55 | 56 | let filter = defaultMatcher.match( 57 | parseURL(url), contentTypes.POPUP, 58 | documentHost, null, specificOnly 59 | ); 60 | 61 | if (filter instanceof BlockingFilter) 62 | browser.tabs.remove(tabId).catch(() => {}); 63 | 64 | logRequest( 65 | [popup.sourcePage.id], 66 | {url, type: "POPUP", docDomain: documentHost, specificOnly}, 67 | filter 68 | ); 69 | } 70 | 71 | function onPopupURLChanged(details) 72 | { 73 | // Ignore frames inside the popup window. 74 | if (details.frameId != 0) 75 | return; 76 | 77 | let popup = loadingPopups.get(details.tabId); 78 | if (popup) 79 | { 80 | popup.url = details.url; 81 | if (popup.sourceFrame) 82 | checkPotentialPopup(details.tabId, popup); 83 | } 84 | } 85 | 86 | function onCompleted(details) 87 | { 88 | if (details.frameId == 0 && details.url != "about:blank") 89 | forgetPopup(details.tabId); 90 | } 91 | 92 | function onPopupCreated(tabId, url, sourceTabId, sourceFrameId) 93 | { 94 | if (loadingPopups.size == 0) 95 | { 96 | browser.webRequest.onBeforeRequest.addListener( 97 | onPopupURLChanged, 98 | { 99 | urls: ["http://*/*", "https://*/*"], 100 | types: ["main_frame"] 101 | } 102 | ); 103 | browser.webNavigation.onCommitted.addListener(onPopupURLChanged); 104 | browser.webNavigation.onCompleted.addListener(onCompleted); 105 | browser.tabs.onRemoved.addListener(forgetPopup); 106 | } 107 | 108 | let popup = { 109 | url, 110 | sourcePage: new ext.Page({id: sourceTabId}), 111 | sourceFrame: null 112 | }; 113 | 114 | loadingPopups.set(tabId, popup); 115 | 116 | let frame = ext.getFrame(sourceTabId, sourceFrameId); 117 | 118 | if (checkAllowlisted(popup.sourcePage, frame)) 119 | { 120 | forgetPopup(tabId); 121 | } 122 | else 123 | { 124 | popup.sourceFrame = frame; 125 | checkPotentialPopup(tabId, popup); 126 | } 127 | } 128 | 129 | // Versions of Firefox before 54 do not support 130 | // webNavigation.onCreatedNavigationTarget 131 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1190687 132 | if ("onCreatedNavigationTarget" in browser.webNavigation) 133 | { 134 | browser.webNavigation.onCreatedNavigationTarget.addListener(details => 135 | { 136 | onPopupCreated(details.tabId, details.url, details.sourceTabId, 137 | details.sourceFrameId); 138 | }); 139 | } 140 | 141 | // On Firefox, clicking on a link doesn't 142 | // emit the webNavigation.onCreatedNavigationTarget event (and since Firefox 79, 143 | // "noopener" is implied by default). But on Chrome, opening a new empty tab 144 | // emits the tabs.onCreated event with openerTabId set. So the code below would 145 | // cause new tabs created by the user to be considered popups too, on Chrome. 146 | if (info.platform == "gecko") 147 | { 148 | browser.tabs.onCreated.addListener(details => 149 | { 150 | // We only care about tabs created by another tab. 151 | // e.g. clicking on a link with target=_blank. 152 | if (typeof details.openerTabId == "undefined") 153 | return; 154 | 155 | // onCreated doesn't provide the frameId of the opener. 156 | onPopupCreated(details.id, details.url, details.openerTabId, 0); 157 | }); 158 | } 159 | -------------------------------------------------------------------------------- /lib/stats.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | /** @module stats */ 19 | 20 | "use strict"; 21 | 22 | const {BlockingFilter} = require("../adblockpluscore/lib/filterClasses"); 23 | const {filterNotifier} = require("../adblockpluscore/lib/filterNotifier"); 24 | const browserAction = require("./browserAction"); 25 | const {port} = require("./messaging"); 26 | const {Prefs} = require("./prefs"); 27 | 28 | const badgeColor = "#646464"; 29 | const badgeRefreshRate = 4; 30 | 31 | let blockedPerPage = new ext.PageMap(); 32 | 33 | let getBlockedPerPage = 34 | /** 35 | * Gets the number of requests blocked on the given page. 36 | * 37 | * @param {Page} page 38 | * @return {Number} 39 | */ 40 | exports.getBlockedPerPage = page => blockedPerPage.get(page) || 0; 41 | 42 | let activeTabIds = new Set(); 43 | let activeTabIdByWindowId = new Map(); 44 | 45 | let badgeUpdateScheduled = false; 46 | 47 | function updateBadge(tabId) 48 | { 49 | if (!Prefs.show_statsinicon) 50 | return; 51 | 52 | for (let id of (typeof tabId == "undefined" ? activeTabIds : [tabId])) 53 | { 54 | let page = new ext.Page({id}); 55 | let blockedCount = blockedPerPage.get(page); 56 | 57 | browserAction.setBadge(page.id, blockedCount && { 58 | color: badgeColor, 59 | number: blockedCount 60 | }); 61 | } 62 | } 63 | 64 | function scheduleBadgeUpdate(tabId) 65 | { 66 | if (!badgeUpdateScheduled && Prefs.show_statsinicon && 67 | (typeof tabId == "undefined" || activeTabIds.has(tabId))) 68 | { 69 | setTimeout(() => { badgeUpdateScheduled = false; updateBadge(); }, 70 | 1000 / badgeRefreshRate); 71 | badgeUpdateScheduled = true; 72 | } 73 | } 74 | 75 | // Once nagivation for the tab has been committed to (e.g. it's no longer 76 | // being prerendered) we clear its badge, or if some requests were already 77 | // blocked beforehand we display those on the badge now. 78 | browser.webNavigation.onCommitted.addListener(details => 79 | { 80 | if (details.frameId == 0) 81 | updateBadge(details.tabId); 82 | }); 83 | 84 | /** 85 | * Records a blocked request. 86 | * 87 | * @param {Filter} filter 88 | * @param {Array.} tabIds 89 | */ 90 | exports.recordBlockedRequest = (filter, tabIds) => 91 | { 92 | if (!(filter instanceof BlockingFilter)) 93 | return; 94 | 95 | for (let tabId of tabIds) 96 | { 97 | let page = new ext.Page({id: tabId}); 98 | let blocked = blockedPerPage.get(page) || 0; 99 | 100 | blockedPerPage.set(page, ++blocked); 101 | scheduleBadgeUpdate(tabId); 102 | } 103 | 104 | // Make sure blocked_total is only read after the storage was loaded. 105 | Prefs.untilLoaded.then(() => { Prefs.blocked_total++; }); 106 | }; 107 | 108 | Prefs.on("show_statsinicon", () => 109 | { 110 | browser.tabs.query({}).then(tabs => 111 | { 112 | for (let tab of tabs) 113 | { 114 | if (Prefs.show_statsinicon) 115 | updateBadge(tab.id); 116 | else 117 | browserAction.setBadge(tab.id, null); 118 | } 119 | }); 120 | }); 121 | 122 | /** 123 | * Returns the number of blocked requests for the sender's page. 124 | * 125 | * @event "stats.getBlockedPerPage" 126 | * @returns {number} 127 | */ 128 | port.on("stats.getBlockedPerPage", 129 | message => getBlockedPerPage(new ext.Page(message.tab))); 130 | 131 | browser.tabs.query({active: true}).then(tabs => 132 | { 133 | for (let tab of tabs) 134 | { 135 | activeTabIds.add(tab.id); 136 | activeTabIdByWindowId.set(tab.windowId, tab.id); 137 | } 138 | 139 | scheduleBadgeUpdate(); 140 | }); 141 | 142 | browser.tabs.onActivated.addListener(tab => 143 | { 144 | let lastActiveTabId = activeTabIdByWindowId.get(tab.windowId); 145 | if (typeof lastActiveTabId != "undefined") 146 | activeTabIds.delete(lastActiveTabId); 147 | 148 | activeTabIds.add(tab.tabId); 149 | activeTabIdByWindowId.set(tab.windowId, tab.tabId); 150 | 151 | scheduleBadgeUpdate(); 152 | }); 153 | 154 | if ("windows" in browser) 155 | { 156 | browser.windows.onRemoved.addListener(windowId => 157 | { 158 | activeTabIds.delete(activeTabIdByWindowId.get(windowId)); 159 | activeTabIdByWindowId.delete(windowId); 160 | }); 161 | } 162 | -------------------------------------------------------------------------------- /lib/uninstall.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | /** @module uninstall */ 19 | 20 | "use strict"; 21 | 22 | const info = require("info"); 23 | const {analytics} = require("../adblockpluscore/lib/analytics"); 24 | const {filterNotifier} = require("../adblockpluscore/lib/filterNotifier"); 25 | const {filterStorage} = require("../adblockpluscore/lib/filterStorage"); 26 | const {recommendations} = require("../adblockpluscore/lib/recommendations"); 27 | const {isDataCorrupted} = require("./subscriptionInit"); 28 | const {Prefs} = require("./prefs"); 29 | 30 | const abbreviations = [ 31 | ["an", "addonName"], ["av", "addonVersion"], 32 | ["ap", "application"], ["apv", "applicationVersion"], 33 | ["p", "platform"], ["fv", "firstVersion"], ["pv", "platformVersion"], 34 | ["ndc", "notificationDownloadCount"], ["c", "corrupted"], 35 | ["s", "subscriptions"] 36 | ]; 37 | 38 | /** 39 | * Retrieves set of URLs of recommended ad blocking filter lists 40 | * 41 | * @return {Set} 42 | */ 43 | function getAdsSubscriptions() 44 | { 45 | let subscriptions = new Set(); 46 | for (let subscription of recommendations()) 47 | { 48 | if (subscription.type == "ads") 49 | subscriptions.add(subscription.url); 50 | } 51 | return subscriptions; 52 | } 53 | 54 | /** 55 | * Determines whether any of the given subscriptions are installed and enabled 56 | * 57 | * @param {Set} urls 58 | * 59 | * @return {boolean} 60 | */ 61 | function isAnySubscriptionActive(urls) 62 | { 63 | for (let subscription of filterStorage.subscriptions()) 64 | { 65 | if (!subscription.disabled && urls.has(subscription.url)) 66 | return true; 67 | } 68 | 69 | return false; 70 | } 71 | 72 | let setUninstallURL = 73 | /** 74 | * Sets (or updates) the URL that is openend when the extension is uninstalled. 75 | * 76 | * Must be called after prefs got initialized and a data corruption 77 | * if any was detected, as well when notification data change. 78 | */ 79 | exports.setUninstallURL = () => 80 | { 81 | let search = []; 82 | let params = Object.create(info); 83 | 84 | params.corrupted = isDataCorrupted() ? "1" : "0"; 85 | params.firstVersion = analytics.getFirstVersion(); 86 | 87 | let notificationDownloadCount = Prefs.notificationdata.downloadCount || 0; 88 | if (notificationDownloadCount < 5) 89 | params.notificationDownloadCount = notificationDownloadCount; 90 | else if (notificationDownloadCount < 8) 91 | params.notificationDownloadCount = "5-7"; 92 | else if (notificationDownloadCount < 30) 93 | params.notificationDownloadCount = "8-29"; 94 | else if (notificationDownloadCount < 90) 95 | params.notificationDownloadCount = "30-89"; 96 | else if (notificationDownloadCount < 180) 97 | params.notificationDownloadCount = "90-179"; 98 | else 99 | params.notificationDownloadCount = "180+"; 100 | 101 | let aaSubscriptions = new Set([Prefs.subscriptions_exceptionsurl]); 102 | let adsSubscriptions = getAdsSubscriptions(); 103 | let isAcceptableAdsActive = isAnySubscriptionActive(aaSubscriptions); 104 | let isAdBlockingActive = isAnySubscriptionActive(adsSubscriptions); 105 | params.subscriptions = (isAcceptableAdsActive << 1) | isAdBlockingActive; 106 | 107 | for (let [abbreviation, key] of abbreviations) 108 | search.push(abbreviation + "=" + encodeURIComponent(params[key])); 109 | 110 | browser.runtime.setUninstallURL(Prefs.getDocLink("uninstalled") + "&" + 111 | search.join("&")); 112 | }; 113 | 114 | filterNotifier.on("subscription.added", setUninstallURL); 115 | filterNotifier.on("subscription.disabled", setUninstallURL); 116 | filterNotifier.on("subscription.removed", setUninstallURL); 117 | Prefs.on("notificationdata", setUninstallURL); 118 | -------------------------------------------------------------------------------- /lib/url.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | /** @module url */ 19 | 20 | "use strict"; 21 | 22 | /** 23 | * Gets the IDN-decoded hostname from the URL of a frame. 24 | * If the URL don't have host information (like "about:blank" 25 | * and "data:" URLs) it falls back to the parent frame. 26 | * 27 | * @param {?Frame} frame 28 | * @param {URL} [originUrl] 29 | * @return {string} 30 | */ 31 | exports.extractHostFromFrame = (frame, originUrl) => 32 | { 33 | for (; frame; frame = frame.parent) 34 | { 35 | let {hostname} = frame.url; 36 | if (hostname) 37 | return hostname; 38 | } 39 | 40 | return originUrl ? originUrl.hostname : ""; 41 | }; 42 | -------------------------------------------------------------------------------- /managed-storage-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "suppress_first_run_page": { 5 | "type": "boolean" 6 | }, 7 | "additional_subscriptions": { 8 | "type": "array", 9 | "items": { 10 | "type": "string" 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /options.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | let iframe = document.getElementById("content"); 21 | 22 | iframe.onload = () => 23 | { 24 | document.title = iframe.contentDocument.title; 25 | }; 26 | 27 | browser.runtime.sendMessage({ 28 | type: "app.get", 29 | what: "application" 30 | }).then(application => 31 | { 32 | // Load the mobile version of the options page on Firefox for Android. 33 | iframe.src = iframe.getAttribute("data-src-" + application) || 34 | iframe.getAttribute("data-src"); 35 | }); 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adblockpluschrome", 3 | "repository": "https://gitlab.com/eyeo/adblockplus/adblockpluschrome", 4 | "license": "GPL-3.0", 5 | "engines": { 6 | "node": ">=12.17.0", 7 | "npm": ">=6" 8 | }, 9 | "dependencies": { 10 | "@tensorflow/tfjs-converter": "1.3.2", 11 | "@tensorflow/tfjs-core": "1.3.2" 12 | }, 13 | "devDependencies": { 14 | "argparse": "^1.0.10", 15 | "chromedriver": "^85.0.1", 16 | "del": "^5.1.0", 17 | "dmg": "^0.1.0", 18 | "eslint": "^7.9.0", 19 | "eslint-config-eyeo": "^3.2.0", 20 | "extract-zip": "^2.0.1", 21 | "geckodriver": "^1.20.0", 22 | "got": "^11.6.2", 23 | "gulp": "^4.0.2", 24 | "gulp-tar": "^3.1.0", 25 | "gulp-gzip": "^1.4.2", 26 | "gulp-vinyl-zip": "^2.2.1", 27 | "handlebars": "^4.7.6", 28 | "jimp": "^0.16.1", 29 | "jsdoc": "^3.6.5", 30 | "merge-stream": "^2.0.0", 31 | "mocha": "^8.1.3", 32 | "msedgedriver": "^83.0.0", 33 | "ncp": "^2.0.0", 34 | "selenium-webdriver": "^4.0.0-alpha.7", 35 | "semver": "^7.3.2", 36 | "through": "^2.3.8", 37 | "webpack-merge": "^5.1.4", 38 | "webpack-stream": "^6.1.0" 39 | }, 40 | "scripts": { 41 | "generate-buildnum": "node --no-warnings build/utils/git.mjs", 42 | "lint": "eslint --ext .js,.mjs *.js *.mjs lib/ qunit/ ext/ test/ build/", 43 | "test-only": "mocha --unhandled-rejections=strict --delay", 44 | "test": "npm run test-only --", 45 | "postinstall": "cd adblockplusui && npm install --only=production --loglevel=error --no-audit --no-fund --no-package-lock --no-optional", 46 | "posttest": "npm run lint", 47 | "docs": "jsdoc --configure jsdoc.conf --destination docs lib", 48 | "download-test-browsers": "node test/bin/downloadBrowsers.mjs" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /polyfill.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | { 21 | let asyncAPIs = [ 22 | "browserAction.setIcon", 23 | "browserAction.getPopup", 24 | "contentSettings.cookies.get", 25 | "contentSettings.javascript.get", 26 | "contextMenus.removeAll", 27 | "devtools.panels.create", 28 | "management.getAll", 29 | "management.getSelf", 30 | "notifications.clear", 31 | "notifications.create", 32 | "permissions.contains", 33 | "permissions.remove", 34 | "permissions.request", 35 | "runtime.getBrowserInfo", 36 | "runtime.openOptionsPage", 37 | "runtime.sendMessage", 38 | "runtime.setUninstallURL", 39 | "storage.local.get", 40 | "storage.local.remove", 41 | "storage.local.set", 42 | "storage.managed.get", 43 | "tabs.captureVisibleTab", 44 | "tabs.create", 45 | "tabs.executeScript", 46 | "tabs.get", 47 | "tabs.getCurrent", 48 | "tabs.insertCSS", 49 | "tabs.query", 50 | "tabs.reload", 51 | "tabs.remove", 52 | "tabs.removeCSS", 53 | "tabs.sendMessage", 54 | "tabs.update", 55 | "webNavigation.getAllFrames", 56 | "webRequest.handlerBehaviorChanged", 57 | "windows.create", 58 | "windows.update" 59 | ]; 60 | 61 | // Chrome (<= 66) and Opera (<= 54) don't accept passing a callback for 62 | // browserAction.setBadgeText and browserAction.setBadgeBackgroundColor 63 | const maybeAsyncAPIs = [ 64 | ["browserAction.setBadgeText", {text: ""}], 65 | ["browserAction.setBadgeBackgroundColor", {color: [0, 0, 0, 0]}] 66 | ]; 67 | let syncAPIs = []; 68 | 69 | // Since we add a callback for all messaging API calls in our wrappers, 70 | // Chrome assumes we're interested in the response; when there's no response, 71 | // it sets runtime.lastError 72 | const portClosedBeforeResponseError = 73 | // Older versions of Chrome have a typo: 74 | // https://crrev.com/c33f51726eacdcc1a487b21a13611f7eab580d6d 75 | /^The message port closed before a res?ponse was received\.$/; 76 | 77 | // This is the error Firefox throws when a message listener is not a 78 | // function. 79 | const invalidMessageListenerError = "Invalid listener for runtime.onMessage."; 80 | 81 | let messageListeners = new WeakMap(); 82 | 83 | function getAPIWrappables(api) 84 | { 85 | let object = browser; 86 | let path = api.split("."); 87 | let name = path.pop(); 88 | 89 | for (let node of path) 90 | { 91 | object = object[node]; 92 | 93 | if (!object) 94 | return; 95 | } 96 | 97 | let func = object[name]; 98 | if (!func) 99 | return; 100 | 101 | return {object, name, func}; 102 | } 103 | 104 | function wrapAsyncAPI(api) 105 | { 106 | let wrappables = getAPIWrappables(api); 107 | 108 | if (!wrappables) 109 | return; 110 | 111 | let {object, name, func} = wrappables; 112 | 113 | // If the property is not writable assigning it will fail, so we use 114 | // Object.defineProperty here instead. Assuming the property isn't 115 | // inherited its other attributes (e.g. enumerable) are preserved, 116 | // except for accessor attributes (e.g. get and set) which are discarded 117 | // since we're specifying a value. 118 | Object.defineProperty(object, name, { 119 | value(...args) 120 | { 121 | let resolvePromise = null; 122 | let rejectPromise = null; 123 | 124 | func.call(object, ...args, result => 125 | { 126 | let error = browser.runtime.lastError; 127 | if (error && !portClosedBeforeResponseError.test(error.message)) 128 | rejectPromise(new Error(error.message)); 129 | else 130 | resolvePromise(result); 131 | }); 132 | 133 | return new Promise((resolve, reject) => 134 | { 135 | resolvePromise = resolve; 136 | rejectPromise = reject; 137 | }); 138 | } 139 | }); 140 | } 141 | 142 | function wrapSyncAPI(api) 143 | { 144 | let wrappables = getAPIWrappables(api); 145 | 146 | if (!wrappables) 147 | return; 148 | 149 | let {object, name, func} = wrappables; 150 | 151 | Object.defineProperty(object, name, { 152 | value(...args) 153 | { 154 | return Promise.resolve(func.call(object, ...args)); 155 | } 156 | }); 157 | } 158 | 159 | function wrapRuntimeOnMessage() 160 | { 161 | let {onMessage} = browser.runtime; 162 | let {addListener, removeListener} = onMessage; 163 | 164 | onMessage.addListener = function(listener) 165 | { 166 | if (typeof listener != "function") 167 | throw new Error(invalidMessageListenerError); 168 | 169 | // Don't add the same listener twice or we end up with multiple wrappers. 170 | if (messageListeners.has(listener)) 171 | return; 172 | 173 | let wrapper = (message, sender, sendResponse) => 174 | { 175 | let wait = listener(message, sender, sendResponse); 176 | 177 | if (wait instanceof Promise) 178 | { 179 | wait.then(sendResponse, reason => 180 | { 181 | try 182 | { 183 | sendResponse(); 184 | } 185 | catch (error) 186 | { 187 | // sendResponse can throw if the internal port is closed; be sure 188 | // to throw the original error. 189 | } 190 | 191 | throw reason; 192 | }); 193 | } 194 | 195 | return !!wait; 196 | }; 197 | 198 | addListener.call(onMessage, wrapper); 199 | messageListeners.set(listener, wrapper); 200 | }; 201 | 202 | onMessage.removeListener = function(listener) 203 | { 204 | if (typeof listener != "function") 205 | throw new Error(invalidMessageListenerError); 206 | 207 | let wrapper = messageListeners.get(listener); 208 | if (wrapper) 209 | { 210 | removeListener.call(onMessage, wrapper); 211 | messageListeners.delete(listener); 212 | } 213 | }; 214 | 215 | onMessage.hasListener = function(listener) 216 | { 217 | if (typeof listener != "function") 218 | throw new Error(invalidMessageListenerError); 219 | 220 | return messageListeners.has(listener); 221 | }; 222 | } 223 | 224 | function shouldWrapAPIs() 225 | { 226 | try 227 | { 228 | return !(browser.storage.local.get([]) instanceof Promise); 229 | } 230 | catch (error) 231 | { 232 | } 233 | 234 | return true; 235 | } 236 | 237 | function acceptsCallback(func, args) 238 | { 239 | try 240 | { 241 | func(...args, () => {}); 242 | return true; 243 | } 244 | catch (e) 245 | { 246 | return false; 247 | } 248 | } 249 | 250 | if (shouldWrapAPIs()) 251 | { 252 | // Unlike Firefox, Chrome doesn't have a "browser" object, but provides 253 | // the extension API through the "chrome" namespace (non-standard). 254 | if (typeof browser == "undefined") 255 | self.browser = chrome; 256 | 257 | for (let [api, ...testArgs] of maybeAsyncAPIs) 258 | { 259 | let wrappables = getAPIWrappables(api); 260 | 261 | if (!wrappables) 262 | continue; 263 | 264 | let {func} = wrappables; 265 | 266 | (acceptsCallback(func, testArgs) ? asyncAPIs : syncAPIs).push(api); 267 | } 268 | 269 | for (let api of asyncAPIs) 270 | wrapAsyncAPI(api); 271 | 272 | for (let api of syncAPIs) 273 | wrapSyncAPI(api); 274 | 275 | wrapRuntimeOnMessage(); 276 | } 277 | } 278 | 279 | // Object.values is not supported in Chrome <54. 280 | if (!("values" in Object)) 281 | Object.values = obj => Object.keys(obj).map(key => obj[key]); 282 | 283 | // Firefox <56 separates the locale parts with an underscore instead of a dash. 284 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1374552 285 | let {getUILanguage} = browser.i18n; 286 | browser.i18n.getUILanguage = function() 287 | { 288 | return getUILanguage().replace("_", "-"); 289 | }; 290 | 291 | // Chrome <69 does not support OffscreenCanvas 292 | if (typeof OffscreenCanvas == "undefined") 293 | { 294 | self.OffscreenCanvas = function(width, height) 295 | { 296 | let canvas = document.createElement("canvas"); 297 | canvas.width = width; 298 | canvas.height = height; 299 | return canvas; 300 | }; 301 | } 302 | 303 | // Some Node.js modules rely on the global reference. 304 | self.global = self; 305 | -------------------------------------------------------------------------------- /qunit/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "qunit": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /qunit/subscriptions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "EasyList Czech and Slovak+EasyList", 4 | "url": "https://easylist-downloads.adblockplus.org/easylistczechslovak+easylist.txt", 5 | "homepage": "https://adblock.sk/", 6 | "languages": [ 7 | "cs", 8 | "sk" 9 | ], 10 | "type": "ads" 11 | }, 12 | { 13 | "title": "EasyList", 14 | "url": "https://easylist-downloads.adblockplus.org/easylist.txt", 15 | "homepage": "https://easylist.to/", 16 | "languages": [ 17 | "en" 18 | ], 19 | "type": "ads" 20 | }, 21 | { 22 | "title": "EasyList Dutch+EasyList", 23 | "url": "https://easylist-downloads.adblockplus.org/easylistdutch+easylist.txt", 24 | "homepage": "https://easylist.to/", 25 | "languages": [ 26 | "nl" 27 | ], 28 | "type": "ads" 29 | }, 30 | { 31 | "title": "EasyList Germany+EasyList", 32 | "url": "https://easylist-downloads.adblockplus.org/easylistgermany+easylist.txt", 33 | "homepage": "https://easylist.to/", 34 | "languages": [ 35 | "de" 36 | ], 37 | "type": "ads" 38 | }, 39 | { 40 | "title": "EasyList Italy+EasyList", 41 | "url": "https://easylist-downloads.adblockplus.org/easylistitaly+easylist.txt", 42 | "homepage": "https://easylist.to/", 43 | "languages": [ 44 | "it" 45 | ], 46 | "type": "ads" 47 | }, 48 | { 49 | "title": "Liste FR+EasyList", 50 | "url": "https://easylist-downloads.adblockplus.org/liste_fr+easylist.txt", 51 | "homepage": "https://forums.lanik.us/viewforum.php?f=91", 52 | "languages": [ 53 | "fr" 54 | ], 55 | "type": "ads" 56 | }, 57 | { 58 | "title": "RuAdList+EasyList", 59 | "url": "https://easylist-downloads.adblockplus.org/ruadlist+easylist.txt", 60 | "homepage": "https://forums.lanik.us/viewforum.php?f=102", 61 | "languages": [ 62 | "ru", 63 | "uk" 64 | ], 65 | "type": "ads" 66 | }, 67 | { 68 | "title": "ABP filters", 69 | "url": "https://easylist-downloads.adblockplus.org/abp-filters-anti-cv.txt", 70 | "homepage": "https://github.com/abp-filters/abp-filters-anti-cv", 71 | "languages": [ 72 | "de", 73 | "en" 74 | ], 75 | "type": "circumvention" 76 | }, 77 | { 78 | "title": "ABP Anti-Circumvention list Fr", 79 | "url": "https://easylist-downloads.adblockplus.org/abp-filters-anti-cv-fr.txt", 80 | "homepage": "https://github.com/abp-filters/abp-filters-anti-cv", 81 | "languages": [ 82 | "fr" 83 | ], 84 | "type": "circumvention" 85 | }, 86 | { 87 | "title": "EasyPrivacy", 88 | "url": "https://easylist-downloads.adblockplus.org/easyprivacy.txt", 89 | "homepage": "https://easylist.to/", 90 | "type": "privacy" 91 | }, 92 | { 93 | "title": "Fanboy's Social Blocking List", 94 | "url": "https://easylist-downloads.adblockplus.org/fanboy-social.txt", 95 | "homepage": "https://easylist.to/", 96 | "type": "social" 97 | } 98 | ] 99 | -------------------------------------------------------------------------------- /qunit/tests/cssEscaping.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const {Filter, ElemHideFilter} = 4 | require("../../adblockpluscore/lib/filterClasses"); 5 | const {escapeCSS, quoteCSS} = require("../../lib/filterComposer"); 6 | 7 | QUnit.module("CSS escaping", () => 8 | { 9 | QUnit.test("CSS escaping", assert => 10 | { 11 | function testSelector(opts) 12 | { 13 | let mustMatch = opts.mustMatch !== false; 14 | let doc = document.implementation.createHTMLDocument(); 15 | 16 | let style = doc.createElement("style"); 17 | doc.documentElement.appendChild(style); 18 | style.sheet.insertRule(opts.selector + " {}", 0); 19 | 20 | let element; 21 | try 22 | { 23 | element = doc.createElement(opts.tagName || "div"); 24 | } 25 | catch (e) 26 | { 27 | // Some characters we are going to test can not occur in tag names, 28 | // but we still have to make sure that no exception is thrown when 29 | // calling .querySelector() and .insertRule() 30 | element = null; 31 | mustMatch = false; 32 | } 33 | 34 | if (element) 35 | { 36 | for (let attr in opts.attributes) 37 | element.setAttribute(attr, opts.attributes[attr]); 38 | 39 | doc.documentElement.appendChild(element); 40 | } 41 | 42 | let foundElement = doc.querySelector(opts.selector); 43 | let filter = Filter.fromText("##" + opts.selector); 44 | 45 | if (!(filter instanceof ElemHideFilter)) 46 | assert.ok(false, opts.selector + " (not allowed in elemhide filters)"); 47 | else if (mustMatch) 48 | assert.equal(foundElement, element, opts.selector); 49 | else 50 | assert.ok(true, opts.selector); 51 | } 52 | 53 | function testEscape(s) 54 | { 55 | testSelector({ 56 | selector: escapeCSS(s), 57 | tagName: s 58 | }); 59 | 60 | testSelector({ 61 | selector: "#" + escapeCSS(s), 62 | attributes: {id: s} 63 | }); 64 | 65 | testSelector({ 66 | selector: "." + escapeCSS(s), 67 | attributes: {class: s}, 68 | 69 | // Whitespace characters split the class name, hence the selector 70 | // won't match. But we still have to make sure that no exception 71 | // is thrown when calling .querySelector() and .insertRule() 72 | mustMatch: !/\s/.test(s) 73 | }); 74 | 75 | testSelector({ 76 | selector: "[foo=" + quoteCSS(s) + "]", 77 | attributes: {foo: s} 78 | }); 79 | } 80 | 81 | for (let i = 1; i < 0x80; i++) 82 | { 83 | let chr = String.fromCharCode(i); 84 | 85 | // Make sure that all ASCII characters are correctly escaped. 86 | testEscape(chr); 87 | 88 | // Some characters are only escaped when in the first positon, 89 | // so we still have to make sure that everything is correctly escaped 90 | // in subsequent positions. 91 | testEscape("x" + chr); 92 | 93 | // Leading dashes must be escaped, when followed by certain characters. 94 | testEscape("-" + chr); 95 | } 96 | 97 | // Test some non-ASCII characters. However, those shouldn't 98 | // require escaping. 99 | testEscape("\uD83D\uDE3B\u2665\u00E4"); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /qunit/tests/prefs.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const {Prefs} = require("../../lib/prefs"); 4 | 5 | QUnit.module("Preferences", () => 6 | { 7 | function afterWrite(prefKey) 8 | { 9 | return Promise.race([ 10 | new Promise((resolve, reject) => 11 | { 12 | let onChange = (changes, area) => 13 | { 14 | if (area == "local" && prefKey in changes) 15 | { 16 | browser.storage.onChanged.removeListener(onChange); 17 | resolve(); 18 | } 19 | }; 20 | browser.storage.onChanged.addListener(onChange); 21 | }), 22 | // We take care to timeout after 500ms in case the onChange event doesn't 23 | // fire when we expect it to. For example, Firefox 66 has a bug[1] whereby 24 | // the event doesn't fire for falsey values. 25 | // 1 - https://bugzilla.mozilla.org/show_bug.cgi?id=1541449 26 | new Promise((resolve, reject) => 27 | { 28 | setTimeout(() => { resolve(); }, 500); 29 | }) 30 | ]); 31 | } 32 | 33 | function performStorageTests(prefName, prefKey, defaultValue, newValue, tests, 34 | assert) 35 | { 36 | let [method, whichValue] = tests.shift(); 37 | let value = whichValue == "default" ? defaultValue : newValue; 38 | 39 | return browser.storage.local.get(prefKey) 40 | .then(items => 41 | { 42 | let expectingWrite = typeof defaultValue == "object" || 43 | prefKey in items || 44 | whichValue == "new"; 45 | let promise = expectingWrite ? afterWrite(prefKey) : Promise.resolve(); 46 | 47 | if (method == "property") 48 | Prefs[prefName] = value; 49 | else 50 | Prefs.set(prefName, value); 51 | 52 | assert.deepEqual(Prefs[prefName], value, 53 | `Assigned Prefs['${prefName}'] ${whichValue} value`); 54 | 55 | return promise; 56 | }).then(() => 57 | browser.storage.local.get(prefKey) 58 | ).then(items => 59 | { 60 | if (whichValue == "default" && typeof defaultValue != "object") 61 | { 62 | assert.equal(prefKey in items, false, 63 | prefKey + " shouldn't be present in stoage.local"); 64 | } 65 | else 66 | { 67 | assert.equal(prefKey in items, true, 68 | prefKey + " should be present in stoage.local"); 69 | 70 | assert.deepEqual(items[prefKey], value, 71 | prefKey + " in storage.local should have the value " + 72 | JSON.stringify(value)); 73 | } 74 | 75 | if (tests.length) 76 | { 77 | return performStorageTests(prefName, prefKey, 78 | defaultValue, newValue, tests, assert); 79 | } 80 | }); 81 | } 82 | 83 | function testPrefStorage(prefName, defaultValue, newValue, assert) 84 | { 85 | let prefKey = "pref:" + prefName; 86 | let tests = [["property", "default"], 87 | ["property", "new"], 88 | ["property", "default"], 89 | ["set", "new"], 90 | ["set", "default"]]; 91 | 92 | let backupValue = Prefs[prefName]; 93 | return performStorageTests(prefName, prefKey, defaultValue, newValue, tests, 94 | assert) 95 | .catch(exception => { assert.ok(false, exception); }) 96 | .then(() => Prefs.set(prefName, backupValue)); 97 | } 98 | 99 | QUnit.test("Numerical preference", assert => 100 | { 101 | let done = assert.async(); 102 | 103 | testPrefStorage("patternsbackups", 0, 12, assert).then(done); 104 | }); 105 | 106 | QUnit.test("Boolean preference", assert => 107 | { 108 | let done = assert.async(); 109 | 110 | testPrefStorage("savestats", false, true, assert).then(done); 111 | }); 112 | 113 | QUnit.test("String preference", assert => 114 | { 115 | let done = assert.async(); 116 | 117 | let defaultValue = "https://notification.adblockplus.org/notification.json"; 118 | let newValue = "https://notification.adblockplus.org/foo\u1234bar.json"; 119 | 120 | testPrefStorage("notificationurl", defaultValue, newValue, assert) 121 | .then(done); 122 | }); 123 | 124 | QUnit.test("Object preference", assert => 125 | { 126 | let done = assert.async(); 127 | 128 | testPrefStorage("notificationdata", {}, {foo: 1, bar: 2}, assert) 129 | .then(done); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /qunit/tests/subscriptionInit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | let {chooseFilterSubscriptions} = require("../../lib/subscriptionInit"); 21 | 22 | QUnit.module("Subscription initialization", hooks => 23 | { 24 | let subscriptions = require("../subscriptions.json"); 25 | let origGetUILanguage; 26 | let language; 27 | 28 | hooks.before(() => 29 | { 30 | origGetUILanguage = browser.i18n.getUILanguage; 31 | browser.i18n.getUILanguage = () => language; 32 | }); 33 | 34 | hooks.after(() => 35 | { 36 | browser.i18n.getUILanguage = origGetUILanguage; 37 | }); 38 | 39 | QUnit.test("chooses default filter subscriptions", assert => 40 | { 41 | language = "en"; 42 | 43 | let subs = chooseFilterSubscriptions(subscriptions); 44 | assert.ok(subs); 45 | 46 | let sub1 = subs.find(sub => sub.type == "circumvention"); 47 | assert.ok(sub1); 48 | let sub2 = subs.find(sub => sub.type == "ads"); 49 | assert.ok(sub1); 50 | 51 | assert.deepEqual(sub1.languages, ["de", "en"]); 52 | assert.deepEqual(sub2.languages, ["en"]); 53 | }); 54 | 55 | QUnit.test("falls back to default language", assert => 56 | { 57 | language = "sl"; 58 | 59 | let subs = chooseFilterSubscriptions(subscriptions); 60 | assert.ok(subs); 61 | let sub1 = subs.find(sub => sub.type == "ads"); 62 | assert.ok(sub1); 63 | assert.deepEqual(sub1.languages, ["en"]); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /qunit/tests/uninstall.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | const {analytics} = require("../../adblockpluscore/lib/analytics"); 21 | const {filterStorage} = require("../../adblockpluscore/lib/filterStorage"); 22 | const {Prefs} = require("../../lib/prefs"); 23 | const {setUninstallURL} = require("../../lib/uninstall"); 24 | 25 | const realSetUninstallURL = browser.runtime.setUninstallURL; 26 | 27 | let uninstallURL; 28 | let urlParams = () => new URL(uninstallURL).search.substr(1).split("&"); 29 | 30 | QUnit.module("Uninstall URL", hooks => 31 | { 32 | hooks.beforeEach(assert => 33 | { 34 | browser.runtime.setUninstallURL = url => uninstallURL = url; 35 | assert.ok(true); 36 | }); 37 | hooks.afterEach(assert => 38 | { 39 | browser.runtime.setUninstalLURL = realSetUninstallURL; 40 | assert.ok(true); 41 | }); 42 | 43 | QUnit.test("parameters in uninstall URL", assert => 44 | { 45 | const info = require("info"); 46 | const expectedParams = [ 47 | ["an", info.addonName], 48 | ["av", info.addonVersion], 49 | ["ap", info.application], 50 | ["apv", info.applicationVersion], 51 | ["p", info.platform], 52 | ["fv", analytics.getFirstVersion()], 53 | ["pv", info.platformVersion], 54 | ["ndc", "0"], 55 | ["c", "0"], 56 | ["s", "3"] 57 | ]; 58 | setUninstallURL(); 59 | 60 | let params = urlParams(); 61 | for (let [name, value] of expectedParams) 62 | { 63 | value = encodeURIComponent(value); 64 | assert.ok( 65 | params.includes(`${name}=${value}`), 66 | `The parameter '${name}' has the expected value '${value}'` 67 | ); 68 | } 69 | }); 70 | 71 | QUnit.test("uninstall URL length", assert => 72 | { 73 | const maxLength = 255; 74 | setUninstallURL(); 75 | assert.ok( 76 | uninstallURL.length <= maxLength, 77 | `uninstall URL is not longer than ${maxLength} characters` 78 | ); 79 | }); 80 | 81 | let initialSubscriptions; 82 | 83 | QUnit.module("subscription parameter", { 84 | beforeEach() 85 | { 86 | browser.runtime.setUninstallURL = url => uninstallURL = url; 87 | initialSubscriptions = Array.from(filterStorage.subscriptions()); 88 | }, 89 | afterEach() 90 | { 91 | for (let subscription of initialSubscriptions) 92 | filterStorage.addSubscription(subscription); 93 | browser.runtime.setUninstalLURL = realSetUninstallURL; 94 | } 95 | }); 96 | 97 | QUnit.test("parameter s=0", assert => 98 | { 99 | for (let subscription of initialSubscriptions) 100 | filterStorage.removeSubscription(subscription); 101 | setUninstallURL(); 102 | assert.ok( 103 | urlParams().includes("s=0"), 104 | "subscription parameter 's' has the expected value '0'" 105 | ); 106 | }); 107 | 108 | QUnit.test("parameter s=1", assert => 109 | { 110 | for (let subscription of initialSubscriptions) 111 | { 112 | if (subscription.type != "ads") 113 | filterStorage.removeSubscription(subscription); 114 | } 115 | setUninstallURL(); 116 | assert.ok( 117 | urlParams().includes("s=1"), 118 | "subscription parameter 's' has the expected value '1'" + urlParams() 119 | ); 120 | }); 121 | 122 | QUnit.test("parameter s=2", assert => 123 | { 124 | for (let subscription of initialSubscriptions) 125 | { 126 | if (subscription.url != Prefs.subscriptions_exceptionsurl) 127 | filterStorage.removeSubscription(subscription); 128 | } 129 | setUninstallURL(); 130 | assert.ok( 131 | urlParams().includes("s=2"), 132 | "subscription parameter 's' has the expected value '2'" + urlParams() 133 | ); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /qunit/tests/url.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | const {extractHostFromFrame} = require("../../lib/url"); 21 | 22 | QUnit.module("URL/host tools", () => 23 | { 24 | QUnit.test("Extracting hostname from frame", assert => 25 | { 26 | function testFrameHostname(hierarchy, expectedHostname, message) 27 | { 28 | let frame = null; 29 | 30 | for (let url of hierarchy) 31 | frame = {parent: frame, url: new URL(url)}; 32 | 33 | assert.equal(extractHostFromFrame(frame), expectedHostname, message); 34 | } 35 | 36 | testFrameHostname(["http://example.com/"], "example.com", "single frame"); 37 | testFrameHostname(["http://example.com/", "http://example.org/"], 38 | "example.org", "with parent frame"); 39 | testFrameHostname(["http://example.com/", "data:text/plain,foo"], 40 | "example.com", "data: URL, hostname in parent"); 41 | testFrameHostname(["http://example.com/", "about:blank", "about:blank"], 42 | "example.com", "about:blank, hostname in ancestor"); 43 | testFrameHostname(["about:blank", "about:blank"], "", 44 | "about:blank, no hostname"); 45 | testFrameHostname(["http://xn--f-1gaa.com/"], "xn--f-1gaa.com", 46 | "with punycode"); 47 | testFrameHostname(["http://user:password@example.com/"], "example.com", 48 | "with auth credentials"); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /subscriptionLink.postload.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | if (document instanceof HTMLDocument) 21 | { 22 | document.addEventListener("click", event => 23 | { 24 | // Ignore right-clicks 25 | if (event.button == 2) 26 | return; 27 | 28 | // Ignore simulated clicks. 29 | if (event.isTrusted == false) 30 | return; 31 | 32 | // Search the link associated with the click 33 | let link = event.target; 34 | while (!(link instanceof HTMLAnchorElement)) 35 | { 36 | link = link.parentNode; 37 | 38 | if (!link) 39 | return; 40 | } 41 | 42 | let queryString = null; 43 | if (link.protocol == "http:" || link.protocol == "https:") 44 | { 45 | if (link.host == "subscribe.adblockplus.org" && link.pathname == "/") 46 | queryString = link.search.substr(1); 47 | } 48 | else 49 | { 50 | // Firefox 51 doesn't seem to populate the "search" property for 51 | // links with non-standard URL schemes so we need to extract the query 52 | // string manually. 53 | let match = /^abp:\/*subscribe\/*\?(.*)/i.exec(link.href); 54 | if (match) 55 | queryString = match[1]; 56 | } 57 | 58 | if (!queryString) 59 | return; 60 | 61 | // This is our link - make sure the browser doesn't handle it 62 | event.preventDefault(); 63 | event.stopPropagation(); 64 | 65 | // Decode URL parameters 66 | let title = null; 67 | let url = null; 68 | for (let param of queryString.split("&")) 69 | { 70 | let parts = param.split("=", 2); 71 | if (parts.length != 2 || !/\S/.test(parts[1])) 72 | continue; 73 | switch (parts[0]) 74 | { 75 | case "title": 76 | title = decodeURIComponent(parts[1]); 77 | break; 78 | case "location": 79 | url = decodeURIComponent(parts[1]); 80 | break; 81 | } 82 | } 83 | if (!url) 84 | return; 85 | 86 | // Default title to the URL 87 | if (!title) 88 | title = url; 89 | 90 | // Trim spaces in title and URL 91 | title = title.trim(); 92 | url = url.trim(); 93 | if (!/^(https?|ftp):/.test(url)) 94 | return; 95 | 96 | browser.runtime.sendMessage({ 97 | type: "subscriptions.add", 98 | title, 99 | url, 100 | confirm: true 101 | }); 102 | }, true); 103 | } 104 | -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "mocha": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/bin/downloadBrowsers.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import path from "path"; 19 | import {loadModules} from "../misc/utils.mjs"; 20 | 21 | (async() => 22 | { 23 | for (let [module] of await loadModules(path.join("test", "browsers"))) 24 | { 25 | if (!module.ensureBrowser) 26 | continue; 27 | 28 | for (let version of [module.oldestCompatibleVersion, 29 | module.getLatestVersion()]) 30 | { 31 | try 32 | { 33 | let binary = await module.ensureBrowser(await version); 34 | if (module.ensureDriver) 35 | await module.ensureDriver(binary); 36 | } 37 | catch (e) 38 | { 39 | console.warn(e); 40 | } 41 | } 42 | } 43 | })(); 44 | -------------------------------------------------------------------------------- /test/browsers/chromium.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import webdriver from "selenium-webdriver"; 19 | import chrome from "selenium-webdriver/chrome.js"; 20 | import got from "got"; 21 | import path from "path"; 22 | import {exec, execFile} from "child_process"; 23 | import {promisify} from "util"; 24 | 25 | export {ensureChromium as ensureBrowser} 26 | from "../../adblockpluscore/test/runners/chromium_download.mjs"; 27 | 28 | // We need to require the chromedriver, 29 | // otherwise on Windows the chromedriver path is not added to process.env.PATH. 30 | import "chromedriver"; 31 | 32 | export let target = "chrome"; 33 | 34 | // The Chromium version is a build number, quite obscure. 35 | // Chromium 63.0.3239.x is 508578 36 | // Chromium 65.0.3325.0 is 530368 37 | // We currently want Chromiun 63, as we still support it and that's the 38 | // loweset version that supports WebDriver. 39 | export let oldestCompatibleVersion = 508578; 40 | const OLDEST_DRIVER_VERSION = "2.36"; // Chromium 63 41 | 42 | export async function ensureDriver(browserBinary) 43 | { 44 | let chromedriverPath = 45 | path.resolve("adblockpluscore", "chromium-snapshots", "chromedriver"); 46 | let env = {...process.env, npm_config_chromedriver_skip_download: false, 47 | npm_config_tmp: chromedriverPath}; 48 | if (browserBinary) 49 | { 50 | let browserVersion; 51 | if (process.platform == "win32") 52 | { 53 | let arg = `'${browserBinary.split("'").join("''")}'`; 54 | let command = `(Get-ItemProperty ${arg}).VersionInfo.ProductVersion`; 55 | let {stdout} = await promisify(exec)(command, {shell: "powershell.exe"}); 56 | browserVersion = stdout.trim(); 57 | } 58 | else 59 | { 60 | let {stdout} = await promisify(execFile)(browserBinary, ["--version"]); 61 | browserVersion = stdout.trim().replace(/.*\s/, ""); 62 | } 63 | 64 | let majorBrowserVersion = parseInt(browserVersion.split(".")[0], 10); 65 | env.CHROMEDRIVER_VERSION = majorBrowserVersion >= 70 ? 66 | `LATEST_${browserVersion}` : OLDEST_DRIVER_VERSION; 67 | } 68 | else 69 | { 70 | env.DETECT_CHROMEDRIVER_VERSION = true; 71 | } 72 | 73 | await promisify(execFile)( 74 | process.execPath, 75 | [path.join("node_modules", "chromedriver", "install.js")], 76 | {env} 77 | ); 78 | } 79 | 80 | export async function getDriver(browserBinary, extensionPaths, insecure) 81 | { 82 | await ensureDriver(browserBinary); 83 | 84 | let options = new chrome.Options() 85 | .addArguments("--no-sandbox", "--disable-gpu") 86 | .addArguments(`load-extension=${extensionPaths.join(",")}`); 87 | 88 | if (browserBinary != null) 89 | options.setChromeBinaryPath(browserBinary); 90 | if (insecure) 91 | options.addArguments("--ignore-certificate-errors"); 92 | 93 | return new webdriver.Builder() 94 | .forBrowser("chrome") 95 | .setChromeOptions(options) 96 | .build(); 97 | } 98 | 99 | export async function getLatestVersion() 100 | { 101 | let os = process.platform; 102 | if (os == "win32") 103 | os = process.arch == "x64" ? "win64" : "win"; 104 | else if (os == "darwin") 105 | os = "mac"; 106 | 107 | let data = await got(`https://omahaproxy.appspot.com/all.json?os=${os}`).json(); 108 | let version = data[0].versions.find(ver => ver.channel == "stable"); 109 | let base = version.branch_base_position; 110 | 111 | if (version.true_branch.includes("_")) 112 | { 113 | // A wrong base may be caused by a mini-branch (patched) release 114 | // In that case, the base is taken from the unpatched version 115 | let cv = version.current_version.split("."); 116 | let unpatched = `${cv[0]}.${cv[1]}.${cv[2]}.0`; 117 | let unpatchedVersion = await got(`https://omahaproxy.appspot.com/deps.json?version=${unpatched}`).json(); 118 | base = unpatchedVersion.chromium_base_position; 119 | } 120 | 121 | return base; 122 | } 123 | -------------------------------------------------------------------------------- /test/browsers/edge.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import webdriver from "selenium-webdriver"; 19 | import msedgedriver from "msedgedriver"; 20 | import fs from "fs"; 21 | import path from "path"; 22 | import {exec, execFile} from "child_process"; 23 | import {promisify} from "util"; 24 | 25 | export let target = "chrome"; 26 | 27 | const MACOS_BINARY_PATH = "/Applications/Microsoft Edge.app" + 28 | "/Contents/MacOS/Microsoft Edge"; 29 | 30 | export function isBrowserInstalled() 31 | { 32 | if (process.platform == "win32") 33 | return true; 34 | if (process.platform == "darwin") 35 | return fs.existsSync(MACOS_BINARY_PATH); 36 | return false; 37 | } 38 | 39 | async function ensureDriver(browserBinary) 40 | { 41 | let version; 42 | if (process.platform == "win32") 43 | { 44 | let arg = browserBinary ? 45 | `'${browserBinary.split("'").join("''")}'` : 46 | "${Env:ProgramFiles(x86)}\\Microsoft\\Edge\\Application\\msedge.exe"; 47 | let command = `(Get-ItemProperty ${arg}).VersionInfo.ProductVersion`; 48 | let {stdout} = await promisify(exec)(command, {shell: "powershell.exe"}); 49 | version = stdout.trim(); 50 | } 51 | else 52 | { 53 | let binary = browserBinary || MACOS_BINARY_PATH; 54 | let {stdout} = await promisify(execFile)(binary, ["--version"]); 55 | version = stdout.trim().replace(/.*\s/, ""); 56 | } 57 | 58 | await promisify(execFile)( 59 | process.execPath, 60 | [path.join("node_modules", "msedgedriver", "install.js")], 61 | {env: {...process.env, EDGECHROMIUMDRIVER_VERSION: version, 62 | npm_config_edgechromiumdriver_skip_download: false}} 63 | ); 64 | } 65 | 66 | export async function getDriver(browserBinary, extensionPaths, insecure) 67 | { 68 | await ensureDriver(browserBinary); 69 | await msedgedriver.start(["--silent"], true); // Starts on localhost:9515 70 | 71 | let options = { 72 | args: ["--no-sandbox", "--disable-partial-raster", 73 | `load-extension=${extensionPaths.join(",")}`] 74 | }; 75 | if (browserBinary) 76 | options.binary = browserBinary; 77 | 78 | return new webdriver.Builder() 79 | .forBrowser("MicrosoftEdge") 80 | .withCapabilities({ 81 | "browserName": "MicrosoftEdge", 82 | "ms:edgeChromium": true, 83 | "ms:edgeOptions": options, 84 | "acceptInsecureCerts": insecure 85 | }) 86 | .usingServer("http://localhost:9515") 87 | .build(); 88 | } 89 | 90 | export function shutdown() 91 | { 92 | msedgedriver.stop(); 93 | } 94 | -------------------------------------------------------------------------------- /test/browsers/firefox.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import webdriver from "selenium-webdriver"; 19 | import firefox from "selenium-webdriver/firefox.js"; 20 | import command from "selenium-webdriver/lib/command.js"; 21 | import got from "got"; 22 | 23 | export {ensureFirefox as ensureBrowser} 24 | from "../../adblockpluscore/test/runners/firefox_download.mjs"; 25 | 26 | // We need to require the geckodriver, 27 | // otherwise on Windows the geckodriver path is not added to process.env.PATH. 28 | import "geckodriver"; 29 | 30 | export let target = "firefox"; 31 | export let oldestCompatibleVersion = "57.0"; 32 | 33 | export async function getDriver(browserBinary, extensionPaths, insecure) 34 | { 35 | let options = new firefox.Options().headless(); 36 | if (browserBinary != null) 37 | options.setBinary(browserBinary); 38 | if (insecure) 39 | options.set("acceptInsecureCerts", true); 40 | 41 | let driver = new webdriver.Builder() 42 | .forBrowser("firefox") 43 | .setFirefoxOptions(options) 44 | .build(); 45 | 46 | for (let extensionPath of extensionPaths) 47 | { 48 | await driver.execute( 49 | new command.Command("install addon") 50 | .setParameter("path", extensionPath) 51 | .setParameter("temporary", true) 52 | ); 53 | } 54 | 55 | return driver; 56 | } 57 | 58 | export async function getLatestVersion() 59 | { 60 | let data = await got("https://product-details.mozilla.org/1.0/firefox_versions.json").json(); 61 | return data.LATEST_FIREFOX_VERSION; 62 | } 63 | -------------------------------------------------------------------------------- /test/entrypoint.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | const TEST_PAGES_URL = process.env.TEST_PAGES_URL || 19 | "https://testpages.adblockplus.org/en/"; 20 | const TEST_PAGES_INSECURE = process.env.TEST_PAGES_INSECURE == "true"; 21 | 22 | import path from "path"; 23 | import url from "url"; 24 | import {exec} from "child_process"; 25 | import {promisify} from "util"; 26 | import got from "got"; 27 | import {checkLastError, loadModules} from "./misc/utils.mjs"; 28 | 29 | function getBrowserBinaries(module, browser) 30 | { 31 | let spec = process.env[`${browser.toUpperCase()}_BINARY`]; 32 | if (spec) 33 | { 34 | if (spec == "installed") 35 | return [{getPath: () => Promise.resolve(null)}]; 36 | if (spec.startsWith("path:")) 37 | return [{getPath: () => Promise.resolve(spec.substr(5))}]; 38 | if (spec.startsWith("download:")) 39 | { 40 | if (module.ensureBrowser) 41 | return [{getPath: () => module.ensureBrowser(spec.substr(9))}]; 42 | console.warn(`WARNING: Downloading ${browser} is not supported`); 43 | } 44 | } 45 | 46 | if (!module.ensureBrowser) 47 | { 48 | if (module.isBrowserInstalled()) 49 | return [{getPath: () => Promise.resolve(null)}]; 50 | return []; 51 | } 52 | 53 | return [ 54 | { 55 | version: "oldest", 56 | getPath: () => module.ensureBrowser(module.oldestCompatibleVersion) 57 | }, 58 | { 59 | version: "latest", 60 | getPath: () => module.getLatestVersion().then(module.ensureBrowser) 61 | } 62 | ]; 63 | } 64 | 65 | async function createDevenv(target) 66 | { 67 | if (process.env.SKIP_BUILD != "true") 68 | await promisify(exec)(`gulp devenv -t ${target}`); 69 | } 70 | 71 | async function getDriver(binary, devenvCreated, module) 72 | { 73 | let extensionPaths = [ 74 | path.resolve(`./devenv.${module.target}`), 75 | path.resolve("test", "helper-extension") 76 | ]; 77 | let [browserBin] = await Promise.all([binary.getPath(), devenvCreated]); 78 | return module.getDriver(browserBin, extensionPaths, TEST_PAGES_INSECURE); 79 | } 80 | 81 | async function waitForExtension(driver) 82 | { 83 | let origin; 84 | let handle; 85 | await driver.wait(async() => 86 | { 87 | for (handle of await driver.getAllWindowHandles()) 88 | { 89 | try 90 | { 91 | await driver.switchTo().window(handle); 92 | origin = await driver.executeAsyncScript(` 93 | let callback = arguments[arguments.length - 1]; 94 | (async() => 95 | { 96 | if (typeof browser != "undefined") 97 | { 98 | let info = await browser.management.getSelf(); 99 | if (info.optionsUrl == location.href) 100 | { 101 | callback(location.origin); 102 | return; 103 | } 104 | } 105 | callback(null); 106 | })();`); 107 | } 108 | catch (ex) 109 | { 110 | // Ignore windows that we're unable to switch to 111 | // or execute our script in 112 | } 113 | 114 | if (origin) 115 | return true; 116 | } 117 | return false; 118 | }, 5000, "options page not found"); 119 | 120 | return [handle, origin]; 121 | } 122 | 123 | async function getPageTests() 124 | { 125 | let options = TEST_PAGES_INSECURE ? {rejectUnauthorized: false} : {}; 126 | let response; 127 | 128 | try 129 | { 130 | response = await got(TEST_PAGES_URL, options); 131 | } 132 | catch (e) 133 | { 134 | console.warn(`Warning: Test pages not parsed at "${TEST_PAGES_URL}"\n${e}`); 135 | return []; 136 | } 137 | 138 | let regexp = /"test-link" href="(.*?)"[\S\s]*?>(?:

    )?(.*?) 154 | { 155 | let pageTests = await getPageTests(); 156 | let browsers = await loadModules(path.join("test", "browsers")); 157 | let suites = await loadModules(path.join("test", "suites")); 158 | 159 | for (let [module, browser] of browsers) 160 | { 161 | let devenvCreated = null; 162 | for (let binary of getBrowserBinaries(module, browser)) 163 | { 164 | let description = browser.replace(/./, c => c.toUpperCase()); 165 | if (binary.version) 166 | description += ` (${binary.version})`; 167 | 168 | describe(description, function() 169 | { 170 | this.timeout(0); 171 | this.pageTests = pageTests; 172 | this.testPagesURL = TEST_PAGES_URL; 173 | 174 | before(async function() 175 | { 176 | if (!devenvCreated) 177 | devenvCreated = createDevenv(module.target); 178 | 179 | this.driver = await getDriver(binary, devenvCreated, module); 180 | 181 | let caps = await this.driver.getCapabilities(); 182 | this.browserName = caps.getBrowserName(); 183 | this.browserVersion = caps.getBrowserVersion() || caps.get("version"); 184 | // eslint-disable-next-line no-console 185 | console.log(`Browser: ${this.browserName} ${this.browserVersion}`); 186 | 187 | [this.extensionHandle, 188 | this.extensionOrigin] = await waitForExtension(this.driver); 189 | }); 190 | 191 | beforeEach(async function() 192 | { 193 | let handles = await this.driver.getAllWindowHandles(); 194 | let defaultHandle = handles.shift(); 195 | 196 | for (let handle of handles) 197 | { 198 | if (handle != this.extensionHandle) 199 | { 200 | try 201 | { 202 | await this.driver.switchTo().window(handle); 203 | await this.driver.close(); 204 | } 205 | catch (e) {} 206 | } 207 | } 208 | 209 | await this.driver.switchTo().window(defaultHandle); 210 | }); 211 | 212 | it("extension loaded without errors", async function() 213 | { 214 | await checkLastError(this.driver, this.extensionHandle); 215 | }); 216 | 217 | for (let [{default: defineSuite}] of suites) 218 | defineSuite(); 219 | 220 | after(async function() 221 | { 222 | if (this.driver) 223 | await this.driver.quit(); 224 | 225 | if (module.shutdown) 226 | module.shutdown(); 227 | }); 228 | }); 229 | } 230 | } 231 | run(); 232 | })(); 233 | -------------------------------------------------------------------------------- /test/helper-extension/background.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2020-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | "use strict"; 19 | 20 | // The timeout allows the APB extension being ready on Firefox 21 | setTimeout(() => 22 | { 23 | chrome.management.getAll(extensions => 24 | { 25 | for (let extension of extensions) 26 | { 27 | if (extension.type == "extension" && 28 | extension.installType == "development" && 29 | extension.id != chrome.runtime.id && 30 | extension.name != "Chrome Automation Extension") 31 | { 32 | chrome.tabs.create({url: extension.optionsUrl}); 33 | return; 34 | } 35 | } 36 | }); 37 | }, 1000); 38 | -------------------------------------------------------------------------------- /test/helper-extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Helper extension", 3 | "version": "0.0.1", 4 | "manifest_version": 2, 5 | "description": "Opens the ABP options page", 6 | "background": { 7 | "scripts": ["background.js"] 8 | }, 9 | "permissions": ["management"] 10 | } 11 | -------------------------------------------------------------------------------- /test/misc/utils.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import assert from "assert"; 19 | import fs from "fs"; 20 | import path from "path"; 21 | import url from "url"; 22 | 23 | export async function checkLastError(driver, handle) 24 | { 25 | await driver.switchTo().window(handle); 26 | 27 | let error = await driver.executeAsyncScript(` 28 | let callback = arguments[arguments.length - 1]; 29 | browser.runtime.sendMessage({type: "debug.getLastError"}).then(callback);`); 30 | 31 | if (error != null) 32 | assert.fail("Unhandled error in background page: " + error); 33 | } 34 | 35 | export async function runWithHandle(driver, handle, callback) 36 | { 37 | let currentHandle = await driver.getWindowHandle(); 38 | await driver.switchTo().window(handle); 39 | try 40 | { 41 | return await callback(); 42 | } 43 | finally 44 | { 45 | await driver.switchTo().window(currentHandle); 46 | } 47 | } 48 | 49 | export async function loadModules(dirname) 50 | { 51 | let modules = []; 52 | for (let dirent of await fs.promises.readdir(dirname, {withFileTypes: true})) 53 | { 54 | let filename = path.resolve(dirname, dirent.name); 55 | let basename = path.parse(dirent.name).name; 56 | if (dirent.isDirectory()) 57 | filename = path.join(filename, "index.mjs"); 58 | modules.push([await import(url.pathToFileURL(filename)), basename]); 59 | } 60 | return modules; 61 | } 62 | -------------------------------------------------------------------------------- /test/suites/pages/index.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import assert from "assert"; 19 | import webdriver from "selenium-webdriver"; 20 | import {checkLastError, runWithHandle} from "../../misc/utils.mjs"; 21 | import specializedTests from "./specialized.mjs"; 22 | import defineSubscribeTest from "./subscribe.mjs"; 23 | import defineUninstallTest from "./uninstall.mjs"; 24 | import {getExpectedScreenshot, runFirstTest, 25 | getPage, isExcluded, runGenericTests} from "./utils.mjs"; 26 | 27 | const {By} = webdriver; 28 | 29 | async function getFilters(driver) 30 | { 31 | let filters = new Set(); 32 | for (let element of await driver.findElements(By.css("pre"))) 33 | { 34 | for (let line of (await element.getText()).split("\n")) 35 | filters.add(line); 36 | } 37 | return Array.from(filters).join("\n"); 38 | } 39 | 40 | async function updateFilters(driver, extensionHandle, url) 41 | { 42 | await driver.navigate().to(url); 43 | let filters = await getFilters(driver); 44 | let error = await runWithHandle(driver, extensionHandle, 45 | () => driver.executeAsyncScript(` 46 | let filters = arguments[0]; 47 | let callback = arguments[arguments.length - 1]; 48 | browser.runtime.sendMessage({type: "subscriptions.get", 49 | downloadable: true, 50 | special: true}).then(subs => 51 | Promise.all(subs.map(subscription => 52 | browser.runtime.sendMessage({type: "subscriptions.remove", 53 | url: subscription.url}) 54 | )) 55 | ).then(() => 56 | browser.runtime.sendMessage({type: "filters.importRaw", 57 | text: filters}) 58 | ).then(errors => callback(errors[0]), callback);`, filters)); 59 | 60 | if (error) 61 | throw error; 62 | 63 | await driver.navigate().refresh(); 64 | } 65 | 66 | export default () => 67 | { 68 | describe("Test pages", () => 69 | { 70 | it("discovered filter test cases", function() 71 | { 72 | assert.ok(this.test.parent.parent.pageTests.length > 0); 73 | }); 74 | 75 | defineSubscribeTest(); 76 | 77 | describe("Filters", function() 78 | { 79 | for (let [url, pageTitle] of this.parent.parent.pageTests) 80 | { 81 | it(pageTitle, async function() 82 | { 83 | let page = getPage(url); 84 | if (isExcluded(page, this.browserName, this.browserVersion)) 85 | this.skip(); 86 | 87 | if (page in specializedTests) 88 | { 89 | await updateFilters(this.driver, this.extensionHandle, url); 90 | let locator = By.className("testcase-area"); 91 | for (let element of await this.driver.findElements(locator)) 92 | await specializedTests[page].run(element, this.extensionHandle); 93 | } 94 | else 95 | { 96 | let expectedScreenshot = await getExpectedScreenshot(this.driver, 97 | url); 98 | await updateFilters(this.driver, this.extensionHandle, url); 99 | await runGenericTests(this.driver, expectedScreenshot, 100 | this.browserName, this.browserVersion, 101 | pageTitle, url); 102 | } 103 | 104 | await checkLastError(this.driver, this.extensionHandle); 105 | }); 106 | } 107 | }); 108 | 109 | describe("Final checks", () => 110 | { 111 | it("does not block unfiltered content", async function() 112 | { 113 | await assert.rejects( 114 | runFirstTest(this.driver, this.browserName, this.browserVersion, 115 | this.test.parent.parent.parent.pageTests, 116 | this.test.title, false), 117 | /Screenshots don't match/ 118 | ); 119 | }); 120 | 121 | defineUninstallTest(); 122 | }); 123 | }); 124 | }; 125 | -------------------------------------------------------------------------------- /test/suites/pages/specialized.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import assert from "assert"; 19 | import webdriver from "selenium-webdriver"; 20 | import {runWithHandle} from "../../misc/utils.mjs"; 21 | 22 | const {By} = webdriver; 23 | 24 | let specialized = {}; 25 | 26 | function clickButtonOrLink(element) 27 | { 28 | return element.findElement(By.css("a[href],button")).click(); 29 | } 30 | 31 | async function checkRequestBlocked(driver, resource) 32 | { 33 | let removeTimestamp = s => s.replace(/\?.\d*/, ""); 34 | 35 | await driver.wait(async() => 36 | { 37 | let logs = await driver.manage().logs().get("browser"); 38 | let expected = 39 | `${resource} - Failed to load resource: net::ERR_BLOCKED_BY_CLIENT`; 40 | return logs.some(l => removeTimestamp(l.message).includes(expected)); 41 | }, 2000, "request wasn't blocked"); 42 | } 43 | 44 | async function checkPing(element) 45 | { 46 | let driver = element.getDriver(); 47 | await clickButtonOrLink(element); 48 | await checkRequestBlocked(driver, "ping"); 49 | } 50 | 51 | specialized["filters/ping"] = { 52 | // ping test needs access to browser logs 53 | // https://github.com/mozilla/geckodriver/issues/284 54 | excludedBrowsers: {firefox: ""}, 55 | 56 | run: checkPing 57 | }; 58 | 59 | specialized["exceptions/ping"] = { 60 | excludedBrowsers: {firefox: ""}, 61 | 62 | async run(element) 63 | { 64 | await assert.rejects(async() => checkPing(element), /request wasn't blocked/); 65 | } 66 | }; 67 | 68 | async function getNumberOfHandles(driver) 69 | { 70 | return (await driver.getAllWindowHandles()).length; 71 | } 72 | 73 | async function checkPopup(element, extensionHandle) 74 | { 75 | let driver = element.getDriver(); 76 | let nHandles = await getNumberOfHandles(driver); 77 | let token = Math.floor(Math.random() * 1e8); 78 | await runWithHandle(driver, extensionHandle, () => driver.executeScript(` 79 | self.tabCreated${token} = new Promise(resolve => 80 | { 81 | browser.tabs.onCreated.addListener(function listener() 82 | { 83 | browser.tabs.onCreated.removeListener(listener); 84 | resolve(); 85 | }); 86 | });`)); 87 | await clickButtonOrLink(element); 88 | await runWithHandle(driver, extensionHandle, () => driver.executeAsyncScript(` 89 | let callback = arguments[arguments.length - 1]; 90 | self.tabCreated${token}.then(callback);`)); 91 | await driver.sleep(1000); 92 | return await getNumberOfHandles(driver) > nHandles; 93 | } 94 | 95 | specialized["filters/popup"] = { 96 | async run(element, extensionHandle) 97 | { 98 | let hasPopup = await checkPopup(element, extensionHandle); 99 | assert.ok(!hasPopup, "popup was closed"); 100 | } 101 | }; 102 | 103 | specialized["exceptions/popup"] = { 104 | async run(element, extensionHandle) 105 | { 106 | let hasPopup = await checkPopup(element, extensionHandle); 107 | assert.ok(hasPopup, "popup remained open"); 108 | } 109 | }; 110 | 111 | specialized["filters/other"] = { 112 | // other test needs access to browser logs 113 | // https://github.com/mozilla/geckodriver/issues/284 114 | excludedBrowsers: {firefox: ""}, 115 | 116 | async run(element) 117 | { 118 | let driver = element.getDriver(); 119 | await checkRequestBlocked(driver, "other/image.png"); 120 | } 121 | }; 122 | 123 | export {specialized as default}; 124 | -------------------------------------------------------------------------------- /test/suites/pages/subscribe.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import assert from "assert"; 19 | import webdriver from "selenium-webdriver"; 20 | import {checkLastError} from "../../misc/utils.mjs"; 21 | import {runFirstTest, takeScreenshot, writeScreenshotFile} from "./utils.mjs"; 22 | 23 | const {By} = webdriver; 24 | 25 | async function addSubscription(driver, extensionHandle) 26 | { 27 | await driver.switchTo().window((await driver.getAllWindowHandles())[0]); 28 | await driver.findElement(By.id("subscribe-button")).click(); 29 | 30 | await driver.switchTo().window(extensionHandle); 31 | await driver.switchTo().frame(0); 32 | let dialog = driver.findElement(By.id("dialog-content-predefined")); 33 | await driver.wait(async() => 34 | { 35 | let [displayed, title] = await Promise.all([ 36 | dialog.isDisplayed(), 37 | dialog.findElement(By.css(".title span")).getText() 38 | ]); 39 | return displayed && title == "ABP Testcase Subscription"; 40 | }, 2000, "subscribe dialog not shown"); 41 | await dialog.findElement(By.css(".default-focus")).click(); 42 | } 43 | 44 | async function checkSubscriptionAdded(driver, url) 45 | { 46 | let [added, err] = await driver.executeAsyncScript(` 47 | let callback = arguments[arguments.length - 1]; 48 | browser.runtime.sendMessage({type: "subscriptions.get", 49 | ignoreDisabled: true, 50 | downloadable: true}).then(subs => 51 | subs.some(s => 52 | s.url == "${url}abp-testcase-subscription.txt" 53 | ) 54 | ).then( 55 | res => callback([res, null]), 56 | err => callback([null, err]) 57 | );`); 58 | if (err) 59 | throw err; 60 | assert.ok(added, "subscription added"); 61 | } 62 | 63 | export default () => 64 | { 65 | it("subscribes to a link", async function() 66 | { 67 | let {testPagesURL} = this.test.parent.parent; 68 | try 69 | { 70 | await this.driver.navigate().to(testPagesURL); 71 | await this.driver.wait(async() => 72 | { 73 | await addSubscription(this.driver, this.extensionHandle); 74 | return true; 75 | }, 4000); 76 | await checkSubscriptionAdded(this.driver, testPagesURL); 77 | } 78 | catch (e) 79 | { 80 | let screenshot = await takeScreenshot(this.driver); 81 | let scrPath = await writeScreenshotFile(screenshot, this.browserName, 82 | this.browserVersion, 83 | this.test.title, "actual"); 84 | throw new Error(`${e.message}\n${testPagesURL}\n(see ${scrPath})`); 85 | } 86 | await this.driver.switchTo().window( 87 | (await this.driver.getAllWindowHandles())[0] 88 | ); 89 | await runFirstTest(this.driver, this.browserName, this.browserVersion, 90 | this.test.parent.parent.pageTests, this.test.title); 91 | await checkLastError(this.driver, this.extensionHandle); 92 | }); 93 | }; 94 | -------------------------------------------------------------------------------- /test/suites/pages/uninstall.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import {runWithHandle} from "../../misc/utils.mjs"; 19 | 20 | export default () => 21 | { 22 | it("opens uninstall page when extension is uninstalled", async function() 23 | { 24 | await runWithHandle(this.driver, this.extensionHandle, () => 25 | this.driver.executeScript("browser.management.uninstallSelf();") 26 | ); 27 | 28 | await this.driver.wait( 29 | async() => 30 | { 31 | for (let handle of await this.driver.getAllWindowHandles()) 32 | { 33 | await this.driver.switchTo().window(handle); 34 | let url = await this.driver.getCurrentUrl(); 35 | if (url.startsWith("https://adblockplus.org/en/uninstalled")) 36 | return true; 37 | } 38 | return false; 39 | }, 40 | 2000, 41 | "uninstall page did not open" 42 | ); 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /test/suites/pages/utils.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import path from "path"; 19 | import Jimp from "jimp"; 20 | import semver from "semver"; 21 | import specializedTests from "./specialized.mjs"; 22 | 23 | const SCREENSHOT_DIR = path.join("test", "screenshots"); 24 | 25 | export async function takeScreenshot(driver) 26 | { 27 | // On macOS scrollbars appear and disappear overlapping 28 | // the content as scrolling occurs. So we have to hide 29 | // the scrollbars to get reproducible screenshots. 30 | await driver.executeScript(` 31 | let style = document.createElement("style"); 32 | style.textContent = "html { overflow-y: scroll; }" 33 | document.head.appendChild(style); 34 | if (document.documentElement.clientWidth == window.innerWidth) 35 | style.textContent = "html::-webkit-scrollbar { display: none; }"; 36 | else 37 | document.head.removeChild(style);`); 38 | 39 | let fullScreenshot = new Jimp(0, 0); 40 | while (true) 41 | { 42 | let [width, height, offset] = await driver.executeScript(` 43 | window.scrollTo(0, arguments[0]); 44 | return [document.documentElement.clientWidth, 45 | document.documentElement.scrollHeight, 46 | window.scrollY];`, fullScreenshot.bitmap.height); 47 | let data = await driver.takeScreenshot(); 48 | let partialScreenshot = await Jimp.read(Buffer.from(data, "base64")); 49 | let combinedScreenshot = new Jimp(width, offset + 50 | partialScreenshot.bitmap.height); 51 | combinedScreenshot.composite(fullScreenshot, 0, 0); 52 | combinedScreenshot.composite(partialScreenshot, 0, offset); 53 | fullScreenshot = combinedScreenshot; 54 | 55 | if (fullScreenshot.bitmap.height >= height) 56 | break; 57 | } 58 | return fullScreenshot; 59 | } 60 | 61 | export function isExcluded(page, browserName, browserVersion) 62 | { 63 | let excluded; 64 | if (page in specializedTests) 65 | excluded = specializedTests[page].excludedBrowsers; 66 | // Chromium 63 doesn't have user stylesheets (required to 67 | // overrule inline styles). 68 | else if (page == "circumvention/inline-style-important") 69 | excluded = {chrome: "<64"}; 70 | // Older versions of Chromium don't run content 71 | // scripts in dynamically written documents. 72 | // Firefox <67 had a bug that resets the document: 73 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1528146 74 | else if (page == "circumvention/anoniframe-documentwrite") 75 | excluded = {chrome: "<64", firefox: "<67"}; 76 | // shadowing requires Firefox 63+ or 59+ with flag 77 | // dom.webcomponents.shadowdom.enabled 78 | else if (page == "snippets/hide-if-shadow-contains") 79 | excluded = {firefox: "<63"}; 80 | 81 | return !!excluded && browserName in excluded && 82 | semver.satisfies(semver.coerce(browserVersion), excluded[browserName]); 83 | } 84 | 85 | export async function getExpectedScreenshot(driver, url) 86 | { 87 | await driver.navigate().to(`${url}?expected=1`); 88 | return await takeScreenshot(driver); 89 | } 90 | 91 | export async function writeScreenshotFile(image, browserName, browserVersion, 92 | testTitle, suffix) 93 | { 94 | let title = testTitle.toLowerCase().replace(/[^a-z0-9]+/g, "_"); 95 | let filename = `${browserName}_${browserVersion}_${title}_${suffix}.png`; 96 | let screenshotPath = path.join(SCREENSHOT_DIR, filename); 97 | await image.write(screenshotPath); 98 | return screenshotPath; 99 | } 100 | 101 | export async function runGenericTests(driver, expectedScreenshot, 102 | browserName, browserVersion, testTitle, 103 | url, writeScreenshots = true) 104 | { 105 | let actualScreenshot; 106 | 107 | async function compareScreenshots() 108 | { 109 | await driver.wait(async() => 110 | { 111 | actualScreenshot = await takeScreenshot(driver); 112 | let actualBitmap = actualScreenshot.bitmap; 113 | let expectedBitmap = expectedScreenshot.bitmap; 114 | return (actualBitmap.width == expectedBitmap.width && 115 | actualBitmap.height == expectedBitmap.height && 116 | actualBitmap.data.compare(expectedBitmap.data) == 0); 117 | }, 5000, "Screenshots don't match", 500); 118 | } 119 | 120 | try 121 | { 122 | try 123 | { 124 | await compareScreenshots(); 125 | } 126 | catch (e2) 127 | { 128 | // In newer Firefox versions (79+) CSS might be cached: 129 | // https://bugzil.la/1657575. 130 | // We execute the test in a new tab to ensure the cache isn't used. 131 | try 132 | { 133 | await driver.switchTo().newWindow("tab"); 134 | await driver.navigate().to(url); 135 | } 136 | catch (e3) 137 | { 138 | // Older browsers don't support `newWindow`, fall-back to just refresh. 139 | await driver.navigate().refresh(); 140 | } 141 | 142 | await compareScreenshots(); 143 | } 144 | } 145 | catch (e) 146 | { 147 | if (!writeScreenshots) 148 | throw e; 149 | 150 | let paths = []; 151 | for (let [suffix, image] of [["actual", actualScreenshot], 152 | ["expected", expectedScreenshot]]) 153 | { 154 | paths.push(await writeScreenshotFile(image, browserName, browserVersion, 155 | testTitle, suffix)); 156 | } 157 | 158 | throw new Error(`${e.message}\n${url}\n(see ${paths})`); 159 | } 160 | } 161 | 162 | export function getPage(url) 163 | { 164 | return url.substr(url.lastIndexOf("/", url.lastIndexOf("/") - 1) + 1); 165 | } 166 | 167 | export async function runFirstTest(driver, browserName, browserVersion, 168 | pageTests, testTitle, 169 | writeScreenshots = true) 170 | { 171 | for (let [url] of pageTests) 172 | { 173 | let page = getPage(url); 174 | if (!(isExcluded(page, browserName, browserVersion) || 175 | page in specializedTests)) 176 | { 177 | let expectedScreenshot = await getExpectedScreenshot(driver, url); 178 | await driver.navigate().to(url); 179 | await runGenericTests(driver, expectedScreenshot, 180 | browserName, browserVersion, 181 | testTitle, url, writeScreenshots); 182 | return; 183 | } 184 | } 185 | throw new Error("No generic test did run"); 186 | } 187 | -------------------------------------------------------------------------------- /test/suites/qunit.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Adblock Plus , 3 | * Copyright (C) 2006-present eyeo GmbH 4 | * 5 | * Adblock Plus is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 3 as 7 | * published by the Free Software Foundation. 8 | * 9 | * Adblock Plus 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 General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with Adblock Plus. If not, see . 16 | */ 17 | 18 | import assert from "assert"; 19 | import webdriver from "selenium-webdriver"; 20 | import {checkLastError} from "../misc/utils.mjs"; 21 | 22 | const {By, until} = webdriver; 23 | 24 | export default () => 25 | { 26 | it("qunit", async function() 27 | { 28 | await this.driver.navigate().to(this.extensionOrigin + "/qunit/index.html"); 29 | let elem = await this.driver.wait( 30 | until.elementLocated(By.id("qunit-testresult")) 31 | ); 32 | await this.driver.wait(until.elementTextContains(elem, "tests completed")); 33 | 34 | let failures = await this.driver.findElements( 35 | By.css("#qunit-tests > .fail") 36 | ); 37 | let failureDescriptions = await Promise.all(failures.map(async failure => 38 | { 39 | let messages = await failure.findElements( 40 | By.css(".module-name, .test-name, .fail > .test-message") 41 | ); 42 | return (await Promise.all(messages.map(e => e.getText()))).join(", "); 43 | })); 44 | 45 | if (failureDescriptions.length > 0) 46 | { 47 | failureDescriptions.unshift(""); 48 | assert.fail(failureDescriptions.join("\n - ")); 49 | } 50 | 51 | await checkLastError(this.driver, this.extensionHandle); 52 | }); 53 | }; 54 | --------------------------------------------------------------------------------