aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bg/ListManager.js37
-rwxr-xr-xbuild.sh1
-rw-r--r--common/Storage.js (renamed from bg/Storage.js)62
-rw-r--r--html/README (renamed from html/display_panel/content/README)0
-rw-r--r--html/background-panel.png (renamed from html/display_panel/content/background-panel.png)bin14814 -> 14814 bytes
-rw-r--r--html/common.css29
-rw-r--r--html/display_panel/content/display-panel.html5
-rw-r--r--html/display_panel/content/librejs-title-old.pngbin2673 -> 0 bytes
-rw-r--r--html/display_panel/content/main_panel.js27
-rw-r--r--html/display_panel/content/panel-styles.css34
-rw-r--r--html/librejs-title.png (renamed from html/display_panel/content/librejs-title.png)bin14123 -> 14123 bytes
-rw-r--r--html/preferences_panel/pref.js316
-rw-r--r--html/preferences_panel/preferences_panel.html89
-rw-r--r--html/preferences_panel/prefs.css91
-rw-r--r--main_background.js190
-rw-r--r--manifest.json5
16 files changed, 637 insertions, 249 deletions
diff --git a/bg/ListManager.js b/bg/ListManager.js
index 34d9531..e0a85e9 100644
--- a/bg/ListManager.js
+++ b/bg/ListManager.js
@@ -23,27 +23,28 @@
A class to manage whitelist/blacklist operations
*/
-let {ListStore} = require("./Storage");
+let {ListStore} = require("../common/Storage");
class ListManager {
constructor(whitelist, blacklist, builtInHashes) {
this.lists = {whitelist, blacklist};
this.builtInHashes = new Set(builtInHashes);
}
- async whitelist(key) {
- await this.lists.blacklist.remove(key);
- await this.lists.whitelist.store(key);
+
+ static async move(fromList, toList, ...keys) {
+ await Promise.all([fromList.remove(...keys), toList.store(...keys)]);
}
- async blacklist(key) {
- await this.lists.whitelist.remove(key);
- await this.lists.blacklist.store(key);
+
+ async whitelist(...keys) {
+ ListManager.move(this.lists.blacklist, this.lists.whitelist, ...keys);
}
- async forget(key) {
- for (let list of Object.values(this.lists)) {
- await list.remove(key);
- }
+ async blacklist(...keys) {
+ ListManager.move(this.lists.whitelist, this.lists.blacklist, ...keys);
}
- /* key is a string representing either a URL or an optional path
+ async forget(...keys) {
+ await Promise.all(Object.values(this.lists).map(l => l.remove(...keys)));
+ }
+ /* key is a string representing either a URL or an optional path
with a trailing (hash).
Returns "blacklisted", "whitelisted" or defValue
*/
@@ -53,16 +54,16 @@ class ListManager {
if (!match) {
let url = ListStore.urlItem(key);
let site = ListStore.siteItem(key);
- return (blacklist.contains(url) || blacklist.contains(site))
+ return (blacklist.contains(url) || blacklist.contains(site))
? "blacklisted"
- : whitelist.contains(url) || whitelist.contains(site)
- ? "whitelisted" : defValue;
+ : whitelist.contains(url) || whitelist.contains(site)
+ ? "whitelisted" : defValue;
}
-
+
let [hashItem, srcHash] = match; // (hash), hash
-
+
return blacklist.contains(hashItem) ? "blacklisted"
- : this.builtInHashes.has(srcHash) || whitelist.contains(hashItem)
+ : this.builtInHashes.has(srcHash) || whitelist.contains(hashItem)
? "whitelisted"
: defValue;
}
diff --git a/build.sh b/build.sh
index 49104b0..264eb00 100755
--- a/build.sh
+++ b/build.sh
@@ -12,6 +12,7 @@ mkdir ./build_temp
cp -r icons ./build_temp
cp -r ./html ./build_temp
cp -r ./content ./build_temp
+cp -r ./common ./build_temp
cp manifest.json ./build_temp
cp contact_finder.js ./build_temp
cp bundle.js ./build_temp
diff --git a/bg/Storage.js b/common/Storage.js
index ecdc9e4..a83ce8f 100644
--- a/bg/Storage.js
+++ b/common/Storage.js
@@ -23,11 +23,14 @@
A tiny wrapper around extensions storage API, supporting CSV serialization for
retro-compatibility
*/
+"use strict";
var Storage = {
ARRAY: {
- async load(key) {
- let array = (await browser.storage.local.get(key))[key];
+ async load(key, array = undefined) {
+ if (array === undefined) {
+ array = (await browser.storage.local.get(key))[key];
+ }
return array ? new Set(array) : new Set();
},
async save(key, list) {
@@ -40,7 +43,7 @@ var Storage = {
let csv = (await browser.storage.local.get(key))[key];
return csv ? new Set(csv.split(/\s*,\s*/)) : new Set();
},
-
+
async save(key, list) {
return await browser.storage.local.set({[key]: [...list].join(",")});
}
@@ -56,8 +59,13 @@ class ListStore {
this.key = key;
this.storage = storage;
this.items = new Set();
+ browser.storage.onChanged.addListener(changes => {
+ if (!this.saving && this.key in changes) {
+ this.load(changes[this.key].newValue);
+ }
+ });
}
-
+
static hashItem(hash) {
return hash.startsWith("(") ? hash : `(${hash})`;
}
@@ -73,32 +81,50 @@ class ListStore {
return `${url}/*`;
}
}
-
+
async save() {
- return await this.storage.save(this.key, this.items);
+ this._saving = true;
+ try {
+ return await this.storage.save(this.key, this.items);
+ } finally {
+ this._saving = false;
+ }
}
-
- async load() {
+
+ async load(values = undefined) {
try {
- this.items = await this.storage.load(this.key);
+ this.items = await this.storage.load(this.key, values);
} catch (e) {
console.error(e);
}
return this.items;
}
-
- async store(item) {
+
+ async store(...items) {
let size = this.items.size;
- return (size !== this.items.add(item).size) && await this.save();
+ let changed = false;
+ for (let item of items) {
+ if (size !== this.items.add(item).size) {
+ changed = true;
+ }
+ }
+ return changed && await this.save();
}
-
- async remove(item) {
- return this.items.delete(item) && await this.save();
+
+ async remove(...items) {
+ let changed = false;
+ for (let item of items) {
+ if (this.items.delete(item)) {
+ changed = true;
+ }
+ }
+ return changed && await this.save();
}
-
+
contains(item) {
return this.items.has(item);
}
}
-
-module.exports = { ListStore, Storage };
+if (typeof module === "object") {
+ module.exports = { ListStore, Storage };
+}
diff --git a/html/display_panel/content/README b/html/README
index a56ea46..a56ea46 100644
--- a/html/display_panel/content/README
+++ b/html/README
diff --git a/html/display_panel/content/background-panel.png b/html/background-panel.png
index 022ffb3..022ffb3 100644
--- a/html/display_panel/content/background-panel.png
+++ b/html/background-panel.png
Binary files differ
diff --git a/html/common.css b/html/common.css
new file mode 100644
index 0000000..cf2c5d1
--- /dev/null
+++ b/html/common.css
@@ -0,0 +1,29 @@
+html {
+ padding:0px;
+ margin:0px;
+ color:#000 !important;
+ background:url('background-panel.png') !important;
+}
+body {
+ padding:0;
+ margin:10px 30px 10px 20px;
+ color:#000;
+}
+
+div.libre {
+ position: relative;
+}
+
+.libre {
+ width:230px;
+ height:104px;
+ display:block;
+}
+h1.libre {
+ font-size:1.5em;
+ font-weight:normal;
+ padding:0;
+ font-weight:bold;
+ background:url('librejs-title.png') no-repeat top left;
+ text-indent:-1000px;
+}
diff --git a/html/display_panel/content/display-panel.html b/html/display_panel/content/display-panel.html
index 2ed0c9c..df153b3 100644
--- a/html/display_panel/content/display-panel.html
+++ b/html/display_panel/content/display-panel.html
@@ -36,8 +36,8 @@
<div>
<a class="libre"
id="ljs-settings"
- href="javascript:void"
- title="LibreJS Whitelist Settings">
+ href= href="https://www.gnu.org/software/librejs/"
+ title="LibreJS Page Settings">
<h1 class="libre">LibreJS</h1>
</a>
</div>
@@ -52,6 +52,7 @@
</div>
<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>
</div>
</div>
<div id="info">
diff --git a/html/display_panel/content/librejs-title-old.png b/html/display_panel/content/librejs-title-old.png
deleted file mode 100644
index 8a11527..0000000
--- a/html/display_panel/content/librejs-title-old.png
+++ /dev/null
Binary files differ
diff --git a/html/display_panel/content/main_panel.js b/html/display_panel/content/main_panel.js
index 930f7f2..c55b167 100644
--- a/html/display_panel/content/main_panel.js
+++ b/html/display_panel/content/main_panel.js
@@ -64,6 +64,11 @@ document.querySelector("#complain").onclick = e => {
close();
}
+document.querySelector("#open-options").onclick = e => {
+ browser.runtime.openOptionsPage();
+ close();
+}
+
document.querySelector("#reload").onclick = async e => {
let {tabId} = currentReport;
if (tabId) {
@@ -72,9 +77,9 @@ document.querySelector("#reload").onclick = async e => {
}
};
-/*
+/*
* Takes in the [[file_id, reason],...] array and the group name for one group
-* of scripts found in this tab, rendering it as a list with management buttons.
+* of scripts found in this tab, rendering it as a list with management buttons.
* Groups are "unknown", "blacklisted", "whitelisted", "accepted", and "blocked".
*/
function createList(data, group){
@@ -98,7 +103,7 @@ function createList(data, group){
let [scriptId, reason] = entry;
let li = liTemplate.cloneNode(true);
let a = li.querySelector("a");
- a.href = scriptId.split("(")[0];
+ a.href = scriptId.split("(")[0];
a.textContent = scriptId;
li.querySelector(".reason").textContent = reason;
let bySite = !!reason.match(/https?:\/\/[^/]+\/\*/);
@@ -116,7 +121,7 @@ function createList(data, group){
/**
* Updates scripts lists and buttons to act on them.
* If return_HTML is true, it returns the HTML of the popup window without updating it.
-* example report argument:
+* example report argument:
* {
* "accepted": [["FILENAME 1","REASON 1"],["FILENAME 2","REASON 2"]],
* "blocked": [["FILENAME 1","REASON 1"],["FILENAME 2","REASON 2"]],
@@ -131,29 +136,29 @@ function refreshUI(report) {
currentReport = report;
document.querySelector("#site").className = report.siteStatus || "";
- document.querySelector("#site h2").textContent =
+ document.querySelector("#site h2").textContent =
`This site ${report.site}`;
-
+
for (let toBeErased of document.querySelectorAll("#info h2:not(.site) > *, #info ul > *")) {
toBeErased.remove();
}
-
+
let scriptsCount = 0;
for (let group of ["unknown", "accepted", "whitelisted", "blocked", "blacklisted"]) {
if (group in report) createList(report, group);
scriptsCount += report[group].length;
}
-
+
for (let b of document.querySelectorAll(`.forget, .whitelist, .blacklist`)) {
b.disabled = false;
}
for (let b of document.querySelectorAll(
- `.unknown .forget, .accepted .forget, .blocked .forget,
+ `.unknown .forget, .accepted .forget, .blocked .forget,
.whitelisted .whitelist, .blacklisted .blacklist`
)) {
b.disabled = true;
- }
-
+ }
+
let noscript = scriptsCount === 0;
document.body.classList.toggle("empty", noscript);
}
diff --git a/html/display_panel/content/panel-styles.css b/html/display_panel/content/panel-styles.css
index 745c67f..cbf5cf5 100644
--- a/html/display_panel/content/panel-styles.css
+++ b/html/display_panel/content/panel-styles.css
@@ -17,38 +17,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
-html {
- padding:0px;
- margin:0px;
- color:#000 !important;
- background:url('background-panel.png') !important;
-}
+@import url("/html/common.css");
+
body {
- padding:0;
- margin:10px 30px 10px 20px;
- color:#000;
-width:500px;
+ width:500px;
}
-
#header{
display:block;
width:500px;
}
-.libre {
- width:230px;
- height:104px;
- display:block;
-}
-h1.libre {
- font-size:1.5em;
- font-weight:normal;
- padding:0;
- font-weight:bold;
- background:url('librejs-title.png') no-repeat top left;
- text-indent:-1000px;
- overflow:hidden;
-}
h2 {
font-size:1.1em;
font-weight:bold;
@@ -171,3 +149,9 @@ span.blocked {
width: 100%;
text-align: center;
}
+
+
+
+#complain {
+ display: none; /* TODO: Complaint to owner UI */
+}
diff --git a/html/display_panel/content/librejs-title.png b/html/librejs-title.png
index c1a911c..c1a911c 100644
--- a/html/display_panel/content/librejs-title.png
+++ b/html/librejs-title.png
Binary files differ
diff --git a/html/preferences_panel/pref.js b/html/preferences_panel/pref.js
index 4642d32..9cecbb6 100644
--- a/html/preferences_panel/pref.js
+++ b/html/preferences_panel/pref.js
@@ -1,7 +1,8 @@
/**
* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
-* *
+*
* Copyright (C) 2017 Nathan Nichols
+* Copyright (C) 2018 Giorgio maone
*
* This file is part of GNU LibreJS.
*
@@ -19,59 +20,288 @@
* along with GNU LibreJS. If not, see <http://www.gnu.org/licenses/>.
*/
-var store;
+(() => {
+ "use strict";
-function storage_got(items){
- var inputs = document.getElementsByTagName("input");
+ const LIST_NAMES = ["white", "black"];
- if(items["pref_whitelist"] == "undefined"){
- items["pref_whitelist"] = "";
- }
+ var Model = {
+ lists: {},
+ prefs: null,
- if(items["pref_subject"] == "undefined" || items["pref_subject"] == ""){
- items["pref_subject"] = "Issues with Javascript on your website";
- }
+ malformedUrl(url) {
+ let error = null;
+ try {
+ let objUrl = new URL(url);
+ url = objUrl.href;
+ if (!objUrl.protocol.startsWith("http")) {
+ error = "Please enter http:// or https:// URLs only";
+ } else if (!/^[^*]+\*?$/.test(url)) {
+ error = "Only one single trailing path wildcard (/*) allowed";
+ }
+ } catch (e) {
+ error = "Invalid URL";
+ if (url && !url.includes("://")) error += ": missing protocol, either http:// or https://";
+ else if (url.endsWith("://")) error += ": missing domain name";
+ }
+ return error;
+ },
- if(items["pref_body"] == "undefined" || items["pref_body"] == ""){
- items["pref_body"] = "Please consider using a free license for the Javascript on your website. [Message generated by LibreJS. See https://www.gnu.org/software/librejs/ for more information]";
- }
+ async save(prefs = this.prefs) {
+ if (prefs !== this.prefs) {
+ this.prefs = Object.assign(this.prefs, prefs);
+ }
+ this.saving = true;
+ try {
+ return await browser.storage.local.set(prefs);
+ } finally {
+ this.saving = false;
+ }
+ },
- for(var i = 0; i < inputs.length; i++){
- if(inputs[i].id.indexOf("pref_") != -1){
- if(inputs[i].type == "checkbox" && items[inputs[i].id]){
- inputs[i].checked = true;
+ async addToList(list, ...items) {
+ let other = list === Model.lists.black ? Model.lists.white : Model.lists.black;
+ this.saving = true;
+ try {
+ await Promise.all([
+ other.remove(...items),
+ list.store(...items)
+ ]);
+ } finally {
+ this.saving = false;
}
- if(inputs[i].type == "text" && items[inputs[i].id] != undefined){
- inputs[i].value = items[inputs[i].id];
- }
}
- }
+ };
+ Model.loading = (async () => {
+ let prefsNames = [
+ "whitelist",
+ "blacklist",
+ "subject",
+ "body"
+ ];
+ Model.prefs = await browser.storage.local.get(prefsNames.map(name => `pref_${name}`));
+
+ for (let listName of LIST_NAMES) {
+ let prefName = `pref_${listName}list`;
+ await (Model.lists[listName] = new ListStore(prefName, Storage.CSV))
+ .load(Model.prefs[prefName]);
+ }
+ })();
+
+ var Controller = {
+ init() {
+ let widgetsRoot = this.root = document.getElementById("widgets");
+ for (let widget of widgetsRoot.querySelectorAll('[id^="pref_"]')) {
+ if (widget.id in Model.lists) {
+ populateListUI(widget);
+ } else if (widget.id in Model.prefs) {
+ widget.value = Model.prefs[widget.id];
+ }
+ }
+
+ this.populateListUI();
+ this.syncAll();
+
+ for (let ev in Listeners) {
+ widgetsRoot.addEventListener(ev, Listeners[ev]);
+ }
+ document.getElementById("site").onfocus = e => {
+ if (!e.target.value.trim()) {
+ e.target.value = "https://";
+ }
+ };
+
+ browser.storage.onChanged.addListener(changes => {
+ if (!Model.saving &&
+ ("pref_whitelist" in changes || "pref_blacklist" in changes)) {
+ setTimeout(() => {
+ this.populateListUI();
+ this.syncAll();
+ }, 10);
+ }
+ });
+ },
+
+ async addSite(list) {
+ let url = document.getElementById("site").value.trim();
+
+ if (url && !Model.malformedUrl(url)) {
+ await this.addToList(list, url);
+ }
+ },
+ async addToList(list, ...items) {
+ await Model.addToList(list, ...items);
+ this.populateListUI();
+ this.syncAll();
+ },
+ async swapSelection(list) {
+ let origin = list === Model.lists.black ? "white" : "black";
+ await this.addToList(list, ...Array.map(
+ document.querySelectorAll(`select#${origin} option:checked`),
+ option => option.value)
+ );
+ },
+
+ syncAll() {
+ this.syncListsUI();
+ this.syncSiteUI();
+ },
+
+ syncSiteUI() {
+ let widget = document.getElementById("site");
+ let list2button = listName => document.getElementById(`cmd-${listName}list-site`);
+
+ for (let bi of LIST_NAMES.map(list2button)) {
+ bi.disabled = true;
+ }
+
+ let url = widget.value.trim();
+ let malformedUrl = url && Model.malformedUrl(url);
+ widget.classList.toggle("error", !!malformedUrl);
+ document.getElementById("site-error").textContent = malformedUrl || "";
+ if (!url) return;
+ if (url !== widget.value) {
+ widget.value = url;
+ }
+
+ for (let listName of LIST_NAMES) {
+ let list = Model.lists[listName];
+ if (!list.contains(url)) {
+ list2button(listName).disabled = false;
+ }
+ }
+ },
+
+ syncListsUI() {
+ let total = 0;
+ for (let id of ["black", "white"]) {
+ let selected = document.querySelectorAll(`select#${id} option:checked`).length;
+ let other = id === "black" ? "white" : "black";
+ document.getElementById(`cmd-${other}list`).disabled = selected === 0;
+ total += selected;
+ }
+ document.getElementById("cmd-delete").disabled = total === 0;
+ },
+ async deleteSelection() {
+ for (let id of ["black", "white"]) {
+ let selection = document.querySelectorAll(`select#${id} option:checked`);
+ await Model.lists[id].remove(...Array.map(selection, option => option.value));
+ }
+ this.populateListUI();
+ this.syncAll();
+ },
-}
-browser.storage.local.get(storage_got);
-
-document.getElementById("save_changes").addEventListener("click", function(){
- var inputs = document.getElementsByTagName("input");
- // TODO: validate/sanitize the user inputs
- var data = {};
- for(var i = 0; i < inputs.length; i++){
- if(inputs[i].id.indexOf("pref_") != -1){
- var input_val = "";
- if(inputs[i].type == "checkbox"){
- input_val = inputs[i].checked;
- } else{
- if(inputs[i.value] != "undefined"){
- input_val = inputs[i].value;
- } else{
- input_val = "";
+ populateListUI(widget) {
+ if (!widget) {
+ for(let id of ["white", "black"]) {
+ this.populateListUI(document.getElementById(id));
}
+ return;
+ }
+ widget.innerHTML = "";
+ let items = [...Model.lists[widget.id].items].sort();
+ let options = new DocumentFragment();
+ for (let item of items) {
+ let option = document.createElement("option");
+ option.value = option.textContent = option.title = item;
+ options.appendChild(option);
+ }
+ widget.appendChild(options);
+ }
+ };
+
+ var KeyEvents = {
+ Delete(e) {
+ if (e.target.matches("#lists select")) {
+ Controller.deleteSelection();
+ }
+ },
+ Enter(e) {
+ if (e.target.id === "site") {
+ e.target.parentElement.querySelector("button[default]").click();
+ }
+ },
+ KeyA(e) {
+ if (e.target.matches("select") && e.ctrlKey) {
+ for (let o of e.target.options) {
+ o.selected = true;
+ }
+ Controller.syncListsUI();
}
- var input_id = inputs[i].id;
- data[input_id] = input_val;
}
}
- console.log(data);
- browser.storage.local.set(data);
-});
+ var Listeners = {
+ async change(e) {
+ let {target} = e;
+ let {id} = target;
+
+ if (id in Model.lists) {
+ Controller.syncListsUI();
+ let selection = target.querySelectorAll("option:checked");
+ if (selection.length === 1) {
+ document.getElementById("site").value = selection[0].value;
+ }
+ return;
+ }
+ },
+
+ click(e) {
+ let {target} = e;
+
+ if (!/^cmd-(white|black|delete)(list-site)?/.test(target.id)) return;
+ e.preventDefault();
+ let cmd = RegExp.$1;
+ if (cmd === "delete") {
+ Controller.deleteSelection();
+ return;
+ }
+ let list = Model.lists[cmd];
+ if (list) {
+ Controller[RegExp.$2 ? "addSite" : "swapSelection"](list);
+ return;
+ }
+ },
+
+ keypress(e) {
+ let {code} = e;
+ if (code && typeof KeyEvents[code] === "function") {
+ if (KeyEvents[code](e) === false) {
+ e.preventDefault();
+ }
+ return;
+ }
+ },
+
+ async input(e) {
+ let {target} = e;
+ let {id} = target;
+ if (!id) return;
+
+ if (id === "site") {
+ Controller.syncSiteUI();
+ let url = target.value;
+ if (url) {
+ let o = document.querySelector(`#lists select option[value="${url}"]`);
+ if (o) {
+ o.scrollIntoView();
+ o.selected = true;
+ }
+ }
+ return;
+ }
+
+ if (id.startsWith("pref_")) {
+ await Model.save({[id]: target.value});
+ return;
+ }
+ }
+ };
+
+ window.addEventListener("DOMContentLoaded", async e => {
+ await Model.loading;
+ Controller.init();
+ });
+
+})();
diff --git a/html/preferences_panel/preferences_panel.html b/html/preferences_panel/preferences_panel.html
index 2d01f94..effb724 100644
--- a/html/preferences_panel/preferences_panel.html
+++ b/html/preferences_panel/preferences_panel.html
@@ -1,10 +1,13 @@
+<!doctype html>
<html>
- <head>
+<head>
+<meta charset="utf-8"/>
<!-- /**
* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
* *
* Copyright (C) 2011, 2012, 2014 Loic J. Duros
* Copyright (C) 2014, 2015 Nik Nyby
+ * Copyright (C) 2018 Giorgio Maone
*
* This file is part of GNU LibreJS.
*
@@ -25,43 +28,59 @@
<title>
LibreJS preferences
</title>
+ <link rel="stylesheet" type="text/css" href="./prefs.css"/>
+ <script type="text/javascript" src="/common/Storage.js"></script>
+ <script type="text/javascript" src="pref.js"></script>
</head>
-
- <body>
- <h3>
- LibreJS Preferences
- </h3>
- <table>
- <tr>
- <td><p>Allow all scripts from pages with this text <br> in their URL. (Comma seperated, wildcard is *)</p></td>
- <td><input id="pref_whitelist" type="text"></input></td>
- </tr>
- <!--
+ <body>
+ <div class="libre">
+ <a class="libre"
+ id="ljs-settings"
+ href="https://www.gnu.org/software/librejs/"
+ title="LibreJS Settings">
+ <h1 class="libre">LibreJS</h1>
+ </a>
+ <h3>Settings</h3>
+ </div>
+ <div id="widgets">
+ <fieldset id="section-lists"><legend>Allow or block scripts matching the following URLs ("*" matches any path)</legend>
+ <label>Type a new whitelist / blacklist entry:</label>
+ <div id="new-site">
+ <input type="text" id="site" value="" placeholder="https://www.gnu.org/*">
+ <button id="cmd-whitelist-site" class="white" title="Whitelist this site" default>Whitelist</button>
+ <button id="cmd-blacklist-site" class="red" title="Blacklist this site">Blacklist</button>
+ </div>
+ <div id="site-error" class="error-msg"></div>
+ <div id="lists">
+ <div class="white list-container">
+ <label>Whitelist (always allow)</label>
+ <select id="white" multiple size="10"></select>
+ </div>
+ <div id="commands">
+ <button id="cmd-delete" title="Delete">x</button>
+ <button id="cmd-blacklist" title="Move to blacklist">&raquo;</button>
+ <button id="cmd-whitelist" title="Move to whitelist">&laquo;</button>
+ </div>
+ <div class="black list-container">
+ <label>Blacklist (always block)</label>
+ <select id="black" multiple size="10"></select>
+ </div>
+ </div>
+ </fieldset>
- <tr>
- <td><p>Display complaint tab on sites where nonfree nontrivial Javascript detected</p></td>
- <td><input id="pref_complaint_tab" type="checkbox"></input></td>
- </tr>
- <tr>
- <td><p>Display notifications of the JavaScript code being analyzed by LibreJS</p></td>
- <td><input id="pref_notify_analyze" type="checkbox"></input></td>
- </tr>
+ <fieldset id="section-complaint"><legend>Complaint email defaults</legend>
+ <label for="pref_subject">Subject</label>
+ <input id="pref_subject" type="text"
+ value="Issues with Javascript on your website"
+ />
+ <label for="pref_body">Body</label>
+ <textarea id="pref_body" rows="5"
+>Please consider using a free license for the Javascript on your website.
- -->
- <tr>
- <td><p>Default complaint email subject</p></td>
- <td><input id="pref_subject" type="text"></input></td>
- </tr>
- <tr>
- <td><p>Default complaint email body</p></td>
- <td><input id="pref_body" type="text"></input></td>
- </tr>
- <tr>
- <td><input type="button" value="Save changes" id="save_changes"></input></td>
- <td></td>
- </tr>
- </table>
- <script type="text/javascript" src="pref.js"></script>
+[Message generated by LibreJS. See https://www.gnu.org/software/librejs/ for more information]
+</textarea>
+ </fieldset>
+ </div>
</body>
</html>
diff --git a/html/preferences_panel/prefs.css b/html/preferences_panel/prefs.css
new file mode 100644
index 0000000..b52d6c5
--- /dev/null
+++ b/html/preferences_panel/prefs.css
@@ -0,0 +1,91 @@
+@import url("chrome://browser/content/extension.css");
+@import url("/html/common.css");
+h3 {
+ position: absolute;
+ bottom: 0px;
+ left: 240px;
+ font-size: 18px;
+}
+textarea {
+ width: 100%;
+}
+fieldset {
+ border: none;
+ padding: 0;
+ margin-top: 1em;
+ border-top: 1px solid #ccc;
+}
+legend {
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+label, legend {
+ display: block;
+ font-size: 1.2em;
+}
+
+#lists {
+ display: flex;
+ flex-direction: row;
+}
+.list-container {
+ flex: 3;
+ flex-direction: row;
+}
+.list-container select {
+width: 100%
+}
+
+.black {
+ color: #600;
+}
+.white {
+ color: #060;
+}
+
+#commands {
+ display: flex;
+ justify-content: center;
+ flex: none;
+ flex-flow: column nowrap;
+}
+
+#commands button {
+ font-weight: bold;
+}
+input[type="text"] {
+ width: 100%;
+}
+
+#lists label {
+ font-weight: bold;
+}
+#lists select {
+ color: black;
+}
+#black {
+ background-color: #fcc;
+}
+#white {
+ background-color: #cfc;
+}
+
+#new-site {
+ display: flex;
+ flex 2;
+}
+.error-msg {
+ color: red;
+}
+.error-msg::after {
+ content: "\00A0";
+}
+.error {
+ background: #ffe;
+ color: #800;
+}
+
+#section-complaint {
+ display: none; /* TODO: Complaint to owner UI */
+}
diff --git a/main_background.js b/main_background.js
index d12ff54..9e08976 100644
--- a/main_background.js
+++ b/main_background.js
@@ -26,7 +26,7 @@ var jssha = require('jssha');
var walk = require("acorn/dist/walk");
var legacy_license_lib = require("./legacy_license_check.js");
var {ResponseProcessor} = require("./bg/ResponseProcessor");
-var {Storage, ListStore} = require("./bg/Storage");
+var {Storage, ListStore} = require("./common/Storage");
var {ListManager} = require("./bg/ListManager");
var {ExternalLicenses} = require("./bg/ExternalLicenses");
@@ -37,8 +37,8 @@ console.log("main_background.js");
* Also, it controls whether or not this part of the code logs to the console.
*
*/
-var DEBUG = false; // debug the JS evaluation
-var PRINT_DEBUG = false; // Everything else
+var DEBUG = false; // debug the JS evaluation
+var PRINT_DEBUG = false; // Everything else
var time = Date.now();
function dbg_print(a,b){
@@ -134,16 +134,16 @@ function options_listener(changes, area){
// TODO: See if this can be minimized
function flushed(){
dbg_print("cache flushed");
- }
+ }
//var flushingCache = browser.webRequest.handlerBehaviorChanged(flushed);
-
+
dbg_print("Items updated in area" + area +": ");
var changedItems = Object.keys(changes);
var changed_items = "";
for (var i = 0; i < changedItems.length; i++){
- var item = changedItems[i];
+ var item = changedItems[i];
changed_items += item + ",";
}
dbg_print(changed_items);
@@ -167,7 +167,7 @@ async function createReport(initializer = null) {
template.url = (await browser.tabs.get(initializer.tabId)).url;
}
}
-
+
template.site = ListStore.siteItem(template.url);
template.siteStatus = listManager.getStatus(template.site);
return template;
@@ -224,7 +224,7 @@ function debug_print_local(){
*
* NOTE: This WILL break if you provide inconsistent URLs to it.
* Make sure it will use the right URL when refering to a certain script.
-*
+*
*/
async function updateReport(tabId, oldReport, updateUI = false){
let {url} = oldReport;
@@ -253,8 +253,8 @@ async function updateReport(tabId, oldReport, updateUI = false){
*
* Sends a message to the content script that adds a popup entry for a tab.
*
-* The action argument is an object with two properties: one named either
-* "accepted","blocked", "whitelisted", "blacklisted" or "unknown", whose value
+* The action argument is an object with two properties: one named either
+* "accepted","blocked", "whitelisted", "blacklisted" or "unknown", whose value
* is the array [scriptName, reason], and another named "url". Example:
* action = {
* "accepted": ["jquery.js (someHash)","Whitelisted by user"],
@@ -269,9 +269,9 @@ async function updateReport(tabId, oldReport, updateUI = false){
*/
async function addReportEntry(tabId, scriptHashOrUrl, action) {
let report = activityReports[tabId];
- if (!report) report = activityReports[tabId] =
+ if (!report) report = activityReports[tabId] =
await createReport({tabId});
-
+
let type, actionValue;
for (type of ["accepted", "blocked", "whitelisted", "blacklisted"]) {
if (type in action) {
@@ -307,14 +307,14 @@ async function addReportEntry(tabId, scriptHashOrUrl, action) {
console.error("action %o, type %s, entryType %s", action, type, entryType, e);
entryType = "unknown";
}
-
+
if (activeMessagePorts[tabId]) {
try {
activeMessagePorts[tabId].postMessage({show_info: report});
} catch(e) {
}
}
-
+
browser.sessions.setTabValue(tabId, report.url, report);
updateBadge(tabId, report);
return entryType;
@@ -347,21 +347,21 @@ function connected(p) {
p.postMessage(items);
}
browser.storage.local.get(cb);
- return;
+ return;
}
p.onMessage.addListener(async function(m) {
var update = false;
var contact_finder = false;
-
+
for (let action of ["whitelist", "blacklist", "forget"]) {
if (m[action]) {
let [key] = m[action];
- if (m.site) key = ListStore.siteItem(key);
+ if (m.site) key = ListStore.siteItem(key);
await listManager[action](key);
update = true;
}
}
-
+
if(m.report_tab){
openReportInTab(m.report_tab);
}
@@ -380,9 +380,9 @@ function connected(p) {
console.log("Delete local storage");
debug_delete_local();
}
-
+
let tabs = await browser.tabs.query({active: true, currentWindow: true});
-
+
if(contact_finder){
let tab = tabs.pop();
dbg_print(`[TABID:${tab.id}] Injecting contact finder`);
@@ -397,13 +397,13 @@ function connected(p) {
for(let tab of tabs) {
if(activityReports[tab.id]){
// If we have some data stored here for this tabID, send it
- dbg_print(`[TABID: ${tab.id}] Sending stored data associated with browser action'`);
+ dbg_print(`[TABID: ${tab.id}] Sending stored data associated with browser action'`);
p.postMessage({"show_info": activityReports[tab.id]});
} else{
// create a new entry
let report = activityReports[tab.id] = await createReport({"url": tab.url, tabId: tab.id});
- p.postMessage({show_info: report});
- dbg_print(`[TABID: ${tab.id}] No data found, creating a new entry for this window.`);
+ p.postMessage({show_info: report});
+ dbg_print(`[TABID: ${tab.id}] No data found, creating a new entry for this window.`);
}
}
}
@@ -430,7 +430,7 @@ function delete_removed_tab_info(tab_id, remove_info){
/**
* Called when the tab gets updated / activated
*
-* Here we check if new tab's url matches activityReports[tabId].url, and if
+* Here we check if new tab's url matches activityReports[tabId].url, and if
* it doesn't we use the session cached value (if any).
*
*/
@@ -441,7 +441,7 @@ async function onTabUpdated(tabId, changedInfo, tab) {
if (!(report && report.url === url)) {
let cache = await browser.sessions.getTabValue(tabId, url);
// on session restore tabIds may change
- if (cache && cache.tabId !== tabId) cache.tabId = tabId;
+ if (cache && cache.tabId !== tabId) cache.tabId = tabId;
updateBadge(tabId, activityReports[tabId] = cache);
}
}
@@ -457,9 +457,9 @@ var fname_data = require("./fname_data.json").fname_data;
//************************this part can be tested in the HTML file index.html's script test.js****************************
function full_evaluate(script){
- var res = true;
+ var res = true;
if(script === undefined || script == ""){
- return [true,"Harmless null script"];
+ return [true,"Harmless null script"];
}
var ast = acorn.parse_dammit(script).body[0];
@@ -470,10 +470,10 @@ function full_evaluate(script){
var loopkeys = {"for":true,"if":true,"while":true,"switch":true};
var operators = {"||":true,"&&":true,"=":true,"==":true,"++":true,"--":true,"+=":true,"-=":true,"*":true};
try{
- var tokens = acorn_base.tokenizer(script);
+ var tokens = acorn_base.tokenizer(script);
}catch(e){
console.warn("Tokenizer could not be initiated (probably invalid code)");
- return [false,"Tokenizer could not be initiated (probably invalid code)"];
+ return [false,"Tokenizer could not be initiated (probably invalid code)"];
}
try{
var toke = tokens.getToken();
@@ -512,27 +512,27 @@ function full_evaluate(script){
return script.charAt(end+i) == "[";
}
var error_count = 0;
- while(toke !== undefined && toke.type != acorn_base.tokTypes.eof){
+ while(toke !== undefined && toke.type != acorn_base.tokTypes.eof){
if(toke.type.keyword !== undefined){
//dbg_print("Keyword:");
//dbg_print(toke);
-
+
// This type of loop detection ignores functional loop alternatives and ternary operators
if(toke.type.keyword == "function"){
dbg_print("%c NONTRIVIAL: Function declaration.","color:red");
- if(DEBUG == false){
+ if(DEBUG == false){
return [false,"NONTRIVIAL: Function declaration."];
- }
+ }
}
if(loopkeys[toke.type.keyword] !== undefined){
amtloops++;
if(amtloops > 3){
dbg_print("%c NONTRIVIAL: Too many loops/conditionals.","color:red");
- if(DEBUG == false){
+ if(DEBUG == false){
return [false,"NONTRIVIAL: Too many loops/conditionals."];
- }
+ }
}
}
}else if(toke.value !== undefined && operators[toke.value] !== undefined){
@@ -540,39 +540,39 @@ function full_evaluate(script){
// kind of primitive (I.e. a number)
}else if(toke.value !== undefined){
var status = fname_data[toke.value];
- if(status === true){ // is the identifier banned?
+ if(status === true){ // is the identifier banned?
dbg_print("%c NONTRIVIAL: nontrivial token: '"+toke.value+"'","color:red");
- if(DEBUG == false){
+ if(DEBUG == false){
return [false,"NONTRIVIAL: nontrivial token: '"+toke.value+"'"];
- }
+ }
}else if(status === false){// is the identifier not banned?
// Is there bracket suffix notation?
if(is_bsn(toke.end)){
dbg_print("%c NONTRIVIAL: Bracket suffix notation on variable '"+toke.value+"'","color:red");
- if(DEBUG == false){
+ if(DEBUG == false){
return [false,"%c NONTRIVIAL: Bracket suffix notation on variable '"+toke.value+"'"];
- }
+ }
}
}else if(status === undefined){// is the identifier user defined?
// Are arguments being passed to a user defined variable?
if(being_called(toke.end)){
dbg_print("%c NONTRIVIAL: User defined variable '"+toke.value+"' called as function","color:red");
- if(DEBUG == false){
+ if(DEBUG == false){
return [false,"NONTRIVIAL: User defined variable '"+toke.value+"' called as function"];
- }
+ }
}
// Is there bracket suffix notation?
if(is_bsn(toke.end)){
dbg_print("%c NONTRIVIAL: Bracket suffix notation on variable '"+toke.value+"'","color:red");
- if(DEBUG == false){
+ if(DEBUG == false){
return [false,"NONTRIVIAL: Bracket suffix notation on variable '"+toke.value+"'"];
- }
+ }
}
}else{
dbg_print("trivial token:"+toke.value);
}
}
- // If not a keyword or an identifier it's some kind of operator, field parenthesis, brackets
+ // If not a keyword or an identifier it's some kind of operator, field parenthesis, brackets
try{
toke = tokens.getToken();
}catch(e){
@@ -603,7 +603,7 @@ function evaluate(script,name){
var scope_chars = "\{\}\]\[\(\)\,";
var trailing_chars = "\s*"+"\(\.\[";
return new RegExp("(?:[^\\w\\d]|^|(?:"+arith_operators+"))"+object+'(?:\\s*?(?:[\\;\\,\\.\\(\\[])\\s*?)',"g");
- }
+ }
reserved_object_regex("window");
var all_strings = new RegExp('".*?"'+"|'.*?'","gm");
var ml_comment = /\/\*([\s\S]+?)\*\//g;
@@ -622,7 +622,7 @@ function evaluate(script,name){
var res = reserved_object_regex(reserved_objects[i]).exec(temp);
if(res != null){
dbg_print("%c fail","color:red;");
- flag = false;
+ flag = false;
reason = "Script uses a reserved object (" + reserved_objects[i] + ")";
}
}
@@ -644,7 +644,7 @@ function license_valid(matches){
return [false, "malformed or unrecognized license tag"];
}
if(matches[1] != "@license"){
- return [false, "malformed or unrecognized license tag"];
+ return [false, "malformed or unrecognized license tag"];
}
if(licenses[matches[3]] === undefined){
return [false, "malformed or unrecognized license tag"];
@@ -659,14 +659,14 @@ function license_valid(matches){
* Evaluates the content of a script (license, if it is non-trivial)
*
* Returns
-* [
+* [
* true (accepted) or false (denied),
* edited content,
-* reason text
+* reason text
* ]
*/
function license_read(script_src, name, external = false){
-
+
var reason_text = "";
var edited_src = "";
@@ -701,7 +701,7 @@ function license_read(script_src, name, external = false){
edited_src += "\n/*\nLIBREJS BLOCKED:"+nontrivial_status[1]+"\n*/\n";
}
reason_text += "\n" + nontrivial_status[1];
-
+
if(parts_denied == true && parts_accepted == true){
reason_text = "Script was determined partly non-trivial after editing. (check source for details)\n"+reason_text;
}
@@ -709,7 +709,7 @@ function license_read(script_src, name, external = false){
return [false,edited_src,reason_text];
}
else return [true,edited_src,reason_text];
-
+
}
// sponge
dbg_print("undedited_src:");
@@ -742,7 +742,7 @@ function license_read(script_src, name, external = false){
var license_res = license_valid(matches);
if(license_res[0] == true){
edited_src = edited_src + unedited_src.substr(0,endtag_end_index);
- reason_text += "\n" + license_res[1];
+ reason_text += "\n" + license_res[1];
} else{
edited_src = edited_src + "\n/*\n"+license_res[1]+"\n*/\n";
reason_text += "\n" + license_res[1];
@@ -756,34 +756,34 @@ function license_read(script_src, name, external = false){
// TODO: Test if this script is being loaded from another domain compared to activityReports[tabid]["url"]
/**
-* Asynchronous function, returns the final edited script as a string,
+* Asynchronous function, returns the final edited script as a string,
* or an array containing it and the index, if the latter !== -1
*/
async function get_script(response, url, tabId = -1, whitelisted = false, index = -1) {
function result(scriptSource) {
return index === -1 ? scriptSource : [scriptSource, index];
}
-
+
let scriptName = url.split("/").pop();
if (whitelisted) {
if (tabId !== -1) {
- let site = ListStore.siteItem(url);
+ let site = ListStore.siteItem(url);
// Accept without reading script, it was explicitly whitelisted
let reason = whitelist.contains(site)
- ? `All ${site} whitelisted by user`
+ ? `All ${site} whitelisted by user`
: "Address whitelisted by user";
addReportEntry(tabId, url, {"whitelisted": [url, reason], url});
}
return result(`/* LibreJS: script whitelisted by user preference. */\n${response}`);
}
-
+
let [verdict, editedSource, reason] = license_read(response, scriptName, index === -2);
-
+
if (tabId < 0) {
return result(verdict ? response : editedSource);
}
-
+
let sourceHash = hash(response);
let domain = get_domain(url);
let report = activityReports[tabId] || (activityReports[tabId] = await createReport({tabId}));
@@ -792,17 +792,17 @@ async function get_script(response, url, tabId = -1, whitelisted = false, index
let scriptSource = verdict ? response : editedSource;
switch(category) {
case "blacklisted":
- case "whitelisted":
+ case "whitelisted":
return result(`/* LibreJS: script ${category} by user. */\n${scriptSource}`);
default:
- return result(`/* LibreJS: script ${category}. */\n${scriptSource}`);
+ return result(`/* LibreJS: script ${category}. */\n${scriptSource}`);
}
}
function updateBadge(tabId, report = null, forceRed = false) {
let blockedCount = report ? report.blocked.length + report.blacklisted.length : 0;
- let [text, color] = blockedCount > 0 || forceRed
+ let [text, color] = blockedCount > 0 || forceRed
? [blockedCount && blockedCount.toString() || "!" , "red"] : ["✓", "green"]
browser.browserAction.setBadgeText({text, tabId});
browser.browserAction.setBadgeBackgroundColor({color, tabId});
@@ -857,21 +857,21 @@ var ResponseHandler = {
async pre(response) {
let {request} = response;
let {url, type, tabId, frameId, documentUrl} = request;
-
+
url = ListStore.urlItem(url);
let site = ListStore.siteItem(url);
-
+
let blacklistedSite = blacklist.contains(site);
let blacklisted = blacklistedSite || blacklist.contains(url);
let topUrl = request.frameAncestors && request.frameAncestors.pop() || documentUrl;
-
+
if (blacklisted) {
if (type === "script") {
// abort the request before the response gets fetched
- addReportEntry(tabId, url, {url: topUrl,
+ addReportEntry(tabId, url, {url: topUrl,
"blacklisted": [url, blacklistedSite ? `User blacklisted ${site}` : "Blacklisted by user"]});
return ResponseProcessor.REJECT;
- }
+ }
// use CSP to restrict JavaScript execution in the page
request.responseHeaders.unshift({
name: `Content-security-policy`,
@@ -883,7 +883,7 @@ var ResponseHandler = {
if (type === "script") {
if (whitelisted) {
// accept the script and stop processing
- addReportEntry(tabId, url, {url: topUrl,
+ addReportEntry(tabId, url, {url: topUrl,
"whitelisted": [url, whitelistedSite ? `User whitelisted ${site}` : "Whitelisted by user"]});
return ResponseProcessor.ACCEPT;
} else {
@@ -908,7 +908,7 @@ var ResponseHandler = {
// let's keep processing
return ResponseProcessor.CONTINUE;
},
-
+
/**
* Here we do the heavylifting, analyzing unknown scripts
*/
@@ -931,7 +931,7 @@ async function handle_script(response, whitelisted){
}
/**
-* Serializes HTMLDocument objects including the root element and
+* Serializes HTMLDocument objects including the root element and
* the DOCTYPE declaration
*/
function doc2HTML(doc) {
@@ -955,7 +955,7 @@ function remove_noscripts(html_doc){
html_doc.getElementsByName("librejs-path")[i].outerHTML = html_doc.getElementsByName("librejs-path")[i].innerHTML;
}
}
-
+
return doc2HTML(html_doc);
}
@@ -966,23 +966,23 @@ function remove_noscripts(html_doc){
function read_metadata(meta_element){
if(meta_element === undefined || meta_element === null){
- return;
+ return;
}
- console.log("metadata found");
-
+ console.log("metadata found");
+
var metadata = {};
-
- try{
+
+ try{
metadata = JSON.parse(meta_element.innerHTML);
}catch(error){
console.log("Could not parse metadata on page.")
return false;
}
-
+
var license_str = metadata["intrinsic-events"];
if(license_str === undefined){
- console.log("No intrinsic events license");
+ console.log("No intrinsic events license");
return false;
}
console.log(license_str);
@@ -992,7 +992,7 @@ function read_metadata(meta_element){
console.log("invalid (>2 tokens)");
return false;
}
-
+
// this should be adequete to escape the HTML escaping
parts[0] = parts[0].replace(/&amp;/g, '&');
@@ -1013,25 +1013,25 @@ function read_metadata(meta_element){
* Reads/changes the HTML of a page and the scripts within it.
*/
async function editHtml(html, documentUrl, tabId, frameId, whitelisted){
-
+
var parser = new DOMParser();
var html_doc = parser.parseFromString(html, "text/html");
-
+
// moves external licenses reference, if any, before any <SCRIPT> element
- ExternalLicenses.optimizeDocument(html_doc, {tabId, frameId, documentUrl});
-
+ ExternalLicenses.optimizeDocument(html_doc, {tabId, frameId, documentUrl});
+
let url = ListStore.urlItem(documentUrl);
-
+
if (whitelisted) { // don't bother rewriting
await get_script(html, url, tabId, whitelisted); // generates whitelisted report
return null;
}
var scripts = html_doc.scripts;
-
+
var meta_element = html_doc.getElementById("LibreJS-info");
var first_script_src = "";
-
+
// get the potential inline source that can contain a license
for (let script of scripts) {
// The script must be in-line and exist
@@ -1052,7 +1052,7 @@ async function editHtml(html, documentUrl, tabId, frameId, whitelisted){
scripts = [];
} else {
let modified = false;
-
+
// Deal with intrinsic events
for (let element of html_doc.all) {
let attributes = element.attributes;
@@ -1074,7 +1074,7 @@ async function editHtml(html, documentUrl, tabId, frameId, whitelisted){
}
}
}
-
+
let modifiedInline = false;
for(let i = 0, len = scripts.length; i < len; i++) {
let script = scripts[i];
@@ -1090,8 +1090,8 @@ async function editHtml(html, documentUrl, tabId, frameId, whitelisted){
}
}
if (modified) {
- return modifiedInline
- ? await remove_noscripts(html_doc)
+ return modifiedInline
+ ? await remove_noscripts(html_doc)
: doc2HTML(html_doc);
}
}
@@ -1105,7 +1105,7 @@ async function editHtml(html, documentUrl, tabId, frameId, whitelisted){
async function handle_html(response, whitelisted) {
let {text, request} = response;
let {url, tabId, frameId, type} = request;
- if (type === "main_frame") {
+ if (type === "main_frame") {
activityReports[tabId] = await createReport({url, tabId});
updateBadge(tabId);
}
@@ -1136,7 +1136,7 @@ async function init_addon(){
let all_types = [
"beacon", "csp_report", "font", "image", "imageset", "main_frame", "media",
"object", "object_subrequest", "ping", "script", "stylesheet", "sub_frame",
- "web_manifest", "websocket", "xbl", "xml_dtd", "xmlhttprequest", "xslt",
+ "web_manifest", "websocket", "xbl", "xml_dtd", "xmlhttprequest", "xslt",
"other"
];
browser.webRequest.onBeforeRequest.addListener(
@@ -1144,7 +1144,7 @@ async function init_addon(){
{urls: ["<all_urls>"], types: all_types},
["blocking"]
);
-
+
// Analyzes all the html documents and external scripts as they're loaded
ResponseProcessor.install(ResponseHandler);
diff --git a/manifest.json b/manifest.json
index aecf9a8..03778d7 100644
--- a/manifest.json
+++ b/manifest.json
@@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "GNU LibreJS [webExtensions]",
"short_name": "LibreJS [experimental]",
- "version": "7.16",
+ "version": "7.17",
"author": "various",
"description": "Only allows free and/or trivial Javascript to run.",
"applications": {
@@ -34,7 +34,8 @@
"default_popup": "html/display_panel/content/display-panel.html"
},
"options_ui": {
- "page": "html/preferences_panel/preferences_panel.html"
+ "page": "html/preferences_panel/preferences_panel.html",
+ "open_in_tab": true
},
"web_accessible_resources": [
"html/report_page/report.html"