summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bg/ChildPolicies.js4
-rw-r--r--src/bg/RequestGuard.js9
-rw-r--r--src/bg/deferWebTraffic.js2
-rw-r--r--src/content/ftp.js6
-rw-r--r--src/lib/UA.js12
-rw-r--r--src/lib/browser-polyfill.js1186
-rw-r--r--src/lib/persistent-tabs.js4
-rw-r--r--src/lib/restricted.js34
-rw-r--r--src/manifest.json10
-rw-r--r--src/test/Test.js2
-rw-r--r--src/ui/Prompts.js10
-rw-r--r--src/ui/options.css8
-rw-r--r--src/ui/options.html2
-rw-r--r--src/ui/options.js2
-rw-r--r--src/ui/popup.css4
-rw-r--r--src/ui/popup.html2
-rw-r--r--src/ui/prompt.html2
-rw-r--r--src/ui/siteInfo.html2
-rw-r--r--src/ui/toolbar.js2
-rw-r--r--src/ui/ui-hc.css4
-rw-r--r--src/ui/ui.css11
-rw-r--r--src/xss/InjectionChecker.js9
-rw-r--r--src/xss/XSS.js2
23 files changed, 1288 insertions, 41 deletions
diff --git a/src/bg/ChildPolicies.js b/src/bg/ChildPolicies.js
index 74aeccb..5727762 100644
--- a/src/bg/ChildPolicies.js
+++ b/src/bg/ChildPolicies.js
@@ -51,6 +51,10 @@
}
};
+ if (!browser.contentScripts) { // #chromium fallback
+ Scripts.register = () => {};
+ }
+
let flatten = arr => arr.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
let protocolRx = /^(\w+):/i;
diff --git a/src/bg/RequestGuard.js b/src/bg/RequestGuard.js
index e7be814..0731b7b 100644
--- a/src/bg/RequestGuard.js
+++ b/src/bg/RequestGuard.js
@@ -20,7 +20,10 @@ var RequestGuard = (() => {
media: "media",
other: "",
};
- const allTypes = Object.keys(policyTypesMap);
+ const allTypes = UA.isMozilla ? Object.keys(policyTypesMap)
+ : ["main_frame", "sub_frame", "stylesheet", "script", "image", "font",
+ "object", "xmlhttprequest", "ping", "csp_report", "media", "websocket", "other"];
+
Object.assign(policyTypesMap, {"webgl": "webgl"}); // fake types
const TabStatus = {
map: new Map(),
@@ -254,7 +257,7 @@ var RequestGuard = (() => {
return redirected;
}
const ABORT = {cancel: true}, ALLOW = {};
- const INTERNAL_SCHEME = /^(?:chrome|resource|moz-extension|about):/;
+ const INTERNAL_SCHEME = /^(?:chrome|resource|(?:moz|chrome)-extension|about):/;
const listeners = {
onBeforeRequest(request) {
try {
@@ -326,7 +329,7 @@ var RequestGuard = (() => {
capabilities = perms.capabilities;
} else {
capabilities = perms.capabilities;
- if (frameAncestors.length > 0 && ns.sync.cascadeRestrictions) {
+ if (frameAncestors && frameAncestors.length > 0 && ns.sync.cascadeRestrictions) {
// cascade top document's restrictions to subframes
let topUrl = frameAncestors.pop().url;
let topPerms = policy.get(topUrl, topUrl).perms;
diff --git a/src/bg/deferWebTraffic.js b/src/bg/deferWebTraffic.js
index a384e29..571073a 100644
--- a/src/bg/deferWebTraffic.js
+++ b/src/bg/deferWebTraffic.js
@@ -31,7 +31,7 @@ function deferWebTraffic(promiseToWaitFor, next) {
if (type === "main_frame") {
seenTabs.add(tabId);
} else if (documentUrl) {
- if (frameId !== 0) {
+ if (frameId !== 0 && request.frameAncestors) {
documentUrl = request.frameAncestors.pop().url;
}
reloadTab(tabId);
diff --git a/src/content/ftp.js b/src/content/ftp.js
index 4b9585e..73ef5e8 100644
--- a/src/content/ftp.js
+++ b/src/content/ftp.js
@@ -9,7 +9,7 @@
) {
return;
}
-
+
gTable = document.getElementsByTagName("table")[0];
gTBody = gTable.tBodies[0];
if (gTBody.rows.length < 2)
@@ -31,7 +31,7 @@
headCells[i].addEventListener("click", rowAction(i), true);
}
if (gUI_showHidden) {
- gRows = Array.slice(gTBody.rows);
+ gRows = Array.from(gTBody.rows);
hiddenObjects = gRows.some(row => row.className == "hidden-object");
}
gTable.setAttribute("order", "");
@@ -60,7 +60,7 @@
}
function orderBy(column) {
if (!gRows)
- gRows = Array.slice(gTBody.rows);
+ gRows = Array.from(gTBody.rows);
var order;
if (gOrderBy == column) {
order = gTable.getAttribute("order") == "asc" ? "desc" : "asc";
diff --git a/src/lib/UA.js b/src/lib/UA.js
new file mode 100644
index 0000000..1a1771a
--- /dev/null
+++ b/src/lib/UA.js
@@ -0,0 +1,12 @@
+var UA = {
+ isMozilla: document.URL.startsWith("moz-"),
+}
+
+if (!UA.isMozilla) {
+ if (typeof chrome === "object" && !chrome.tabs && typeof exportFunction === "undefined") {
+ // content script shims
+ window.exportFunction = () => {};
+ }
+} else {
+ document.documentElement.classList.add("mozwebext");
+}
diff --git a/src/lib/browser-polyfill.js b/src/lib/browser-polyfill.js
new file mode 100644
index 0000000..b3dd82d
--- /dev/null
+++ b/src/lib/browser-polyfill.js
@@ -0,0 +1,1186 @@
+(function (global, factory) {
+ if (typeof define === "function" && define.amd) {
+ define("webextension-polyfill", ["module"], factory);
+ } else if (typeof exports !== "undefined") {
+ factory(module);
+ } else {
+ var mod = {
+ exports: {}
+ };
+ factory(mod);
+ global.browser = mod.exports;
+ }
+})(this, function (module) {
+ /* webextension-polyfill - v0.3.1 - Tue Aug 21 2018 10:09:34 */
+ /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set sts=2 sw=2 et tw=80: */
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ "use strict";
+
+ if (typeof browser === "undefined" || Object.getPrototypeOf(browser) !== Object.prototype) {
+ const CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE = "The message port closed before a response was received.";
+ const SEND_RESPONSE_DEPRECATION_WARNING = "Returning a Promise is the preferred way to send a reply from an onMessage/onMessageExternal listener, as the sendResponse will be removed from the specs (See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage)";
+
+ // Wrapping the bulk of this polyfill in a one-time-use function is a minor
+ // optimization for Firefox. Since Spidermonkey does not fully parse the
+ // contents of a function until the first time it's called, and since it will
+ // never actually need to be called, this allows the polyfill to be included
+ // in Firefox nearly for free.
+ const wrapAPIs = () => {
+ // NOTE: apiMetadata is associated to the content of the api-metadata.json file
+ // at build time by replacing the following "include" with the content of the
+ // JSON file.
+ const apiMetadata = {
+ "alarms": {
+ "clear": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "clearAll": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "get": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "getAll": {
+ "minArgs": 0,
+ "maxArgs": 0
+ }
+ },
+ "bookmarks": {
+ "create": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "get": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getChildren": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getRecent": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getSubTree": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getTree": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "move": {
+ "minArgs": 2,
+ "maxArgs": 2
+ },
+ "remove": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeTree": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "search": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "update": {
+ "minArgs": 2,
+ "maxArgs": 2
+ }
+ },
+ "browserAction": {
+ "disable": {
+ "minArgs": 0,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "enable": {
+ "minArgs": 0,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "getBadgeBackgroundColor": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getBadgeText": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getPopup": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getTitle": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "openPopup": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "setBadgeBackgroundColor": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "setBadgeText": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "setIcon": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "setPopup": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "setTitle": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ }
+ },
+ "browsingData": {
+ "remove": {
+ "minArgs": 2,
+ "maxArgs": 2
+ },
+ "removeCache": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeCookies": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeDownloads": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeFormData": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeHistory": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeLocalStorage": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removePasswords": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removePluginData": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "settings": {
+ "minArgs": 0,
+ "maxArgs": 0
+ }
+ },
+ "commands": {
+ "getAll": {
+ "minArgs": 0,
+ "maxArgs": 0
+ }
+ },
+ "contextMenus": {
+ "remove": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeAll": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "update": {
+ "minArgs": 2,
+ "maxArgs": 2
+ }
+ },
+ "cookies": {
+ "get": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getAll": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getAllCookieStores": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "remove": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "set": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ },
+ "devtools": {
+ "inspectedWindow": {
+ "eval": {
+ "minArgs": 1,
+ "maxArgs": 2
+ }
+ },
+ "panels": {
+ "create": {
+ "minArgs": 3,
+ "maxArgs": 3,
+ "singleCallbackArg": true
+ }
+ }
+ },
+ "downloads": {
+ "cancel": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "download": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "erase": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getFileIcon": {
+ "minArgs": 1,
+ "maxArgs": 2
+ },
+ "open": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "pause": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeFile": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "resume": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "search": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "show": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ }
+ },
+ "extension": {
+ "isAllowedFileSchemeAccess": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "isAllowedIncognitoAccess": {
+ "minArgs": 0,
+ "maxArgs": 0
+ }
+ },
+ "history": {
+ "addUrl": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "deleteAll": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "deleteRange": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "deleteUrl": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getVisits": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "search": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ },
+ "i18n": {
+ "detectLanguage": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getAcceptLanguages": {
+ "minArgs": 0,
+ "maxArgs": 0
+ }
+ },
+ "identity": {
+ "launchWebAuthFlow": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ },
+ "idle": {
+ "queryState": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ },
+ "management": {
+ "get": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getAll": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "getSelf": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "setEnabled": {
+ "minArgs": 2,
+ "maxArgs": 2
+ },
+ "uninstallSelf": {
+ "minArgs": 0,
+ "maxArgs": 1
+ }
+ },
+ "notifications": {
+ "clear": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "create": {
+ "minArgs": 1,
+ "maxArgs": 2
+ },
+ "getAll": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "getPermissionLevel": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "update": {
+ "minArgs": 2,
+ "maxArgs": 2
+ }
+ },
+ "pageAction": {
+ "getPopup": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getTitle": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "hide": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "setIcon": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "setPopup": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "setTitle": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "show": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ }
+ },
+ "permissions": {
+ "contains": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getAll": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "remove": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "request": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ },
+ "runtime": {
+ "getBackgroundPage": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "getBrowserInfo": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "getPlatformInfo": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "openOptionsPage": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "requestUpdateCheck": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "sendMessage": {
+ "minArgs": 1,
+ "maxArgs": 3
+ },
+ "sendNativeMessage": {
+ "minArgs": 2,
+ "maxArgs": 2
+ },
+ "setUninstallURL": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ },
+ "sessions": {
+ "getDevices": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "getRecentlyClosed": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "restore": {
+ "minArgs": 0,
+ "maxArgs": 1
+ }
+ },
+ "storage": {
+ "local": {
+ "clear": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "get": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "getBytesInUse": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "remove": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "set": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ },
+ "managed": {
+ "get": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "getBytesInUse": {
+ "minArgs": 0,
+ "maxArgs": 1
+ }
+ },
+ "sync": {
+ "clear": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "get": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "getBytesInUse": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "remove": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "set": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ }
+ },
+ "tabs": {
+ "captureVisibleTab": {
+ "minArgs": 0,
+ "maxArgs": 2
+ },
+ "create": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "detectLanguage": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "discard": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "duplicate": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "executeScript": {
+ "minArgs": 1,
+ "maxArgs": 2
+ },
+ "get": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getCurrent": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "getZoom": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "getZoomSettings": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "highlight": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "insertCSS": {
+ "minArgs": 1,
+ "maxArgs": 2
+ },
+ "move": {
+ "minArgs": 2,
+ "maxArgs": 2
+ },
+ "query": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "reload": {
+ "minArgs": 0,
+ "maxArgs": 2
+ },
+ "remove": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeCSS": {
+ "minArgs": 1,
+ "maxArgs": 2
+ },
+ "sendMessage": {
+ "minArgs": 2,
+ "maxArgs": 3
+ },
+ "setZoom": {
+ "minArgs": 1,
+ "maxArgs": 2
+ },
+ "setZoomSettings": {
+ "minArgs": 1,
+ "maxArgs": 2
+ },
+ "update": {
+ "minArgs": 1,
+ "maxArgs": 2
+ }
+ },
+ "topSites": {
+ "get": {
+ "minArgs": 0,
+ "maxArgs": 0
+ }
+ },
+ "webNavigation": {
+ "getAllFrames": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getFrame": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ },
+ "webRequest": {
+ "handlerBehaviorChanged": {
+ "minArgs": 0,
+ "maxArgs": 0
+ }
+ },
+ "windows": {
+ "create": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "get": {
+ "minArgs": 1,
+ "maxArgs": 2
+ },
+ "getAll": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "getCurrent": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "getLastFocused": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "remove": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "update": {
+ "minArgs": 2,
+ "maxArgs": 2
+ }
+ }
+ };
+
+ if (Object.keys(apiMetadata).length === 0) {
+ throw new Error("api-metadata.json has not been included in browser-polyfill");
+ }
+
+ /**
+ * A WeakMap subclass which creates and stores a value for any key which does
+ * not exist when accessed, but behaves exactly as an ordinary WeakMap
+ * otherwise.
+ *
+ * @param {function} createItem
+ * A function which will be called in order to create the value for any
+ * key which does not exist, the first time it is accessed. The
+ * function receives, as its only argument, the key being created.
+ */
+ class DefaultWeakMap extends WeakMap {
+ constructor(createItem, items = undefined) {
+ super(items);
+ this.createItem = createItem;
+ }
+
+ get(key) {
+ if (!this.has(key)) {
+ this.set(key, this.createItem(key));
+ }
+
+ return super.get(key);
+ }
+ }
+
+ /**
+ * Returns true if the given object is an object with a `then` method, and can
+ * therefore be assumed to behave as a Promise.
+ *
+ * @param {*} value The value to test.
+ * @returns {boolean} True if the value is thenable.
+ */
+ const isThenable = value => {
+ return value && typeof value === "object" && typeof value.then === "function";
+ };
+
+ /**
+ * Creates and returns a function which, when called, will resolve or reject
+ * the given promise based on how it is called:
+ *
+ * - If, when called, `chrome.runtime.lastError` contains a non-null object,
+ * the promise is rejected with that value.
+ * - If the function is called with exactly one argument, the promise is
+ * resolved to that value.
+ * - Otherwise, the promise is resolved to an array containing all of the
+ * function's arguments.
+ *
+ * @param {object} promise
+ * An object containing the resolution and rejection functions of a
+ * promise.
+ * @param {function} promise.resolve
+ * The promise's resolution function.
+ * @param {function} promise.rejection
+ * The promise's rejection function.
+ * @param {object} metadata
+ * Metadata about the wrapped method which has created the callback.
+ * @param {integer} metadata.maxResolvedArgs
+ * The maximum number of arguments which may be passed to the
+ * callback created by the wrapped async function.
+ *
+ * @returns {function}
+ * The generated callback function.
+ */
+ const makeCallback = (promise, metadata) => {
+ return (...callbackArgs) => {
+ if (chrome.runtime.lastError) {
+ promise.reject(chrome.runtime.lastError);
+ } else if (metadata.singleCallbackArg || callbackArgs.length <= 1) {
+ promise.resolve(callbackArgs[0]);
+ } else {
+ promise.resolve(callbackArgs);
+ }
+ };
+ };
+
+ const pluralizeArguments = numArgs => numArgs == 1 ? "argument" : "arguments";
+
+ /**
+ * Creates a wrapper function for a method with the given name and metadata.
+ *
+ * @param {string} name
+ * The name of the method which is being wrapped.
+ * @param {object} metadata
+ * Metadata about the method being wrapped.
+ * @param {integer} metadata.minArgs
+ * The minimum number of arguments which must be passed to the
+ * function. If called with fewer than this number of arguments, the
+ * wrapper will raise an exception.
+ * @param {integer} metadata.maxArgs
+ * The maximum number of arguments which may be passed to the
+ * function. If called with more than this number of arguments, the
+ * wrapper will raise an exception.
+ * @param {integer} metadata.maxResolvedArgs
+ * The maximum number of arguments which may be passed to the
+ * callback created by the wrapped async function.
+ *
+ * @returns {function(object, ...*)}
+ * The generated wrapper function.
+ */
+ const wrapAsyncFunction = (name, metadata) => {
+ return function asyncFunctionWrapper(target, ...args) {
+ if (args.length < metadata.minArgs) {
+ throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
+ }
+
+ if (args.length > metadata.maxArgs) {
+ throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);
+ }
+
+ return new Promise((resolve, reject) => {
+ if (metadata.fallbackToNoCallback) {
+ // This API method has currently no callback on Chrome, but it return a promise on Firefox,
+ // and so the polyfill will try to call it with a callback first, and it will fallback
+ // to not passing the callback if the first call fails.
+ try {
+ target[name](...args, makeCallback({ resolve, reject }, metadata));
+ } catch (cbError) {
+ console.warn(`${name} API method doesn't seem to support the callback parameter, ` + "falling back to call it without a callback: ", cbError);
+
+ target[name](...args);
+
+ // Update the API method metadata, so that the next API calls will not try to
+ // use the unsupported callback anymore.
+ metadata.fallbackToNoCallback = false;
+ metadata.noCallback = true;
+
+ resolve();
+ }
+ } else if (metadata.noCallback) {
+ target[name](...args);
+ resolve();
+ } else {
+ target[name](...args, makeCallback({ resolve, reject }, metadata));
+ }
+ });
+ };
+ };
+
+ /**
+ * Wraps an existing method of the target object, so that calls to it are
+ * intercepted by the given wrapper function. The wrapper function receives,
+ * as its first argument, the original `target` object, followed by each of
+ * the arguments passed to the original method.
+ *
+ * @param {object} target
+ * The original target object that the wrapped method belongs to.
+ * @param {function} method
+ * The method being wrapped. This is used as the target of the Proxy
+ * object which is created to wrap the method.
+ * @param {function} wrapper
+ * The wrapper function which is called in place of a direct invocation
+ * of the wrapped method.
+ *
+ * @returns {Proxy<function>}
+ * A Proxy object for the given method, which invokes the given wrapper
+ * method in its place.
+ */
+ const wrapMethod = (target, method, wrapper) => {
+ return new Proxy(method, {
+ apply(targetMethod, thisObj, args) {
+ return wrapper.call(thisObj, target, ...args);
+ }
+ });
+ };
+
+ let hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
+
+ /**
+ * Wraps an object in a Proxy which intercepts and wraps certain methods
+ * based on the given `wrappers` and `metadata` objects.
+ *
+ * @param {object} target
+ * The target object to wrap.
+ *
+ * @param {object} [wrappers = {}]
+ * An object tree containing wrapper functions for special cases. Any
+ * function present in this object tree is called in place of the
+ * method in the same location in the `target` object tree. These
+ * wrapper methods are invoked as described in {@see wrapMethod}.
+ *
+ * @param {object} [metadata = {}]
+ * An object tree containing metadata used to automatically generate
+ * Promise-based wrapper functions for asynchronous. Any function in
+ * the `target` object tree which has a corresponding metadata object
+ * in the same location in the `metadata` tree is replaced with an
+ * automatically-generated wrapper function, as described in
+ * {@see wrapAsyncFunction}
+ *
+ * @returns {Proxy<object>}
+ */
+ const wrapObject = (target, wrappers = {}, metadata = {}) => {
+ let cache = Object.create(null);
+ let handlers = {
+ has(proxyTarget, prop) {
+ return prop in target || prop in cache;
+ },
+
+ get(proxyTarget, prop, receiver) {
+ if (prop in cache) {
+ return cache[prop];
+ }
+
+ if (!(prop in target)) {
+ return undefined;
+ }
+
+ let value = target[prop];
+
+ if (typeof value === "function") {
+ // This is a method on the underlying object. Check if we need to do
+ // any wrapping.
+
+ if (typeof wrappers[prop] === "function") {
+ // We have a special-case wrapper for this method.
+ value = wrapMethod(target, target[prop], wrappers[prop]);
+ } else if (hasOwnProperty(metadata, prop)) {
+ // This is an async method that we have metadata for. Create a
+ // Promise wrapper for it.
+ let wrapper = wrapAsyncFunction(prop, metadata[prop]);
+ value = wrapMethod(target, target[prop], wrapper);
+ } else {
+ // This is a method that we don't know or care about. Return the
+ // original method, bound to the underlying object.
+ value = value.bind(target);
+ }
+ } else if (typeof value === "object" && value !== null && (hasOwnProperty(wrappers, prop) || hasOwnProperty(metadata, prop))) {
+ // This is an object that we need to do some wrapping for the children
+ // of. Create a sub-object wrapper for it with the appropriate child
+ // metadata.
+ value = wrapObject(value, wrappers[prop], metadata[prop]);
+ } else {
+ // We don't need to do any wrapping for this property,
+ // so just forward all access to the underlying object.
+ Object.defineProperty(cache, prop, {
+ configurable: true,
+ enumerable: true,
+ get() {
+ return target[prop];
+ },
+ set(value) {
+ target[prop] = value;
+ }
+ });
+
+ return value;
+ }
+
+ cache[prop] = value;
+ return value;
+ },
+
+ set(proxyTarget, prop, value, receiver) {
+ if (prop in cache) {
+ cache[prop] = value;
+ } else {
+ target[prop] = value;
+ }
+ return true;
+ },
+
+ defineProperty(proxyTarget, prop, desc) {
+ return Reflect.defineProperty(cache, prop, desc);
+ },
+
+ deleteProperty(proxyTarget, prop) {
+ return Reflect.deleteProperty(cache, prop);
+ }
+ };
+
+ // Per contract of the Proxy API, the "get" proxy handler must return the
+ // original value of the target if that value is declared read-only and
+ // non-configurable. For this reason, we create an object with the
+ // prototype set to `target` instead of using `target` directly.
+ // Otherwise we cannot return a custom object for APIs that
+ // are declared read-only and non-configurable, such as `chrome.devtools`.
+ //
+ // The proxy handlers themselves will still use the original `target`
+ // instead of the `proxyTarget`, so that the methods and properties are
+ // dereferenced via the original targets.
+ let proxyTarget = Object.create(target);
+ return new Proxy(proxyTarget, handlers);
+ };
+
+ /**
+ * Creates a set of wrapper functions for an event object, which handles
+ * wrapping of listener functions that those messages are passed.
+ *
+ * A single wrapper is created for each listener function, and stored in a
+ * map. Subsequent calls to `addListener`, `hasListener`, or `removeListener`
+ * retrieve the original wrapper, so that attempts to remove a
+ * previously-added listener work as expected.
+ *
+ * @param {DefaultWeakMap<function, function>} wrapperMap
+ * A DefaultWeakMap object which will create the appropriate wrapper
+ * for a given listener function when one does not exist, and retrieve
+ * an existing one when it does.
+ *
+ * @returns {object}
+ */
+ const wrapEvent = wrapperMap => ({
+ addListener(target, listener, ...args) {
+ target.addListener(wrapperMap.get(listener), ...args);
+ },
+
+ hasListener(target, listener) {
+ return target.hasListener(wrapperMap.get(listener));
+ },
+
+ removeListener(target, listener) {
+ target.removeListener(wrapperMap.get(listener));
+ }
+ });
+
+ // Keep track if the deprecation warning has been logged at least once.
+ let loggedSendResponseDeprecationWarning = false;
+
+ const onMessageWrappers = new DefaultWeakMap(listener => {
+ if (typeof listener !== "function") {
+ return listener;
+ }
+
+ /**
+ * Wraps a message listener function so that it may send responses based on
+ * its return value, rather than by returning a sentinel value and calling a
+ * callback. If the listener function returns a Promise, the response is
+ * sent when the promise either resolves or rejects.
+ *
+ * @param {*} message
+ * The message sent by the other end of the channel.
+ * @param {object} sender
+ * Details about the sender of the message.
+ * @param {function(*)} sendResponse
+ * A callback which, when called with an arbitrary argument, sends
+ * that value as a response.
+ * @returns {boolean}
+ * True if the wrapped listener returned a Promise, which will later
+ * yield a response. False otherwise.
+ */
+ return function onMessage(message, sender, sendResponse) {
+ let didCallSendResponse = false;
+
+ let wrappedSendResponse;
+ let sendResponsePromise = new Promise(resolve => {
+ wrappedSendResponse = function (response) {
+ if (!loggedSendResponseDeprecationWarning) {
+ console.warn(SEND_RESPONSE_DEPRECATION_WARNING, new Error().stack);
+ loggedSendResponseDeprecationWarning = true;
+ }
+ didCallSendResponse = true;
+ resolve(response);
+ };
+ });
+
+ let result;
+ try {
+ result = listener(message, sender, wrappedSendResponse);
+ } catch (err) {
+ result = Promise.reject(err);
+ }
+
+ const isResultThenable = result !== true && isThenable(result);
+
+ // If the listener didn't returned true or a Promise, or called
+ // wrappedSendResponse synchronously, we can exit earlier
+ // because there will be no response sent from this listener.
+ if (result !== true && !isResultThenable && !didCallSendResponse) {
+ return false;
+ }
+
+ // A small helper to send the message if the promise resolves
+ // and an error if the promise rejects (a wrapped sendMessage has
+ // to translate the message into a resolved promise or a rejected
+ // promise).
+ const sendPromisedResult = promise => {
+ promise.then(msg => {
+ // send the message value.
+ sendResponse(msg);
+ }, error => {
+ // Send a JSON representation of the error if the rejected value
+ // is an instance of error, or the object itself otherwise.
+ let message;
+ if (error && (error instanceof Error || typeof error.message === "string")) {
+ message = error.message;
+ } else {
+ message = "An unexpected error occurred";
+ }
+
+ sendResponse({
+ __mozWebExtensionPolyfillReject__: true,
+ message
+ });
+ }).catch(err => {
+ // Print an error on the console if unable to send the response.
+ console.error("Failed to send onMessage rejected reply", err);
+ });
+ };
+
+ // If the listener returned a Promise, send the resolved value as a
+ // result, otherwise wait the promise related to the wrappedSendResponse
+ // callback to resolve and send it as a response.
+ if (isResultThenable) {
+ sendPromisedResult(result);
+ } else {
+ sendPromisedResult(sendResponsePromise);
+ }
+
+ // Let Chrome know that the listener is replying.
+ return true;
+ };
+ });
+
+ const wrappedSendMessageCallback = ({ reject, resolve }, reply) => {
+ if (chrome.runtime.lastError) {
+ // Detect when none of the listeners replied to the sendMessage call and resolve
+ // the promise to undefined as in Firefox.
+ // See https://github.com/mozilla/webextension-polyfill/issues/130
+ if (chrome.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) {
+ resolve();
+ } else {
+ reject(chrome.runtime.lastError);
+ }
+ } else if (reply && reply.__mozWebExtensionPolyfillReject__) {
+ // Convert back the JSON representation of the error into
+ // an Error instance.
+ reject(new Error(reply.message));
+ } else {
+ resolve(reply);
+ }
+ };
+
+ const wrappedSendMessage = (name, metadata, apiNamespaceObj, ...args) => {
+ if (args.length < metadata.minArgs) {
+ throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
+ }
+
+ if (args.length > metadata.maxArgs) {
+ throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);
+ }
+
+ return new Promise((resolve, reject) => {
+ const wrappedCb = wrappedSendMessageCallback.bind(null, { resolve, reject });
+ args.push(wrappedCb);
+ apiNamespaceObj.sendMessage(...args);
+ });
+ };
+
+ const staticWrappers = {
+ runtime: {
+ onMessage: wrapEvent(onMessageWrappers),
+ onMessageExternal: wrapEvent(onMessageWrappers),
+ sendMessage: wrappedSendMessage.bind(null, "sendMessage", { minArgs: 1, maxArgs: 3 })
+ },
+ tabs: {
+ sendMessage: wrappedSendMessage.bind(null, "sendMessage", { minArgs: 2, maxArgs: 3 })
+ }
+ };
+ const settingMetadata = {
+ clear: { minArgs: 1, maxArgs: 1 },
+ get: { minArgs: 1, maxArgs: 1 },
+ set: { minArgs: 1, maxArgs: 1 }
+ };
+ apiMetadata.privacy = {
+ network: {
+ networkPredictionEnabled: settingMetadata,
+ webRTCIPHandlingPolicy: settingMetadata
+ },
+ services: {
+ passwordSavingEnabled: settingMetadata
+ },
+ websites: {
+ hyperlinkAuditingEnabled: settingMetadata,
+ referrersEnabled: settingMetadata
+ }
+ };
+
+ return wrapObject(chrome, staticWrappers, apiMetadata);
+ };
+
+ // The build process adds a UMD wrapper around this file, which makes the
+ // `module` variable available.
+ module.exports = wrapAPIs(); // eslint-disable-line no-undef
+ } else {
+ module.exports = browser; // eslint-disable-line no-undef
+ }
+});
+//# sourceMappingURL=browser-polyfill.js.map
diff --git a/src/lib/persistent-tabs.js b/src/lib/persistent-tabs.js
index 8f7f711..f24c6dd 100644
--- a/src/lib/persistent-tabs.js
+++ b/src/lib/persistent-tabs.js
@@ -7,12 +7,12 @@ if (typeof flextabs === "function") {
let rx = new RegExp(`(?:^|[#;])tab-${id}=(\\d+)(?:;|$)`);
let current = location.hash.match(rx);
console.log(`persisted %o`, current);
- let toggles = tabs.querySelectorAll(".flextabs__toggle");
+ let toggles = Array.from(tabs.querySelectorAll(".flextabs__toggle"));
let currentToggle = toggles[current && parseInt(current[1]) || 0];
if (currentToggle) currentToggle.click();
for (let toggle of toggles) {
toggle.addEventListener("click", e => {
- let currentIdx = Array.indexOf(toggles, toggle);
+ let currentIdx = toggles.indexOf(toggle);
location.hash = location.hash.split(";").filter(p => !rx.test(p))
.concat(`tab-${id}=${currentIdx}`).join(";");
});
diff --git a/src/lib/restricted.js b/src/lib/restricted.js
index b9bddae..840ab69 100644
--- a/src/lib/restricted.js
+++ b/src/lib/restricted.js
@@ -1,22 +1,22 @@
{
// see https://bugzilla.mozilla.org/show_bug.cgi?id=1415644
- let domains = [
- "accounts-static.cdn.mozilla.net",
- "accounts.firefox.com",
- "addons.cdn.mozilla.net",
- "addons.mozilla.org",
- "api.accounts.firefox.com",
- "content.cdn.mozilla.net",
- "content.cdn.mozilla.net",
- "discovery.addons.mozilla.org",
- "input.mozilla.org",
- "install.mozilla.org",
- "oauth.accounts.firefox.com",
- "profile.accounts.firefox.com",
- "support.mozilla.org",
- "sync.services.mozilla.com",
- "testpilot.firefox.com",
- ];
+ let domains = UA.isMozilla ? [
+ "accounts-static.cdn.mozilla.net",
+ "accounts.firefox.com",
+ "addons.cdn.mozilla.net",
+ "addons.mozilla.org",
+ "api.accounts.firefox.com",
+ "content.cdn.mozilla.net",
+ "content.cdn.mozilla.net",
+ "discovery.addons.mozilla.org",
+ "input.mozilla.org",
+ "install.mozilla.org",
+ "oauth.accounts.firefox.com",
+ "profile.accounts.firefox.com",
+ "support.mozilla.org",
+ "sync.services.mozilla.com",
+ "testpilot.firefox.com",
+ ] : [ "chrome.google.com" ];
function isRestrictedURL(u) {
try {
diff --git a/src/manifest.json b/src/manifest.json
index ae158ec..1e68885 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -8,10 +8,10 @@
"strict_min_version": "59.0"
}
},
- "version": "10.2.5",
+ "version": "10.5rc1",
"description": "__MSG_Description__",
"incognito": "spanning",
-
+
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'none'",
"icons": {
@@ -35,6 +35,8 @@
"background": {
"persistent": true,
"scripts": [
+ "lib/UA.js",
+ "lib/browser-polyfill.js",
"lib/uuid.js",
"lib/log.js",
"lib/include.js",
@@ -77,6 +79,8 @@
"match_about_blank": true,
"all_frames": true,
"js": [
+ "lib/UA.js",
+ "lib/browser-polyfill.js",
"lib/log.js",
"lib/uuid.js",
"lib/sha256.js",
@@ -117,11 +121,13 @@
"commands": {
"_execute_browser_action": {
+ "description": "NoScript UI",
"suggested_key": {
"default": "Alt+Shift+N"
}
},
"togglePermissions": {
+ "description": "Toggle permissions",
"suggested_key": {
"default": "Ctrl+Shift+T"
}
diff --git a/src/test/Test.js b/src/test/Test.js
index 8ca2ed7..22145a6 100644
--- a/src/test/Test.js
+++ b/src/test/Test.js
@@ -26,7 +26,7 @@ var Test = (() => {
error(e);
}
this[r ? "passed" : "failed"]++;
- log(`${r ? "PASSED" : "FAILED"} ${msg || uneval(test)}`);
+ log(`${r ? "PASSED" : "FAILED"} ${msg || test}`);
if (typeof callback === "function") try {
callback(r, test, msg);
} catch(e) {
diff --git a/src/ui/Prompts.js b/src/ui/Prompts.js
index 03ea9ee..ed83ac2 100644
--- a/src/ui/Prompts.js
+++ b/src/ui/Prompts.js
@@ -7,14 +7,16 @@ var Prompts = (() => {
async open(data) {
promptData = data;
this.close();
- this.currentWindow = await browser.windows.create({
+ let options = {
url: browser.extension.getURL("ui/prompt.html"),
type: "panel",
- allowScriptsToClose: true,
- // titlePreface: "NoScript ",
width: data.features.width,
height: data.features.height,
- });
+ };
+ if (UA.isMozilla) {
+ options.allowScriptsToClose = true;
+ }
+ this.currentWindow = await browser.windows.create(options);
}
async close() {
if (this.currentWindow) {
diff --git a/src/ui/options.css b/src/ui/options.css
index 6e6eec7..ca64859 100644
--- a/src/ui/options.css
+++ b/src/ui/options.css
@@ -195,3 +195,11 @@ input[type="file"] {
border-radius: .2em .2em 0 0;
padding: .2em .4em;
}
+
+#xss-options {
+ display: none;
+}
+
+.mozwebext #xss-options {
+ display: block;
+}
diff --git a/src/ui/options.html b/src/ui/options.html
index 9ffaa69..dbf1e15 100644
--- a/src/ui/options.html
+++ b/src/ui/options.html
@@ -8,6 +8,8 @@
<link rel="stylesheet" href="/lib/flextabs.css" />
<link rel="stylesheet" href="options.css" />
<link rel="stylesheet" href="whirlpool.css" />
+<script src="/lib/UA.js"></script>
+<script src="/lib/browser-polyfill.js"></script>
<script src="/lib/include.js"></script>
<script src="/lib/log.js"></script>
<script src="/lib/flextabs.js"></script>
diff --git a/src/ui/options.js b/src/ui/options.js
index 1932269..2a52a82 100644
--- a/src/ui/options.js
+++ b/src/ui/options.js
@@ -103,7 +103,7 @@
}
let button = document.querySelector("#btn-delete-xss-choices");
let choices = UI.xssUserChoices;
- button.disabled = Object.keys(choices).length === 0;
+ button.disabled = !choices || Object.keys(choices).length === 0;
button.onclick = () => {
UI.updateSettings({
xssUserChoices: {}
diff --git a/src/ui/popup.css b/src/ui/popup.css
index ce96023..a463905 100644
--- a/src/ui/popup.css
+++ b/src/ui/popup.css
@@ -16,6 +16,7 @@ body {
#top a {
appearance: none !important;
+ webkit-appearance: none !important;
-moz-appearance: none !important;
width: 2em;
height: 2em;
@@ -119,8 +120,9 @@ body {
}
.hider-close {
- -moz-appearance: none;
appearance: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
color: black;
background: transparent;
padding: 0;
diff --git a/src/ui/popup.html b/src/ui/popup.html
index 517b233..5efc9e9 100644
--- a/src/ui/popup.html
+++ b/src/ui/popup.html
@@ -6,6 +6,8 @@
<title>NoScript Settings</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="popup.css" />
+<script src="/lib/UA.js"></script>
+<script src="/lib/browser-polyfill.js"></script>
<script src="/lib/include.js"></script>
<script src="/lib/log.js"></script>
<script src="/common/locale.js"></script>
diff --git a/src/ui/prompt.html b/src/ui/prompt.html
index 431b920..7761128 100644
--- a/src/ui/prompt.html
+++ b/src/ui/prompt.html
@@ -5,6 +5,8 @@
<title></title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="prompt.css" />
+<script src="/lib/UA.js"></script>
+<script src="/lib/browser-polyfill.js"></script>
<script src="/lib/include.js"></script>
<script src="/lib/log.js"></script>
<script src="/common/locale.js"></script>
diff --git a/src/ui/siteInfo.html b/src/ui/siteInfo.html
index 0cb24ec..c37fb53 100644
--- a/src/ui/siteInfo.html
+++ b/src/ui/siteInfo.html
@@ -1,5 +1,7 @@
<!DOCTYPE html>
<meta charset="utf-8">
+<script src="/lib/UA.js"></script>
+<script src="/lib/browser-polyfill.js"></script>
<script src="/lib/log.js"></script>
<script src="/lib/include.js"></script>
<script src="siteInfo.js"></script>
diff --git a/src/ui/toolbar.js b/src/ui/toolbar.js
index d2a2f6e..df3515e 100644
--- a/src/ui/toolbar.js
+++ b/src/ui/toolbar.js
@@ -87,7 +87,7 @@
}
UI.local.toolbarLayout = {
left, right,
- hidden: Array.map(document.querySelectorAll("#top > .hider > .icon"), el => el.id),
+ hidden: Array.from(document.querySelectorAll("#top > .hider > .icon")).map(el => el.id),
};
debug("%o", UI.local);
diff --git a/src/ui/ui-hc.css b/src/ui/ui-hc.css
index 2db0a5b..0978458 100644
--- a/src/ui/ui-hc.css
+++ b/src/ui/ui-hc.css
@@ -5,10 +5,14 @@ input {
}
input[type="radio"] {
+ appearance: radio !important;
+ -webkit-appearance: radio !important;
-moz-appearance: radio !important;
padding-right: .2em !important;
}
input[type="checkbox"] {
+ appearance: checkbox !important;
+ -webkit-appearance: checkbox !important;
-moz-appearance: checkbox !important;
}
diff --git a/src/ui/ui.css b/src/ui/ui.css
index f59646a..1d40030 100644
--- a/src/ui/ui.css
+++ b/src/ui/ui.css
@@ -3,6 +3,7 @@ body {
font-family: sans-serif;
font: -moz-use-system-font;
font-size: 12px;
+ min-width: 600px;
}
.mobile > body {
@@ -139,6 +140,8 @@ input[type="checkbox"] {
input.https-only {
font-size: 1em;
+ appearance: none;
+ -webkit-appearance: none;
-moz-appearance: none;
background: url(/img/ui-http64.png) no-repeat center;
background-size: 1.5em;
@@ -186,6 +189,8 @@ span.preset {
.presets input.preset {
font-size: 1em;
+ appearance: none;
+ -webkit-appearance: none;
-moz-appearance: none;
background: url(/img/ui-no64.png) no-repeat center left;
background-size: 1.5em;
@@ -264,6 +269,8 @@ input.preset:active, input.preset:focus, input.preset:hover {
}
button.options {
+ appearance: none;
+ -webkit-appearance: none;
-moz-appearance: none;
border: none;
background: none transparent;
@@ -276,6 +283,8 @@ button.options {
}
.preset .options {
+ appearance: none;
+ -webkit-appearance: none;
-moz-appearance: none;
border: 0;
@@ -315,6 +324,8 @@ input.preset[value="CUSTOM"] {
input.temp {
font-size: 1em;
+ appearance: none;
+ -webkit-appearance: none;
-moz-appearance: none;
margin: 0;
padding: 0;
diff --git a/src/xss/InjectionChecker.js b/src/xss/InjectionChecker.js
index 52cba27..a09938b 100644
--- a/src/xss/InjectionChecker.js
+++ b/src/xss/InjectionChecker.js
@@ -107,11 +107,10 @@ XSS.InjectionChecker = (async () => {
var bs = {
nq: new RegExp("[" + def + "]")
};
- Array.forEach("'\"`", // special treatment for quotes
- function(c) {
- bs[c] = new RegExp("[" + def + c + "]");
- }
- );
+ for (let c of ['"', '"', '`']) {
+ // special treatment for quotes
+ bs[c] = new RegExp("[" + def + c + "]");
+ }
delete this.breakStops;
return (this.breakStops = bs);
},
diff --git a/src/xss/XSS.js b/src/xss/XSS.js
index 18630fa..9c2fca3 100644
--- a/src/xss/XSS.js
+++ b/src/xss/XSS.js
@@ -113,6 +113,8 @@ var XSS = (() => {
return {
async start() {
+ if (!UA.isMozilla) return; // async webRequest is supported on Mozilla only
+
let {onBeforeRequest} = browser.webRequest;
let {xssScanRequestBody} = ns.sync;
if (xssScanRequestBody !== this.xssScanRequestBody) {