From c2530fc59734b16a642723b3a9401f9da5033b2d Mon Sep 17 00:00:00 2001 From: hackademix Date: Thu, 13 Sep 2018 16:18:43 +0200 Subject: Adjust directory layout and packaging to allow Storage.js to be shared with the settings page in the xpi release. --- bg/ListManager.js | 2 +- bg/Storage.js | 130 ------------------ build.sh | 1 + common/Storage.js | 130 ++++++++++++++++++ html/preferences_panel/preferences_panel.html | 2 +- main_background.js | 190 +++++++++++++------------- 6 files changed, 228 insertions(+), 227 deletions(-) delete mode 100644 bg/Storage.js create mode 100644 common/Storage.js diff --git a/bg/ListManager.js b/bg/ListManager.js index 7284aca..e0a85e9 100644 --- a/bg/ListManager.js +++ b/bg/ListManager.js @@ -23,7 +23,7 @@ A class to manage whitelist/blacklist operations */ -let {ListStore} = require("./Storage"); +let {ListStore} = require("../common/Storage"); class ListManager { constructor(whitelist, blacklist, builtInHashes) { diff --git a/bg/Storage.js b/bg/Storage.js deleted file mode 100644 index a83ce8f..0000000 --- a/bg/Storage.js +++ /dev/null @@ -1,130 +0,0 @@ -/** -* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript. -* -* Copyright (C) 2018 Giorgio Maone -* -* 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 . -*/ - -/** - A tiny wrapper around extensions storage API, supporting CSV serialization for - retro-compatibility -*/ -"use strict"; - -var Storage = { - ARRAY: { - 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) { - return await browser.storage.local.set({[key]: [...list]}); - }, - }, - - CSV: { - async load(key) { - 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(",")}); - } - } -}; - -/** - A class to hold and persist blacklists and whitelists -*/ - -class ListStore { - constructor(key, storage = Storage.ARRAY) { - 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})`; - } - static urlItem(url) { - let queryPos = url.indexOf("?"); - return queryPos === -1 ? url : url.substring(0, queryPos); - } - static siteItem(url) { - if (url.endsWith("/*")) return url; - try { - return `${new URL(url).origin}/*`; - } catch (e) { - return `${url}/*`; - } - } - - async save() { - this._saving = true; - try { - return await this.storage.save(this.key, this.items); - } finally { - this._saving = false; - } - } - - async load(values = undefined) { - try { - this.items = await this.storage.load(this.key, values); - } catch (e) { - console.error(e); - } - return this.items; - } - - async store(...items) { - let size = this.items.size; - let changed = false; - for (let item of items) { - if (size !== this.items.add(item).size) { - changed = true; - } - } - return changed && 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); - } -} -if (typeof module === "object") { - module.exports = { ListStore, Storage }; -} 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/common/Storage.js b/common/Storage.js new file mode 100644 index 0000000..a83ce8f --- /dev/null +++ b/common/Storage.js @@ -0,0 +1,130 @@ +/** +* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript. +* +* Copyright (C) 2018 Giorgio Maone +* +* 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 . +*/ + +/** + A tiny wrapper around extensions storage API, supporting CSV serialization for + retro-compatibility +*/ +"use strict"; + +var Storage = { + ARRAY: { + 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) { + return await browser.storage.local.set({[key]: [...list]}); + }, + }, + + CSV: { + async load(key) { + 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(",")}); + } + } +}; + +/** + A class to hold and persist blacklists and whitelists +*/ + +class ListStore { + constructor(key, storage = Storage.ARRAY) { + 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})`; + } + static urlItem(url) { + let queryPos = url.indexOf("?"); + return queryPos === -1 ? url : url.substring(0, queryPos); + } + static siteItem(url) { + if (url.endsWith("/*")) return url; + try { + return `${new URL(url).origin}/*`; + } catch (e) { + return `${url}/*`; + } + } + + async save() { + this._saving = true; + try { + return await this.storage.save(this.key, this.items); + } finally { + this._saving = false; + } + } + + async load(values = undefined) { + try { + this.items = await this.storage.load(this.key, values); + } catch (e) { + console.error(e); + } + return this.items; + } + + async store(...items) { + let size = this.items.size; + let changed = false; + for (let item of items) { + if (size !== this.items.add(item).size) { + changed = true; + } + } + return changed && 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); + } +} +if (typeof module === "object") { + module.exports = { ListStore, Storage }; +} diff --git a/html/preferences_panel/preferences_panel.html b/html/preferences_panel/preferences_panel.html index fff6f9c..ab89d92 100644 --- a/html/preferences_panel/preferences_panel.html +++ b/html/preferences_panel/preferences_panel.html @@ -29,7 +29,7 @@ LibreJS preferences - + diff --git a/main_background.js b/main_background.js index 08007f8..b003c90 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(/&/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