Skip to content

Commit

Permalink
feat: working version with deny list
Browse files Browse the repository at this point in the history
* chore: tune jest to work with ESM

* feat(request-matcher): basic matching and unit tests

* feat: requests blocking (wip - missing unit tests)

* chore: add html test pages

* feat(List): new List data structure

Closes #40

* refactor(request_matcher): use the new List instead

* refactor: extract requestListener to its own file

* test: add playwright and intial e2e test

* refactor: move url fixtures to shared folder

* refactor:fix

* docs: add draft testing doc

* tests: pause work on automated functional tests for now

* feat: add model folder and DenyList model

* feat: add ListPopulator to prepoluate lists

* tests: use itty.bitty.site for https

* feat: add error listener to webRequest

* tests: silence window.alert's :)

* refactor: url fixtures export style

* refactor: share url fixtures with src/shared

* refactor: make bin script executable

* refactor: require a type when constructing a List

* refactor: list_populator

* refactor: update test pages html
  • Loading branch information
lfilho committed Jul 26, 2020
1 parent 3278f91 commit 65e33b5
Show file tree
Hide file tree
Showing 21 changed files with 403 additions and 17 deletions.
67 changes: 67 additions & 0 deletions __tests__/e2e/blocking_requests.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//TODO work in this file is paused/block.
// More details and follow up on: https://github.com/lfilho/sample-webextension/issues/38

/*
import browserWrapper from 'playwright-firefox';
import child_process from 'child_process';
import webExt from 'web-ext';
import pptr from 'puppeteer-core';
import { LOCAL_TEST_PAGE_URL } from '../shared/__url_fixtures.js';
*/
let page;
/*
let browser;
beforeAll(async () => {
webExt.default.util.logger.consoleStream.makeVerbose();
const runningInfo = await webExt.default.cmd
.run(
{
sourceDir: `${process.cwd()}/src`,
firefox: 'nightly'
},
{ shouldExitProgram: false }
)
.then((runner) => runner.extensionRunners[0].runningInfo);
// Needed because `webExt.cmd.run` returns before the DevTools agent starts running.
// Alternative would be to wrap the call to pptr.connect() with some custom retry logic
child_process.execSync('sleep 5');
const browserURL = `ws://127.0.0.1:${runningInfo.debuggerPort}`;
browser = await browserWrapper.firefox.connect({
wsEndpoint: browserURL,
logger: {
isEnabled: () => true,
log: (name, severity, message, args) => {
console.log(`[${severity}] ${name} ${message}. Args: ${args}`);
},
},
});
const context = await browser.newContext();
page = await context.newPage();
await page.goto(LOCAL_TEST_PAGE_URL);
});
afterAll(async () => {
await browser.close();
});
*/

describe('request to known tracker urls should be blocked', () => {
it.skip('loads the test page', async () => {
const text = await page.evaluate(() => {
return document.querySelector('[data-testid="test-page-header"]');
});
expect(text).toBeTruthy();
});

it.skip('blocks a known bad url by not loading its iframe', async () => {
//TODO waiting on resolution for: https://github.com/mozilla/web-ext/issues/1927
// More details and follow up on: https://github.com/lfilho/sample-webextension/issues/38
});
});
45 changes: 45 additions & 0 deletions __tests__/shared/test_page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>Test page</title>
<style type="text/css">
body {
padding: 2rem;
}

#bad-page-iframe,
#good-page-iframe {
display: block;
width: 100%;
height: 13rem;
border: 0.2rem solid transparent;
}

#bad-page-iframe {
border-color: red;
}

#good-page-iframe {
border-color: green;
}
</style>
</head>

<body>
<h1 data-testid="test-page-header">✅ Test page</h1>

<h2>Bad page iframe (should be blank):</h2>
<iframe id="bad-page-iframe"
src="https://itty.bitty.site/#bad_page/data:text/html;charset=utf-8;bxze64,XQAAAAI1AgAAAAAAAAAeGInmWR9D5qtrM4PFJv4W1okR98bzFbE2QlIWuIKxrmOKcpOUCz+bgN13tm1YwI65ckLaaMk97I0eK4ZJmz5rEyi5AYIrCihWPyWsU8imdwCjsAu9mVY4Uz3NDJSwS2tBy4fpBWIzlJM+PEIF1Dz7uHdB5dd0JkIYoHoNI51/xPcOHjaLtkdq929hapYOYARvUkxhKCAYZqoeWl/azB+/iRrItZhXoN2Xhb7cEouQ4ySG85i7cH0nZAJxZezxYYPzjYOKj1QECrYU2ufsndUGql9zsUXyBzVv3UCYzraVIpXLzEWabESPgYK1HY3biPhAiXzCU1i3msUdqKm0jl/HwqnVgJjhKaCsQvuv8pyy+VVkaz/XPVibLpmrbELCVUdG/jgKW2ig1pqjIAIoFb7YROo5+5026HlBBKlBJSJtJPiB5wAAbZMivZGwt72qmdA5R4jjQVr+djo2"
frameborder="0"></iframe>

<h2>Good page iframe (should not be blank):</h2>
<iframe id="good-page-iframe"
src="https://itty.bitty.site/#good_page/data:text/html;charset=utf-8;bxze64,XQAAAALrAQAAAAAAAAAeGInmWR9D5qtrM4PFJv4W1okR98bzFbE2QlIiIqEKRNhWozLGaVxy2UBVi6vb5PjLiS+KmhnoBI2zbVEi38FFqGt0V2dZ/n48NtEOjSTkOFXBuNLAKC6rlcwmvnHnUnMAWA2l/QImsEbNvvf1bv40vbtBzNp9F3TGp/HpcdlmwUSp59tjbjdUlRkOVnMxBaTPI6tnqjg9UBREwBH6Y4c6xLg53hJodPJyK8AysLdOEqC5OPFdGrHq7n6ViwKq90juHDM+UhFD8ug4iSu0Yo74yBMAo7Rtj+Jd5h9AkkjDCs/m4RMIP7KQKT4AldOuVvxaNDd4LcfbH/7lFxzMpv2FPyYxeR5ZmDMwE6422v7jh2OnV4nTcu42kWkhVBP7U06PxG1bBjmd5+5p11z/jK399tAktg=="
frameborder="0"></iframe>
</body>

</html>
7 changes: 7 additions & 0 deletions __tests__/src/lib/__request_fixtures.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { A_URL } from '../../../src/shared/__url_fixtures.js';

export const RANDOM_REQUEST_DETAILS = { url: A_URL };
export const BLOCKING_RESPONSE = {
CANCELLED: { cancel: true },
NOT_CANCELLED: { cancel: false },
};
62 changes: 62 additions & 0 deletions __tests__/src/lib/list.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import List from '../../../src/lib/model/list.js';
import {
A_URL,
ANOTHER_URL,
SOME_URLS,
} from '../../../src/shared/__url_fixtures.js';

let list;

beforeEach(() => {
const RANDOM_TYPE = List.types.DENY_LIST;
list = new List(RANDOM_TYPE);
});

describe('List', () => {
it('should add an url to the list', () => {
const originalListSize = list.size;
list.add(A_URL);
expect(list.size).toBe(originalListSize + 1);
});

it('should not add duplicate urls to the list', () => {
const originalListSize = list.size;
list.add(A_URL);
list.add(A_URL);
expect(list.size).toBe(originalListSize + 1);
});

it('should remove an url from the list', () => {
const originalListSize = list.size;
list.add(A_URL);
list.remove(A_URL);
expect(list.size).toBe(originalListSize);
});

it('should tell if an url is on the list', () => {
list.add(A_URL);
expect(list.has(A_URL)).toBe(true);
expect(list.has(ANOTHER_URL)).toBe(false);
});

it('should clear the list', () => {
list.add(A_URL);
list.add(ANOTHER_URL);
list.clear();
expect(list.size).toBe(0);
});

it('should return the size of the list', () => {
expect(list.size).toBe(0);
list.add(A_URL);
expect(list.size).toBe(1);
list.add(ANOTHER_URL);
expect(list.size).toBe(2);
});

it('should add several urls at once', () => {
expect(list.size).toBe(0);
list.bulkAdd(SOME_URLS);
expect(list.size).toBe(SOME_URLS.length);
});
});
25 changes: 25 additions & 0 deletions __tests__/src/lib/request_listener.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { jest } from '@jest/globals';

import requestListener from '../../../src/lib/request_listener.js';
import RequestMatcher from '../../../src/lib/request_matcher.js';

import {
RANDOM_REQUEST_DETAILS,
BLOCKING_RESPONSE,
} from './__request_fixtures.js';

jest.mock('../../../src/lib/request_matcher.js');

describe('Request Listener', () => {
it('should return a BlockingResponse with cancel: true for a tracker url', () => {
RequestMatcher.isDenied = jest.fn().mockReturnValue(true);
const result = requestListener(RANDOM_REQUEST_DETAILS);
expect(result).toEqual(BLOCKING_RESPONSE.CANCELLED);
});

it('should return a BlockingResponse with cancel: false for a non-tracker url', () => {
RequestMatcher.isDenied = jest.fn().mockReturnValue(false);
const result = requestListener(RANDOM_REQUEST_DETAILS);
expect(result).toEqual(BLOCKING_RESPONSE.NOT_CANCELLED);
});
});
17 changes: 17 additions & 0 deletions __tests__/src/lib/request_matcher.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import RequestMatcher from '../../../src/lib/request_matcher.js';
import { BAD_URL, GOOD_URL } from '../../../src/shared/__url_fixtures.js';

beforeAll(() => RequestMatcher.denyList.add(BAD_URL));
afterAll(() => RequestMatcher.denyList.clear());

describe('Request Matcher', () => {
it('should deny urls in the deny list', () => {
const result = RequestMatcher.isDenied(BAD_URL);
expect(result).toBe(true);
});

it('should not deny urls if they are not in the deny list', () => {
const result = RequestMatcher.isDenied(GOOD_URL);
expect(result).toBe(false);
});
});
2 changes: 2 additions & 0 deletions bin/change-ext-version.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/usr/bin/env node

import fs from 'fs';

const MANITFEST_FILE = 'src/manifest.json';
Expand Down
15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"homepage": "https://github.com/lfilho/sample-webextension#readme",
"scripts": {
"develop": "npm run web-ext -- run",
"develop": "npm run web-ext -- run --bc --url $(pwd)/__tests__/shared/test_page.html",
"format": "prettier --write \"**/*.{js,json,css,md}\"",
"//eslint is configured with prettier, so it will check and fix js formatting too": "",
"lint": "npm run lint:js && npm run lint:ext",
Expand All @@ -25,18 +25,19 @@
"lint:js:fix": "npm run lint:js -- --fix",
"pretest": "npm run lint",
"start": "npm run develop",
"test": "NODE_ENV=test jest",
"test": "NODE_ENV=test node --experimental-vm-modules node_modules/jest/bin/jest.js",
"test:watch": "npm test -- --watch",
"web-ext": "web-ext",
"//Release related scripts:": "",
"release": "semantic-release",
"build": "npm run web-ext -- build",
"get-version": "echo $npm_package_version",
"change-ext-version": "node bin/change-ext-version.js"
"change-ext-version": "bin/change-ext-version.js"
},
"devDependencies": {
"@commitlint/cli": "8.3.5",
"@commitlint/config-conventional": "8.3.4",
"@jest/globals": "26.0.1",
"@semantic-release/commit-analyzer": "8.0.1",
"@semantic-release/exec": "5.0.0",
"@semantic-release/github": "7.0.7",
Expand All @@ -59,15 +60,16 @@
"jest": {
"displayName": "test",
"testRegex": "\\.spec\\.js$",
"testEnvironment": "jsdom",
"testEnvironment": "jest-environment-node",
"testURL": "http://localhost",
"watchPlugins": [
"jest-watch-typeahead/filename",
"jest-watch-typeahead/testname"
],
"coveragePathIgnorePatterns": [
"/node_modules/"
]
],
"transform": {}
},
"husky": {
"hooks": {
Expand Down Expand Up @@ -145,5 +147,6 @@
"webExt": {
"sourceDir": "src",
"artifactsDir": "dist"
}
},
"dependencies": {}
}
10 changes: 10 additions & 0 deletions src/background_script.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
</head>
<body>
<script type="module" src="./background_script.js"></script>
</body>
</html>
5 changes: 4 additions & 1 deletion src/background_script.js
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
console.log('This is coming from background script!');
import RequestBlocker from './lib/request_blocker.js';

console.log('Extension is ready to start blocking!');
RequestBlocker.startMonitoring();
2 changes: 1 addition & 1 deletion src/browserAction/script.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
window.alert('This is coming from the extension button!');
console.info('This is coming from the extension button!');
2 changes: 1 addition & 1 deletion src/content_script.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
window.alert('This is comming from content script!');
console.info('This is comming from content script!');
20 changes: 20 additions & 0 deletions src/lib/list_populator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import List from './model/list.js';
import { BAD_URLS } from '../shared/__url_fixtures.js';

// TODO for now, just populating it with the examples from fixtures
// TODO real deal: fetch from local files shipped with the extension, augment it with live sources...
// https://github.com/lfilho/sample-webextension/issues/24
// https://github.com/lfilho/sample-webextension/issues/18
// https://github.com/lfilho/sample-webextension/issues/17

export default class ListPopulator {
static async populateList(list) {
const listTypeToUrlMapper = {
[List.types.DENY_LIST]: BAD_URLS,
};
const urls = listTypeToUrlMapper[list.type];
list.bulkAdd(urls);

//TODO https://github.com/lfilho/sample-webextension/issues/8
}
}
7 changes: 7 additions & 0 deletions src/lib/model/deny_list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import List from './list.js';

export default class DenyList extends List {
constructor() {
super(List.types.DENY_LIST);
}
}
50 changes: 50 additions & 0 deletions src/lib/model/list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
export default class List {
/*
* Using a Set and mainly proxying the methods to their native ones.
* So if we change the underlaying data structure in the future,
* the API stays the same and the impact on the codebase is minimal.
*/

constructor(type) {
if (!type) {
throw new Error('List constructor needs a type');
//TODO Refactor Error strategy later on to contain an unique error code and both technical and user friendly messages
}
this.list = new Set();
this.type = type;
}

static get types() {
return Object.freeze({
DENY_LIST: 'DENY',
ALLOW_LIST: 'ALLOW',
});
}

has(url) {
// Look kid, one day there will be a complex algorithm here.
// For now, full matches only. In future iterations,
// we should support some sort of pattern instead: *evil-tracker.com/*
return this.list.has(url);
}

add(url) {
this.list.add(url);
}

remove(url) {
this.list.delete(url);
}

clear() {
this.list.clear();
}

get size() {
return this.list.size;
}

bulkAdd(values) {
values.forEach(this.list.add.bind(this.list));
}
}
Loading

0 comments on commit 65e33b5

Please sign in to comment.