aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README11
-rwxr-xr-xbuild.sh15
-rw-r--r--common/Test.js55
-rw-r--r--html/common.css12
-rw-r--r--html/display_panel/content/display-panel.html2
-rw-r--r--html/display_panel/content/main_panel.js12
-rw-r--r--html/display_panel/content/panel-styles.css3
-rw-r--r--main_background.js15
-rw-r--r--test/SpecRunner.html43
-rw-r--r--test/resources/app-trilicensed.js1
-rw-r--r--test/resources/index.html38
-rw-r--r--test/resources/jquery.js1
-rw-r--r--test/resources/jslicense.html25
-rw-r--r--test/resources/proprietary.js1
-rw-r--r--test/resources/tracker.js1
-rw-r--r--test/spec/LibreJSSpec.js212
17 files changed, 446 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index 9a139c8..3314ced 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@ SDK LibreJS source/
# dependencies
node_modules/
+test/lib/
# artifacts
librejs.xpi
diff --git a/README b/README
index a380900..f5cf254 100644
--- a/README
+++ b/README
@@ -15,6 +15,11 @@ To build the extension run:
To build the extension plus create a .xpi package run:
$ ./build.sh
+To build the extension including the automated test suite (see TEST below) run:
+ $ ./build.sh -t
+ or
+ $ ./build.sh --test
+
Note: this build.sh script relies on no new source files being created.
@@ -24,6 +29,12 @@ To debug this add-on on IceCat and other Firefox derivatives, browse to the spec
LibreJS should work with other WebExtensions-compliant browsers; but currently, none of them meet the freedom standards of GNU, so no help will be provided for their usage.
+TEST:
+
+An automated test suite runs automatically in its own tab whenever the extension
+is loaded as a "temporary add-on" from about:debugging.
+Otherwise (if included in the xpi, see BUILD above) it can be launched from the
+UI by clicking the [Automated self test...] button.
CONTACT:
diff --git a/build.sh b/build.sh
index 1e07fcb..0cd41d7 100755
--- a/build.sh
+++ b/build.sh
@@ -1,6 +1,16 @@
PATH=$PATH:./node_modules/.bin
which browserify > /dev/null || (echo "can not find browserify" && false) || exit
+# prepare / update testing environment if needed
+JASMINE_VER=3.2.1
+JASMINE_LIB="test/lib/jasmine-$JASMINE_VER"
+if ! [ -d "$JASMINE_LIB" ]; then
+ mkdir -p test/lib
+ rm -rf test/lib/jasmine-*
+ JASMINE_URL="https://github.com/jasmine/jasmine/releases/download/v$JASMINE_VER/jasmine-standalone-$JASMINE_VER.zip"
+ curl -L -o "$JASMINE_LIB.zip" "$JASMINE_URL" && unzip -d test/ $JASMINE_LIB.zip lib/**
+ rm -f "$JASMINE_LIB.zip"
+fi
# Build the main file
browserify main_background.js -o bundle.js
@@ -9,7 +19,10 @@ browserify main_background.js -o bundle.js
mkdir ./build_temp
# Move source files to temp directory
-cp -r icons ./build_temp
+if [ "$1" == "-t" -o "$1" == "--test" ]; then
+ cp -r ./test ./build_temp
+fi
+cp -r ./icons ./build_temp
cp -r ./html ./build_temp
cp -r ./content ./build_temp
cp -r ./common ./build_temp
diff --git a/common/Test.js b/common/Test.js
new file mode 100644
index 0000000..e272d1e
--- /dev/null
+++ b/common/Test.js
@@ -0,0 +1,55 @@
+/**
+* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
+*
+* Copyright (C) 2018 Giorgio Maone <giorgio@maone.net>
+*
+* This file is part of GNU LibreJS.
+*
+* GNU LibreJS is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* GNU LibreJS is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with GNU LibreJS. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+"use strict";
+var Test = (() => {
+ const RUNNER_URL = browser.extension.getURL("/test/SpecRunner.html");
+ return {
+ /*
+ returns RUNNER_URL if it's a test-enabled build or an about:debugging
+ temporary extension session, null otherwise
+ */
+ async getURL() {
+ let url = RUNNER_URL;
+ try {
+ await fetch(url);
+ } catch (e) {
+ url = null;
+ }
+ this.getURL = () => url;
+ return url;
+ },
+
+ async getTab(activate = false) {
+ let url = await this.getURL();
+ let tab = url ? (await browser.tabs.query({url}))[0] ||
+ (await browser.tabs.create({url}))
+ : null;
+ if (tab && activate) {
+ await browser.tabs.update(tab.id, {active: true});
+ }
+ return tab;
+ }
+ };
+})();
+if (typeof module === "object") {
+ module.exports = Test;
+}
diff --git a/html/common.css b/html/common.css
index cf2c5d1..14931fa 100644
--- a/html/common.css
+++ b/html/common.css
@@ -3,6 +3,7 @@ html {
margin:0px;
color:#000 !important;
background:url('background-panel.png') !important;
+ font-family: sans-serif;
}
body {
padding:0;
@@ -26,4 +27,15 @@ h1.libre {
font-weight:bold;
background:url('librejs-title.png') no-repeat top left;
text-indent:-1000px;
+ position: relative;
+}
+h1.libre span {
+ font-family: sans-serif;
+ position: absolute;
+ bottom: 32px;
+ padding: 0 16px;
+ left: 230px;
+ display: block;
+ text-indent: 0;
+ vertical-align: bottom;
}
diff --git a/html/display_panel/content/display-panel.html b/html/display_panel/content/display-panel.html
index df153b3..80f4cb9 100644
--- a/html/display_panel/content/display-panel.html
+++ b/html/display_panel/content/display-panel.html
@@ -53,6 +53,7 @@
<button id="complain">Complain to site owner</button>
<button id="report-tab">Show this report in a new tab</button>
<button id="open-options">Settings...</button>
+ <button id="autotest">Automated self test...</button>
</div>
</div>
<div id="info">
@@ -100,5 +101,6 @@
</div>
</div>
</body>
+<script src="/common/Test.js"></script>
<script src="main_panel.js"></script>
</html>
diff --git a/html/display_panel/content/main_panel.js b/html/display_panel/content/main_panel.js
index c55b167..2509545 100644
--- a/html/display_panel/content/main_panel.js
+++ b/html/display_panel/content/main_panel.js
@@ -38,6 +38,18 @@ myPort.postMessage({"update": true, tabId: parseInt(currentReport && currentRepo
// Display the actual extension version Number
document.querySelector("#version").textContent = browser.runtime.getManifest().version;
+// Enable autotest button if this is a test-enabled build / session
+(async () => {
+ if (await Test.getURL()) {
+ let button = document.querySelector("#autotest");
+ button.style.display = "block";
+ button.onclick = async () => {
+ await Test.getTab(true);
+ close();
+ }
+ }
+})();
+
var liTemplate = document.querySelector("#li-template");
liTemplate.remove();
diff --git a/html/display_panel/content/panel-styles.css b/html/display_panel/content/panel-styles.css
index 0d849e4..940a8ff 100644
--- a/html/display_panel/content/panel-styles.css
+++ b/html/display_panel/content/panel-styles.css
@@ -141,7 +141,8 @@ span.blocked {
display: none;
}
-.tab #must-reload, .tab #buttons, .empty #buttons {
+.tab #must-reload, .tab #buttons,
+.empty #complain, .empty #report-tab, #autotest {
display: none;
}
diff --git a/main_background.js b/main_background.js
index cdc987e..5c2e8df 100644
--- a/main_background.js
+++ b/main_background.js
@@ -1175,6 +1175,21 @@ async function init_addon() {
ResponseProcessor.install(ResponseHandler);
legacy_license_lib.init();
+
+
+ let Test = require("./common/Test");
+ if (Test.getURL()) {
+ // export testable functions to the global scope
+ this.LibreJS = {
+ editHtml,
+ handle_script,
+ ExternalLicenses,
+ };
+ // create or focus the autotest tab if it's a debugging session
+ if ((await browser.management.getSelf()).installType === "development") {
+ Test.getTab(true);
+ }
+ }
}
diff --git a/test/SpecRunner.html b/test/SpecRunner.html
new file mode 100644
index 0000000..42a372f
--- /dev/null
+++ b/test/SpecRunner.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<!--
+/**
+* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
+*
+* Copyright (C) 2018 Giorgio Maone <giorgio@maone.net>
+*
+* This file is part of GNU LibreJS.
+*
+* GNU LibreJS is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* GNU LibreJS is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with GNU LibreJS. If not, see <http://www.gnu.org/licenses/>.
+*/
+-->
+<head>
+ <meta charset="utf-8">
+ <title>LibreJS Tests</title>
+
+ <link rel="shortcut icon" type="image/png" href="/icons/librejs.png">
+ <link rel="stylesheet" href="lib/jasmine-3.2.1/jasmine.css">
+ <link rel="stylesheet" href="/html/common.css">
+ <script src="lib/jasmine-3.2.1/jasmine.js"></script>
+ <script src="lib/jasmine-3.2.1/jasmine-html.js"></script>
+ <script src="lib/jasmine-3.2.1/boot.js"></script>
+
+ <script src="spec/LibreJSSpec.js"></script>
+
+</head>
+
+<body>
+ <h1 class="libre">LibreJS <span>Autotest</span></h1>
+</body>
+</html>
diff --git a/test/resources/app-trilicensed.js b/test/resources/app-trilicensed.js
new file mode 100644
index 0000000..7b2c2ea
--- /dev/null
+++ b/test/resources/app-trilicensed.js
@@ -0,0 +1 @@
+document.write(`<p>Executing ${document.currentScript.src}</p>`);
diff --git a/test/resources/index.html b/test/resources/index.html
new file mode 100644
index 0000000..c5f364c
--- /dev/null
+++ b/test/resources/index.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<!--
+/**
+* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
+*
+* Copyright (C) 2018 Giorgio Maone <giorgio@maone.net>
+*
+* This file is part of GNU LibreJS.
+*
+* GNU LibreJS is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* GNU LibreJS is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with GNU LibreJS. If not, see <http://www.gnu.org/licenses/>.
+*/
+-->
+<head>
+ <meta charset="utf-8">
+ <title>LibreJS test document</title>
+ <link rel="shortcut icon" type="image/png" href="/icons/librejs.png">
+</head>
+<body>
+<h1>LibreJS test document</h1>
+<a href="jslicense.html" rel="jslicense">JavaScript license information</a>
+<script src="jquery.js"></script>
+<script src="app-trilicensed.js"></script>
+<script src="proprietary.js"></script>
+<script src="tracker.js"></script>
+</body>
+</html>
diff --git a/test/resources/jquery.js b/test/resources/jquery.js
new file mode 100644
index 0000000..7b2c2ea
--- /dev/null
+++ b/test/resources/jquery.js
@@ -0,0 +1 @@
+document.write(`<p>Executing ${document.currentScript.src}</p>`);
diff --git a/test/resources/jslicense.html b/test/resources/jslicense.html
new file mode 100644
index 0000000..0cd0449
--- /dev/null
+++ b/test/resources/jslicense.html
@@ -0,0 +1,25 @@
+<table id="jslicense-labels1">
+<tr>
+<td><a href="jquery.js">Fake jQuery</a></td>
+<td><a href="http://www.jclark.com/xml/copying.txt">Expat</a>
+<td><a href="jquery-1.7.tar.gz">jquery-1.7.tar.gz</a></td>
+</tr>
+<tr>
+<td><a href="app-trilicensed.js">App specific script, tri-licensed (2 free and 1 proprietary license)</a></td>
+<td>
+<a href="magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt">GNU V3</a>
+<br>
+<a href="http://www.jclark.com/xml/copying.txt">Expat</a>
+<br>
+<a href="https://evil.com/someproprietarylicense">Proprietary, optional</a></td>
+</td>
+<td><a href="app-trilicensed.js">app-trilicensed.js</a></td>
+</tr>
+<tr>
+<td><a href="proprietary.js">App specific script, proprietary only</a></td>
+<td>
+<a href="https://evil.com/someproprietarylicense">Proprietary</a></td>
+</td>
+<td><a href="proprietary.js">proprietary.js</a></td>
+</tr>
+</table>
diff --git a/test/resources/proprietary.js b/test/resources/proprietary.js
new file mode 100644
index 0000000..7b2c2ea
--- /dev/null
+++ b/test/resources/proprietary.js
@@ -0,0 +1 @@
+document.write(`<p>Executing ${document.currentScript.src}</p>`);
diff --git a/test/resources/tracker.js b/test/resources/tracker.js
new file mode 100644
index 0000000..7b2c2ea
--- /dev/null
+++ b/test/resources/tracker.js
@@ -0,0 +1 @@
+document.write(`<p>Executing ${document.currentScript.src}</p>`);
diff --git a/test/spec/LibreJSSpec.js b/test/spec/LibreJSSpec.js
new file mode 100644
index 0000000..8638d67
--- /dev/null
+++ b/test/spec/LibreJSSpec.js
@@ -0,0 +1,212 @@
+/*
+* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
+*
+* Copyright (C) 2018 Giorgio Maone <giorgio@maone.net>
+*
+* This file is part of GNU LibreJS.
+*
+* GNU LibreJS is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* GNU LibreJS is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with GNU LibreJS. If not, see <http://www.gnu.org/licenses/>.
+*/
+"use strict";
+
+describe("LibreJS' components", () => {
+ const LENIENT_TAG_MSG =
+ `actual requirement? at this moment (20181001)
+ formal validity only gets checked, therefore this test would fail.`;
+
+ let LibreJS = browser.extension.getBackgroundPage().LibreJS;
+ let license = {
+ id: 'GPL-3.0',
+ url: 'http://www.gnu.org/licenses/gpl-3.0.html',
+ magnet: 'magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt',
+ };
+ let unknownLicense = {
+ id: 'Acme-proprietary-1.5',
+ url: 'http://www.acme.com/license-1.5.html',
+ magnet: 'magnet:?xt=urn:btih:2f739d935676111cfff4b4693e3816e664797050&dn=acme-1.5.txt'
+ };
+
+ let trivial = "1+1";
+ let nontrivial = `function nt() { document.documentElement.innerHTML=""; nt(); }`;
+ let licensed = `// @license ${license.id} ${license.magnet}\n${nontrivial}\n// @license-end`;
+ let unknownLicensed = `// @license ${unknownLicense.id} ${unknownLicense.magnet}\n${nontrivial}\n// @license-end`;
+ let malformedLicensed = `// @license\n${nontrivial}`;
+
+ let tab;
+
+ beforeAll(async () => {
+ let url = browser.extension.getURL("/test/resources/index.html");
+ tab = (await browser.tabs.query({url}))[0] || (await browser.tabs.create({url}));
+ });
+
+ describe("The external script source processor", () => {
+ let url = "https://www.gnu.org/mock-script.js";
+
+ let processScript = async (source, whitelisted = false) =>
+ await LibreJS.handle_script({
+ text: source,
+ request: {url, tabId: tab.id, documentUrl: tab.url, frameId: 0},
+ }, whitelisted);
+
+ it("should accept whitelisted scripts", async () => {
+ expect(await processScript(nontrivial, true) || nontrivial).toContain(nontrivial);
+ });
+
+ it("should block trivial scripts too", async () => {
+ let processed = await processScript(trivial);
+ expect(processed || trivial).not.toContain(trivial);
+ });
+
+ it("should block non-trivial scripts", async () => {
+ let processed = await processScript(nontrivial);
+ expect(processed || nontrivial).not.toContain(nontrivial);
+ });
+
+ it("should accept scripts with known free license tags", async () => {
+ let processed = await processScript(licensed);
+ expect(processed || licensed).toContain(nontrivial);
+ });
+
+ xit("should block scripts with unknown license tags", async () => {
+ let processed = await processScript(unknownLicensed);
+ expect(processed).not.toContain(nontrivial);
+ }).pend(LENIENT_TAG_MSG);
+
+ it("should block scripts with malformed license tags", async () => {
+ let processed = await processScript(malformedLicensed);
+ expect(processed).not.toContain(nontrivial);
+ });
+ });
+
+ describe("The HTML processor", () => {
+ let processHtml =
+ async (html, whitelisted = false) =>
+ LibreJS.editHtml(html, tab.url, tab.id, 0, whitelisted);
+
+ let addScript = (html, script, before = "</head>") =>
+ html.replace(before, `<script>${script}</script>${before}`);
+
+ function extractScripts(html, def = "") {
+ let matches = html && html.match(/<script>[^]*?<\/script>/g);
+ return matches && matches.join("") || def;
+ }
+
+ let html, nontrivialInHtml;
+ beforeAll(async () => {
+ html = (await browser.tabs.executeScript(tab.id, {
+ runAt: "document_start",
+ code: "document.documentElement.outerHTML"
+ }))[0];
+ nontrivialInHtml = addScript(html, nontrivial);
+ });
+
+ it("should not modify scriptless documents", async () => {
+ expect(await processHtml(html)).toBeNull();
+ });
+
+ it("should not modify whitelisted documents", async () => {
+ expect(await processHtml(nontrivialInHtml, true)).toBeNull();
+ });
+
+ it("should accept trivial scripts", async () => {
+ let trivialInHtml = addScript(html, trivial);
+ let processed = await processHtml(trivialInHtml);
+ expect(extractScripts(processed, trivial)).toContain(trivial);
+ });
+
+ it("should block non-trivial scripts", async () => {
+ let processed = await processHtml(nontrivialInHtml);
+ expect(extractScripts(processed, nontrivial)).not.toContain(nontrivial);
+ });
+
+ it("should accept scripts with known free license tags", async () => {
+ let licensedInHtml = addScript(html, licensed);
+ let processed = await processHtml(licensedInHtml);
+ expect(extractScripts(processed, licensed)).toContain(nontrivial);
+ });
+
+ xit("should block scripts with unknown license tags", async () => {
+ let unknownInHtml = addScript(html, unknownLicensed);
+ let processed = await processHtml(unknownInHtml);
+ expect(extractScripts(processed, nontrivial)).not.toContain(nontrivial);
+ }).pend(LENIENT_TAG_MSG);
+
+ it("should block scripts with malformed license tags", async () => {
+ let malformedInHtml = addScript(html, malformedLicensed);
+ let processed = await processHtml(malformedInHtml);
+ expect(extractScripts(processed, nontrivial)).not.toContain(nontrivial);
+ });
+
+ it("should accept scripts on globally licensed pages", async () => {
+ let globalLicense = `/* @licstart The following is the entire license notice
+ for the JavaScript code in this page.
+ -- Some free license --
+ @licend The above is the entire license notice for the JavaScript code in this page. */`;
+
+ let licensed = addScript(nontrivialInHtml, globalLicense, "<script>");
+ let processed = await processHtml(html);
+ expect(extractScripts(processed, licensed)).toContain(nontrivial);
+ });
+
+ it("should discriminate trivial, non-trivial and licensed mixed on the same page", async () => {
+ let mixedPage = addScript(addScript(nontrivialInHtml, trivial), licensed);
+ let processed = await processHtml(mixedPage);
+ expect(processed).not.toBeNull();
+ let scripts = extractScripts(processed, nontrivial);
+ expect(scripts).toContain(trivial);
+ expect(scripts).toContain(licensed);
+ expect(scripts.replace(licensed, "")).not.toContain(nontrivial);
+ });
+ });
+
+ describe("The external (Web Labels) license checker", () => {
+ let {ExternalLicenses} = LibreJS;
+ let check;
+
+ beforeAll(async () => {
+ let documentUrl = tab.url;
+ let args = {tabId: tab.id, frameId: 0, documentUrl};
+ let resolve = url => new URL(url, documentUrl).href;
+ check = async url => await ExternalLicenses.check(Object.assign({url: resolve(url)}, args));
+ await browser.tabs.executeScript(tab.id, {
+ file: "/content/externalLicenseChecker.js"
+ });
+ });
+
+ it("should recognize free licenses", async () => {
+ let scriptInfo = await check("jquery.js");
+ console.debug(scriptInfo);
+ expect(scriptInfo.free).toBeTruthy();
+ });
+ it("should accept scripts if any of multiple licenses is free", async () => {
+ let scriptInfo = await check("app-trilicensed.js");
+ console.debug(scriptInfo);
+ expect(scriptInfo.free).toBeTruthy();
+ });
+ it("should block scripts declaring only proprietary license(s)", async () => {
+ let scriptInfo = await check("proprietary.js");
+ console.debug(scriptInfo);
+ expect(scriptInfo.free).toBeFalsy();
+ });
+ it("should block scripts not declaring any license", async () => {
+ let scriptInfo = await check("tracker.js");
+ console.debug(scriptInfo);
+ expect(scriptInfo).toBeNull();
+ });
+ });
+ afterAll(async () => {
+ // await browser.tabs.remove(tab.id);
+ browser.tabs.update((await browser.tabs.getCurrent()).id, {active: true});
+ });
+});