From 81f60022e840c1080ed269e6022c2e37bad76157 Mon Sep 17 00:00:00 2001
From: Thomas Rientjes <synzvato@protonmail.com>
Date: Tue, 7 Nov 2017 18:27:55 -0300
Subject: [PATCH] Refactor existing codebase

---
 .eslintignore                    |   6 ++
 .eslintrc                        | 176 +++++++++++++++++++++++++++++--
 audit/.eslintrc                  | 136 ++++++++++++++++++++++++
 audit/run.js                     |  69 ++++++------
 core/constants.js                |  58 ++++++++++
 core/interceptor.js              |  31 +++---
 core/main.js                     |   3 +-
 core/mappings.js                 |  84 +++++++--------
 core/request-analyzer.js         |  46 +++-----
 core/resources.js                |  84 +++++++--------
 core/state-manager.js            |  57 ++++------
 modules/internal/.gitkeep        |   0
 modules/internal/helpers.js      | 169 +++++++++++++++++++++++++++++
 pages/background/background.html |   3 +
 pages/options/options.html       |   3 +
 pages/options/options.js         | 109 +++++--------------
 pages/popup/popup.html           |   5 +-
 pages/popup/popup.js             | 175 +++++-------------------------
 18 files changed, 772 insertions(+), 442 deletions(-)
 create mode 100644 .eslintignore
 create mode 100644 audit/.eslintrc
 create mode 100644 core/constants.js
 delete mode 100644 modules/internal/.gitkeep
 create mode 100644 modules/internal/helpers.js

diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..65658bd
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,6 @@
+_locales
+audit
+icons
+modules/*
+!modules/internal
+resources
diff --git a/.eslintrc b/.eslintrc
index 978d0fa..b703040 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,11 +1,165 @@
-# Usage: `eslint --config .eslint.yml .`
-extends: "eslint:recommended"
-env:
-  browser: true
-rules:
-  indent:
-    - error
-    - 4
-  no-undef: 1 # warn
-  no-unused-vars: 1
-  no-console: 1
\ No newline at end of file
+{
+  "env": {
+    "browser": true,
+    "es6": true,
+    "webextensions": true
+  },
+  "extends": "eslint:recommended",
+  "globals": {
+    "Address": true,
+    "files": true,
+    "helpers": true,
+    "interceptor": true,
+    "mappings": true,
+    "requestAnalyzer": true,
+    "Resource": true,
+    "resources": true,
+    "Setting": true,
+    "stateManager": true,
+    "WebRequest": true,
+    "Whitelist": true
+  },
+  "overrides": {
+    "files": [
+      "core/constants.js",
+      "core/files.js",
+      "core/mappings.js",
+      "core/resources.js"
+    ],
+    "rules": {
+      "no-unused-vars": "off"
+    }
+  },
+  "rules": {
+    "array-bracket-newline": "error",
+    "array-bracket-spacing": "error",
+    "arrow-body-style": "error",
+    "arrow-parens": "error",
+    "arrow-spacing": "error",
+    "block-spacing": "error",
+    "brace-style": "error",
+    "camelcase": "error",
+    "comma-spacing": "error",
+    "comma-style": "error",
+    "computed-property-spacing": "error",
+    "consistent-this": "error",
+    "curly": "error",
+    "eol-last": "error",
+    "eqeqeq": "error",
+    "func-call-spacing": "error",
+    "function-paren-newline": "error",
+    "generator-star-spacing": "error",
+    "indent": [
+      "error",
+      4
+    ],
+    "key-spacing": "error",
+    "keyword-spacing": "error",
+    "linebreak-style": [
+      "error",
+      "unix"
+    ],
+    "new-cap": "error",
+    "new-parens": "error",
+    "no-array-constructor": "error",
+    "no-bitwise": "error",
+    "no-confusing-arrow": "error",
+    "no-continue": "error",
+    "no-duplicate-imports": "error",
+    "no-eval": "error",
+    "no-extend-native": "error",
+    "no-implicit-coercion": "error",
+    "no-implied-eval": "error",
+    "no-invalid-this": "error",
+    "no-iterator": "error",
+    "no-label-var": "error",
+    "no-labels": "error",
+    "no-lone-blocks": "error",
+    "no-loop-func": "error",
+    "no-multi-spaces": "error",
+    "no-multi-str": "error",
+    "no-multiple-empty-lines": [
+      "error", {
+        "max": 1,
+        "maxEOF": 1,
+        "maxBOF": 0
+      }
+    ],
+    "max-len": [
+      "error", {
+        "code": 120
+      }
+    ],
+    "no-negated-condition": "error",
+    "no-new": "error",
+    "no-new-func": "error",
+    "no-new-object": "error",
+    "no-new-wrappers": "error",
+    "no-octal-escape": "error",
+    "no-proto": "error",
+    "no-return-assign": "error",
+    "no-return-await": "error",
+    "no-script-url": "error",
+    "no-self-compare": "error",
+    "no-sequences": "error",
+    "no-shadow-restricted-names": "error",
+    "no-tabs": "error",
+    "no-ternary": "error",
+    "no-throw-literal": "error",
+    "no-trailing-spaces": "error",
+    "no-undef-init": "error",
+    "no-unmodified-loop-condition": "error",
+    "no-unused-expressions": "error",
+    "no-use-before-define": "error",
+    "no-useless-call": "error",
+    "no-useless-concat": "error",
+    "no-useless-constructor": "error",
+    "no-useless-rename": "error",
+    "no-useless-return": "error",
+    "no-void": "error",
+    "no-warning-comments": "warn",
+    "no-whitespace-before-property": "error",
+    "no-with": "error",
+    "object-curly-spacing": "error",
+    "object-shorthand": [
+      "error",
+      "consistent-as-needed"
+    ],
+    "operator-assignment": "error",
+    "operator-linebreak": "error",
+    "prefer-numeric-literals": "error",
+    "prefer-promise-reject-errors": "error",
+    "quote-props": "error",
+    "prefer-rest-params": "error",
+    "prefer-spread": "error",
+    "prefer-template": "error",
+    "quotes": [
+      "error",
+      "single"
+    ],
+    "rest-spread-spacing": "error",
+    "require-await": "error",
+    "semi": "error",
+    "semi-spacing": "error",
+    "semi-style": "error",
+    "space-before-blocks": "error",
+    "space-before-function-paren": "error",
+    "space-in-parens": "error",
+    "space-infix-ops": "error",
+    "space-unary-ops": "error",
+    "spaced-comment": "error",
+    "strict": [
+      "error",
+      "global"
+    ],
+    "switch-colon-spacing": "error",
+    "symbol-description": "error",
+    "template-curly-spacing": "error",
+    "template-tag-spacing": "error",
+    "unicode-bom": "error",
+    "vars-on-top": "error",
+    "wrap-regex": "error",
+    "yield-star-spacing": "error",
+    "yoda": "error"
+  }
+}
diff --git a/audit/.eslintrc b/audit/.eslintrc
new file mode 100644
index 0000000..70f48be
--- /dev/null
+++ b/audit/.eslintrc
@@ -0,0 +1,136 @@
+{
+  "env": {
+    "es6": true,
+    "node": true
+  },
+  "extends": "eslint:recommended",
+  "parserOptions": {
+    "sourceType": "module"
+  },
+  "rules": {
+    "array-bracket-newline": "error",
+    "array-bracket-spacing": "error",
+    "arrow-body-style": "error",
+    "arrow-parens": "error",
+    "arrow-spacing": "error",
+    "block-spacing": "error",
+    "brace-style": "error",
+    "camelcase": "error",
+    "comma-spacing": "error",
+    "comma-style": "error",
+    "computed-property-spacing": "error",
+    "consistent-this": "error",
+    "curly": "error",
+    "eol-last": "error",
+    "eqeqeq": "error",
+    "func-call-spacing": "error",
+    "function-paren-newline": "error",
+    "generator-star-spacing": "error",
+    "indent": [
+      "error",
+      4
+    ],
+    "key-spacing": "error",
+    "keyword-spacing": "error",
+    "linebreak-style": [
+      "error",
+      "unix"
+    ],
+    "new-cap": "error",
+    "new-parens": "error",
+    "no-array-constructor": "error",
+    "no-bitwise": "error",
+    "no-confusing-arrow": "error",
+    "no-console": "off",
+    "no-continue": "error",
+    "no-duplicate-imports": "error",
+    "no-eval": "error",
+    "no-extend-native": "error",
+    "no-implicit-coercion": "error",
+    "no-implied-eval": "error",
+    "no-invalid-this": "error",
+    "no-iterator": "error",
+    "no-label-var": "error",
+    "no-labels": "error",
+    "no-lone-blocks": "error",
+    "no-loop-func": "error",
+    "no-multi-spaces": "error",
+    "no-multi-str": "error",
+    "no-multiple-empty-lines": [
+      "error", {
+        "max": 1,
+        "maxEOF": 1,
+        "maxBOF": 0
+      }
+    ],
+    "no-negated-condition": "error",
+    "no-new": "error",
+    "no-new-func": "error",
+    "no-new-object": "error",
+    "no-new-wrappers": "error",
+    "no-octal-escape": "error",
+    "no-proto": "error",
+    "no-return-assign": "error",
+    "no-return-await": "error",
+    "no-script-url": "error",
+    "no-self-compare": "error",
+    "no-sequences": "error",
+    "no-shadow-restricted-names": "error",
+    "no-tabs": "error",
+    "no-ternary": "error",
+    "no-trailing-spaces": "error",
+    "no-undef-init": "error",
+    "no-unmodified-loop-condition": "error",
+    "no-unused-expressions": "error",
+    "no-use-before-define": "error",
+    "no-useless-call": "error",
+    "no-useless-concat": "error",
+    "no-useless-constructor": "error",
+    "no-useless-rename": "error",
+    "no-useless-return": "error",
+    "no-void": "error",
+    "no-warning-comments": "warn",
+    "no-whitespace-before-property": "error",
+    "no-with": "error",
+    "object-curly-spacing": "error",
+    "object-shorthand": [
+      "error",
+      "consistent-as-needed"
+    ],
+    "operator-assignment": "error",
+    "operator-linebreak": "error",
+    "prefer-numeric-literals": "error",
+    "prefer-promise-reject-errors": "error",
+    "prefer-rest-params": "error",
+    "prefer-spread": "error",
+    "prefer-template": "error",
+    "quote-props": "error",
+    "quotes": [
+      "error",
+      "single"
+    ],
+    "require-await": "error",
+    "rest-spread-spacing": "error",
+    "semi": "error",
+    "semi-spacing": "error",
+    "semi-style": "error",
+    "space-before-blocks": "error",
+    "space-before-function-paren": "error",
+    "space-in-parens": "error",
+    "space-infix-ops": "error",
+    "space-unary-ops": "error",
+    "spaced-comment": "error",
+    "strict": [
+      "error",
+      "global"
+    ],
+    "switch-colon-spacing": "error",
+    "symbol-description": "error",
+    "template-curly-spacing": "error",
+    "template-tag-spacing": "error",
+    "unicode-bom": "error",
+    "wrap-regex": "error",
+    "yield-star-spacing": "error",
+    "yoda": "error"
+  }
+}
diff --git a/audit/run.js b/audit/run.js
index add84d9..eafa53f 100644
--- a/audit/run.js
+++ b/audit/run.js
@@ -1,4 +1,3 @@
-/* eslint-env node */
 /**
  * Resource Audit Script
  * Belongs to Decentraleyes.
@@ -38,11 +37,11 @@ var resourceAmount = 0;
  * Functions
  */
 
-function _fetchLocalResourcePaths(folderPath) {
+function _fetchLocalResourcePaths (folderPath) {
 
     fileSystem.readdirSync(folderPath).forEach(function (resourceName) {
 
-        var resourcePath = folderPath + '/' + resourceName;
+        var resourcePath = `${folderPath}/${resourceName}`;
         var resourceStatistics = fileSystem.statSync(resourcePath);
 
         if (resourceStatistics && resourceStatistics.isDirectory()) {
@@ -56,7 +55,7 @@ function _fetchLocalResourcePaths(folderPath) {
     return localResourcePaths;
 }
 
-function _getLocalResourceContents(fileLocation, callback) {
+function _getLocalResourceContents (fileLocation, callback) {
 
     fileSystem.exists(fileLocation, function (exists) {
 
@@ -82,16 +81,16 @@ function _getLocalResourceContents(fileLocation, callback) {
     });
 }
 
-function _getRemoteResourceContents(remoteResourceRoute, callback) {
+function _getRemoteResourceContents (remoteResourceRoute, callback) {
 
-    var resourceURL = 'https://ajax.googleapis.com/ajax/libs/' + remoteResourceRoute;
+    var resourceURL = `https://ajax.googleapis.com/ajax/libs/${remoteResourceRoute}`;
 
     https.get(resourceURL, function (response) {
 
         var resourceContents = '';
 
         response.on('data', function (chunk) {
-            resourceContents = resourceContents + chunk;
+            resourceContents += chunk;
         });
 
         response.on('end', function () {
@@ -102,20 +101,20 @@ function _getRemoteResourceContents(remoteResourceRoute, callback) {
 
             } else {
 
-                resourceURL = 'https://cdnjs.cloudflare.com/ajax/libs/' + remoteResourceRoute;
+                resourceURL = `https://cdnjs.cloudflare.com/ajax/libs/${remoteResourceRoute}`;
 
                 https.get(resourceURL, function (response) {
 
                     resourceContents = '';
 
                     response.on('data', function (chunk) {
-                        resourceContents = resourceContents + chunk;
+                        resourceContents += chunk;
                     });
 
                     response.on('end', function () {
 
                         if (response.statusCode !== 200) {
-                            throw 'Error: Resource ' + remoteResourceRoute + ' could not be fetched.';
+                            throw `Error: Resource ${remoteResourceRoute} could not be fetched.`;
                         }
 
                         callback(resourceContents, resourceURL);
@@ -130,7 +129,7 @@ function _getRemoteResourceContents(remoteResourceRoute, callback) {
     });
 }
 
-function _hashFileContents(fileContents) {
+function _hashFileContents (fileContents) {
 
     var hash;
 
@@ -143,7 +142,27 @@ function _hashFileContents(fileContents) {
     return hash.read();
 }
 
-function _compareResources(localResourceContents, remoteResourceContents, URL) {
+function _showCompletedMessage () {
+
+    console.log();
+    console.log('       *** FILE INTEGRITY CHECKS COMPLETED');
+    console.log(`       *** ${resourceAmount}/${resourceAmount} RESOURCES WERE ANALYZED`);
+    console.log();
+}
+
+function _incrementComparedResourceAmount () {
+
+    comparedResourceAmount++;
+
+    if (comparedResourceAmount === resourceAmount) {
+
+        setTimeout(function () {
+            _showCompletedMessage();
+        }, 500);
+    }
+}
+
+function _compareResources (localResourceContents, remoteResourceContents, URL) {
 
     var hasSourceMappingURL = sourceMappingURL.existsIn(remoteResourceContents);
     var sourceMappingNotice = '[ ] REMOTE RESOURCE HAD SOURCE MAPPING URL';
@@ -156,8 +175,8 @@ function _compareResources(localResourceContents, remoteResourceContents, URL) {
     var localResourceHash = _hashFileContents(localResourceContents);
     var remoteResourceHash = _hashFileContents(remoteResourceContents);
 
-    console.log('RESOURCE HASH (SHA512): ' + localResourceHash);
-    console.log('RESOURCE HASH (SHA512): ' + remoteResourceHash);
+    console.log(`RESOURCE HASH (SHA512): ${localResourceHash}`);
+    console.log(`RESOURCE HASH (SHA512): ${remoteResourceHash}`);
 
     var fileHashesMatch = (localResourceHash === remoteResourceHash);
 
@@ -174,26 +193,6 @@ function _compareResources(localResourceContents, remoteResourceContents, URL) {
     _incrementComparedResourceAmount();
 }
 
-function _showCompletedMessage() {
-
-    console.log();
-    console.log('       *** FILE INTEGRITY CHECKS COMPLETED');
-    console.log('       *** ' + resourceAmount + '/' + resourceAmount + ' RESOURCES WERE ANALYZED');
-    console.log();
-}
-
-function _incrementComparedResourceAmount() {
-
-    comparedResourceAmount++;
-
-    if (comparedResourceAmount === resourceAmount) {
-
-        setTimeout(function () {
-            _showCompletedMessage();
-        }, 500);
-    }
-}
-
 /**
  * Initializations
  */
@@ -205,7 +204,7 @@ resourceAmount = localResourcePaths.length;
  * Script
  */
 
-localResourcePaths.forEach(function (resourcePath, index) {
+localResourcePaths.forEach(function (resourcePath) {
 
     var resourceRoute = resourcePath.substr(localResourceLocationLength + 1);
     resourceRoute = resourceRoute.substring(0, resourceRoute.length - 4);
diff --git a/core/constants.js b/core/constants.js
new file mode 100644
index 0000000..07ddd42
--- /dev/null
+++ b/core/constants.js
@@ -0,0 +1,58 @@
+/**
+ * Global Constants
+ * Belongs to Decentraleyes.
+ *
+ * @author      Thomas Rientjes
+ * @since       2017-10-27
+ * @license     MPL 2.0
+ *
+ * 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';
+
+/**
+ * Constants
+ */
+
+const Address = {
+    'ANY': '*://*/*',
+    'ANY_PATH': '/*',
+    'ANY_PROTOCOL': '*://',
+    'DOMAIN_EXPRESSION': /:\/\/(.[^/]+)(.*)/,
+    'EXAMPLE': 'example.org',
+    'HTTP_EXPRESSION': /^http?:\/\//,
+    'HTTPS': 'https://',
+    'WWW_PREFIX': 'www.',
+    'WWW_PREFIX_LENGTH': 4
+};
+
+const Resource = {
+    'MAPPING_EXPRESSION': /\.map$/i,
+    'VERSION_EXPRESSION': /(?:\d{1,2}\.){1,3}\d{1,2}/,
+    'VERSION_PLACEHOLDER': '{version}'
+};
+
+const Setting = {
+    'AMOUNT_INJECTED': 'amountInjected',
+    'BLOCK_MISSING': 'blockMissing',
+    'DISABLE_PREFETCH': 'disablePrefetch',
+    'SHOW_ICON_BADGE': 'showIconBadge',
+    'STRIP_METADATA': 'stripMetadata',
+    'WHITELISTED_DOMAINS': 'whitelistedDomains'
+};
+
+const WebRequest = {
+    'GET': 'GET',
+    'BLOCKING': 'blocking',
+    'HEADERS': 'requestHeaders',
+    'ORIGIN_HEADER': 'Origin',
+    'REFERER_HEADER': 'Referer'
+};
+
+const Whitelist = {
+    'TRIM_EXPRESSION': /^;+|;+$/g,
+    'VALUE_SEPARATOR': ';'
+};
diff --git a/core/interceptor.js b/core/interceptor.js
index 57ce92a..e9b28ce 100644
--- a/core/interceptor.js
+++ b/core/interceptor.js
@@ -19,12 +19,6 @@
 
 var interceptor = {};
 
-/**
- * Constants
- */
-
-const HTTP_EXPRESSION = /^http?:\/\//;
-
 /**
  * Public Methods
  */
@@ -43,24 +37,28 @@ interceptor.handleRequest = function (requestDetails, tabIdentifier, tab) {
     }
 
     try {
-        tabDomain = tab.url.match(WEB_DOMAIN_EXPRESSION)[1];
+        tabDomain = tab.url.match(Address.DOMAIN_EXPRESSION)[1];
         tabDomain = requestAnalyzer._normalizeDomain(tabDomain);
     } catch (exception) {
-        tabDomain = 'example.org';
+        tabDomain = Address.EXAMPLE;
     }
 
     // Temporary list of undetectable tainted domains.
     let undetectableTaintedDomains = {
         'cdnjs.com': true,
         'dropbox.com': true,
+        'glowing-bear.org': true,
+        'jadi.sk': true,
         'minigames.mail.ru': true,
         'report-uri.io': true,
+        'scotthelme.co.uk': true,
         'securityheaders.io': true,
         'stefansundin.github.io': true,
-        'udacity.com': true
+        'udacity.com': true,
+        'yourvotematters.co.uk': true
     };
 
-    if (undetectableTaintedDomains[tabDomain] || /yandex\./.test(tabDomain)) {
+    if (undetectableTaintedDomains[tabDomain] || (/yandex\./).test(tabDomain)) {
 
         if (tabDomain !== 'yandex.ru') {
             return interceptor._handleMissingCandidate(requestDetails.url);
@@ -79,8 +77,7 @@ interceptor.handleRequest = function (requestDetails, tabIdentifier, tab) {
     }
 
     stateManager.requests[requestDetails.requestId] = {
-        'tabIdentifier': tabIdentifier,
-        'targetDetails': targetDetails
+        tabIdentifier, targetDetails
     };
 
     return {
@@ -101,12 +98,12 @@ interceptor._handleMissingCandidate = function (requestUrl) {
         };
     }
 
-    if (requestUrl.match(HTTP_EXPRESSION)) {
+    if (requestUrl.match(Address.HTTP_EXPRESSION)) {
 
-        requestUrl = requestUrl.replace(HTTP_EXPRESSION, 'https://');
+        let secureRequestUrl = requestUrl.replace(Address.HTTP_EXPRESSION, Address.HTTPS);
 
         return {
-            'redirectUrl': requestUrl
+            'redirectUrl': secureRequestUrl
         };
 
     } else {
@@ -119,7 +116,7 @@ interceptor._handleMissingCandidate = function (requestUrl) {
 
 interceptor._handleStorageChanged = function (changes) {
 
-    if ('blockMissing' in changes) {
+    if (Setting.BLOCK_MISSING in changes) {
         interceptor.blockMissing = changes.blockMissing.newValue;
     }
 };
@@ -131,7 +128,7 @@ interceptor._handleStorageChanged = function (changes) {
 interceptor.amountInjected = 0;
 interceptor.blockMissing = false;
 
-chrome.storage.local.get(['amountInjected', 'blockMissing'], function (items) {
+chrome.storage.local.get([Setting.AMOUNT_INJECTED, Setting.BLOCK_MISSING], function (items) {
 
     interceptor.amountInjected = items.amountInjected || 0;
     interceptor.blockMissing = items.blockMissing || false;
diff --git a/core/main.js b/core/main.js
index f1d49bc..bbb3e6d 100644
--- a/core/main.js
+++ b/core/main.js
@@ -54,7 +54,8 @@ main._showReleaseNotes = function (details) {
 
     let location = chrome.extension.getURL('pages/welcome/welcome.html');
 
-    if (details.reason === 'install' || details.reason === 'update') {
+    if (details.reason === chrome.runtime.OnInstalledReason.INSTALL ||
+        details.reason === chrome.runtime.OnInstalledReason.UPDATE) {
 
         if (details.temporary !== true) {
 
diff --git a/core/mappings.js b/core/mappings.js
index fca471e..3aac795 100644
--- a/core/mappings.js
+++ b/core/mappings.js
@@ -37,40 +37,40 @@ var mappings = {
 
             // Common Shorthand Notations [Deprecated]
             'dojo/1/dojo/dojo.': {
-                path: 'resources/dojo/1.6.1/dojo/dojo.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/dojo/1.6.1/dojo/dojo.js.dec',
+                'type': 'application/javascript'
             },
             'jquery/1/jquery.': {
-                path: 'resources/jquery/1.11.1/jquery.min.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/jquery/1.11.1/jquery.min.js.dec',
+                'type': 'application/javascript'
             },
             'jqueryui/1/jquery-ui.js': {
-                path: 'resources/jqueryui/1.10.4/jquery-ui.min.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/jqueryui/1.10.4/jquery-ui.min.js.dec',
+                'type': 'application/javascript'
             },
             'jqueryui/1/jquery-ui.min.js': {
-                path: 'resources/jqueryui/1.10.4/jquery-ui.min.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/jqueryui/1.10.4/jquery-ui.min.js.dec',
+                'type': 'application/javascript'
             },
             'mootools/1/mootools-yui-compressed.': {
-                path: 'resources/mootools/1.1.2/mootools-yui-compressed.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/mootools/1.1.2/mootools-yui-compressed.js.dec',
+                'type': 'application/javascript'
             },
             'prototype/1/prototype.': {
-                path: 'resources/prototype/1.7.1.0/prototype.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/prototype/1.7.1.0/prototype.js.dec',
+                'type': 'application/javascript'
             },
             'scriptaculous/1/scriptaculous.': {
-                path: 'resources/scriptaculous/1.9.0/scriptaculous.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/scriptaculous/1.9.0/scriptaculous.js.dec',
+                'type': 'application/javascript'
             },
             'swfobject/2/swfobject.': {
-                path: 'resources/swfobject/2.2/swfobject.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/swfobject/2.2/swfobject.js.dec',
+                'type': 'application/javascript'
             },
             'webfont/1/webfont.': {
-                path: 'resources/webfont/1.5.18/webfont.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/webfont/1.5.18/webfont.js.dec',
+                'type': 'application/javascript'
             }
         }
     },
@@ -118,52 +118,52 @@ var mappings = {
 
             // Common Shorthand Notations [Deprecated]
             'jquery-latest.': {
-                path: 'resources/jquery/1.11.1/jquery.min.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/jquery/1.11.1/jquery.min.js.dec',
+                'type': 'application/javascript'
             },
             'jquery.': {
-                path: 'resources/jquery/1.11.1/jquery.min.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/jquery/1.11.1/jquery.min.js.dec',
+                'type': 'application/javascript'
             },
             'jquery-1.3.min.js': {
-                path: 'resources/jquery/1.3.0/jquery.min.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/jquery/1.3.0/jquery.min.js.dec',
+                'type': 'application/javascript'
             },
             'jquery-1.3.js': {
-                path: 'resources/jquery/1.3.0/jquery.min.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/jquery/1.3.0/jquery.min.js.dec',
+                'type': 'application/javascript'
             },
             'jquery-1.4.min.js': {
-                path: 'resources/jquery/1.4.0/jquery.min.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/jquery/1.4.0/jquery.min.js.dec',
+                'type': 'application/javascript'
             },
             'jquery-1.4.js': {
-                path: 'resources/jquery/1.4.0/jquery.min.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/jquery/1.4.0/jquery.min.js.dec',
+                'type': 'application/javascript'
             },
             'jquery-1.5.min.js': {
-                path: 'resources/jquery/1.5.0/jquery.min.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/jquery/1.5.0/jquery.min.js.dec',
+                'type': 'application/javascript'
             },
             'jquery-1.5.js': {
-                path: 'resources/jquery/1.5.0/jquery.min.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/jquery/1.5.0/jquery.min.js.dec',
+                'type': 'application/javascript'
             },
             'jquery-1.6.min.js': {
-                path: 'resources/jquery/1.6.0/jquery.min.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/jquery/1.6.0/jquery.min.js.dec',
+                'type': 'application/javascript'
             },
             'jquery-1.6.js': {
-                path: 'resources/jquery/1.6.0/jquery.min.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/jquery/1.6.0/jquery.min.js.dec',
+                'type': 'application/javascript'
             },
             'jquery-1.7.min.js': {
-                path: 'resources/jquery/1.7.0/jquery.min.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/jquery/1.7.0/jquery.min.js.dec',
+                'type': 'application/javascript'
             },
             'jquery-1.7.js': {
-                path: 'resources/jquery/1.7.0/jquery.min.js.dec',
-                type: 'application/javascript'
+                'path': 'resources/jquery/1.7.0/jquery.min.js.dec',
+                'type': 'application/javascript'
             }
         }
     },
diff --git a/core/request-analyzer.js b/core/request-analyzer.js
index 513a099..d84a327 100644
--- a/core/request-analyzer.js
+++ b/core/request-analyzer.js
@@ -19,17 +19,6 @@
 
 var requestAnalyzer = {};
 
-/**
- * Constants
- */
-
-const MAPPING_FILE_EXPRESSION = new RegExp('\.map$', 'i');
-const VERSION_EXPRESSION = /(?:\d{1,2}\.){1,3}\d{1,2}/;
-const VERSION_PLACEHOLDER = '{version}';
-const WEB_DOMAIN_EXPRESSION = /:\/\/(.[^\/]+)(.*)/;
-const WEB_PREFIX_VALUE = 'www.';
-const WEB_PREFIX_LENGTH = WEB_PREFIX_VALUE.length;
-
 /**
  * Public Methods
  */
@@ -39,9 +28,9 @@ requestAnalyzer.isValidCandidate = function (requestDetails, tabDetails) {
     let initiatorHost;
 
     try {
-        initiatorHost = tabDetails.url.match(WEB_DOMAIN_EXPRESSION)[1];
+        initiatorHost = tabDetails.url.match(Address.DOMAIN_EXPRESSION)[1];
     } catch (exception) {
-        initiatorHost = 'example.org';
+        initiatorHost = Address.EXAMPLE;
     }
 
     if (initiatorHost && requestAnalyzer.whitelistedDomains[requestAnalyzer._normalizeDomain(initiatorHost)]) {
@@ -49,21 +38,21 @@ requestAnalyzer.isValidCandidate = function (requestDetails, tabDetails) {
     }
 
     // Only requests of type GET can be valid candidates.
-    return requestDetails.method === 'GET';
+    return requestDetails.method === WebRequest.GET;
 };
 
 requestAnalyzer.getLocalTarget = function (requestDetails) {
 
     let destinationHost, destinationPath, hostMappings, basePath, resourceMappings;
 
-    destinationHost = requestDetails.url.match(WEB_DOMAIN_EXPRESSION)[1];
-    destinationPath = requestDetails.url.match(WEB_DOMAIN_EXPRESSION)[2];
+    destinationHost = requestDetails.url.match(Address.DOMAIN_EXPRESSION)[1];
+    destinationPath = requestDetails.url.match(Address.DOMAIN_EXPRESSION)[2];
 
     // Use the proper mappings for the targeted host.
     hostMappings = mappings[destinationHost];
 
     // Resource mapping files are never locally available.
-    if (MAPPING_FILE_EXPRESSION.test(destinationPath)) {
+    if (Resource.MAPPING_EXPRESSION.test(destinationPath)) {
         return false;
     }
 
@@ -96,12 +85,12 @@ requestAnalyzer._matchBasePath = function (hostMappings, channelPath) {
 
 requestAnalyzer._findLocalTarget = function (resourceMappings, basePath, channelHost, channelPath) {
 
-    var resourcePath, versionNumber, resourcePattern;
+    let resourcePath, versionNumber, resourcePattern;
 
     resourcePath = channelPath.replace(basePath, '');
 
-    versionNumber = resourcePath.match(VERSION_EXPRESSION);
-    resourcePattern = resourcePath.replace(versionNumber, VERSION_PLACEHOLDER);
+    versionNumber = resourcePath.match(Resource.VERSION_EXPRESSION);
+    resourcePattern = resourcePath.replace(versionNumber, Resource.VERSION_PLACEHOLDER);
 
     for (let resourceMold of Object.keys(resourceMappings)) {
 
@@ -110,15 +99,15 @@ requestAnalyzer._findLocalTarget = function (resourceMappings, basePath, channel
             let targetPath, version;
 
             targetPath = resourceMappings[resourceMold].path;
-            targetPath = targetPath.replace(VERSION_PLACEHOLDER, versionNumber);
+            targetPath = targetPath.replace(Resource.VERSION_PLACEHOLDER, versionNumber);
 
-            version = versionNumber && versionNumber[0] || targetPath.match(VERSION_EXPRESSION);
+            version = versionNumber && versionNumber[0] || targetPath.match(Resource.VERSION_EXPRESSION);
 
             // Prepare and return a local target.
             return {
-                source: channelHost,
-                version: version,
-                path: targetPath
+                'source': channelHost,
+                'version': version,
+                'path': targetPath
             };
         }
     }
@@ -128,8 +117,7 @@ requestAnalyzer._findLocalTarget = function (resourceMappings, basePath, channel
 
 requestAnalyzer._applyWhitelistedDomains = function () {
 
-    //noinspection JSUnresolvedVariable
-    chrome.storage.local.get('whitelistedDomains', function (items) {
+    chrome.storage.local.get(Setting.WHITELISTED_DOMAINS, function (items) {
         requestAnalyzer.whitelistedDomains = items.whitelistedDomains || {};
     });
 };
@@ -138,8 +126,8 @@ requestAnalyzer._normalizeDomain = function (domain) {
 
     domain = domain.toLowerCase().trim();
 
-    if (domain.startsWith(WEB_PREFIX_VALUE)) {
-        domain = domain.slice(WEB_PREFIX_LENGTH);
+    if (domain.startsWith(Address.WWW_PREFIX)) {
+        domain = domain.slice(Address.WWW_PREFIX_LENGTH);
     }
 
     return domain;
diff --git a/core/resources.js b/core/resources.js
index 76b8d06..cbc7b77 100644
--- a/core/resources.js
+++ b/core/resources.js
@@ -20,73 +20,73 @@
 var resources = {
 
     // AngularJS
-    angular: {
-        path: 'resources/angularjs/{version}/angular.min.js.dec',
-        type: 'application/javascript'
+    'angular': {
+        'path': 'resources/angularjs/{version}/angular.min.js.dec',
+        'type': 'application/javascript'
     },
     // Backbone.js
-    backbone: {
-        path: 'resources/backbone.js/{version}/backbone-min.js.dec',
-        type: 'application/javascript'
+    'backbone': {
+        'path': 'resources/backbone.js/{version}/backbone-min.js.dec',
+        'type': 'application/javascript'
     },
     // Dojo
-    dojo: {
-        path: 'resources/dojo/{version}/dojo/dojo.js.dec',
-        type: 'application/javascript'
+    'dojo': {
+        'path': 'resources/dojo/{version}/dojo/dojo.js.dec',
+        'type': 'application/javascript'
     },
     // Ember.js
-    ember: {
-        path: 'resources/ember.js/{version}/ember.min.js.dec',
-        type: 'application/javascript'
+    'ember': {
+        'path': 'resources/ember.js/{version}/ember.min.js.dec',
+        'type': 'application/javascript'
     },
     // Ext Core
-    extCore: {
-        path: 'resources/ext-core/{version}/ext-core.js.dec',
-        type: 'application/javascript'
+    'extCore': {
+        'path': 'resources/ext-core/{version}/ext-core.js.dec',
+        'type': 'application/javascript'
     },
     // jQuery
-    jQuery: {
-        path: 'resources/jquery/{version}/jquery.min.js.dec',
-        type: 'application/javascript'
+    'jQuery': {
+        'path': 'resources/jquery/{version}/jquery.min.js.dec',
+        'type': 'application/javascript'
     },
     // jQuery UI
-    jQueryUI: {
-        path: 'resources/jqueryui/{version}/jquery-ui.min.js.dec',
-        type: 'application/javascript'
+    'jQueryUI': {
+        'path': 'resources/jqueryui/{version}/jquery-ui.min.js.dec',
+        'type': 'application/javascript'
     },
     // Modernizr
-    modernizr: {
-        path: 'resources/modernizr/{version}/modernizr.min.js.dec',
-        type: 'application/javascript'
+    'modernizr': {
+        'path': 'resources/modernizr/{version}/modernizr.min.js.dec',
+        'type': 'application/javascript'
     },
     // MooTools
-    mootools: {
-        path: 'resources/mootools/{version}/mootools-yui-compressed.js.dec',
-        type: 'application/javascript'
+    'mootools': {
+        'path': 'resources/mootools/{version}/mootools-yui-compressed.js.dec',
+        'type': 'application/javascript'
     },
     // Prototype
-    prototypeJS: {
-        path: 'resources/prototype/{version}/prototype.js.dec',
-        type: 'application/javascript'
+    'prototypeJS': {
+        'path': 'resources/prototype/{version}/prototype.js.dec',
+        'type': 'application/javascript'
     },
     // Scriptaculous
-    scriptaculous: {
-        path: 'resources/scriptaculous/{version}/scriptaculous.js.dec',
-        type: 'application/javascript'
+    'scriptaculous': {
+        'path': 'resources/scriptaculous/{version}/scriptaculous.js.dec',
+        'type': 'application/javascript'
     },
     // SWFObject
-    swfobject: {
-        path: 'resources/swfobject/{version}/swfobject.js.dec',
-        type: 'application/javascript'
+    'swfobject': {
+        'path': 'resources/swfobject/{version}/swfobject.js.dec',
+        'type': 'application/javascript'
     },
     // Underscore.js
-    underscore: {
-        path: 'resources/underscore.js/{version}/underscore-min.js.dec',
-        type: 'application/javascript'
+    'underscore': {
+        'path': 'resources/underscore.js/{version}/underscore-min.js.dec',
+        'type': 'application/javascript'
     },
     // Web Font Loader
-    webfont: {
-        path: 'resources/webfont/{version}/webfont.js.dec',
-        type: 'application/javascript'
+    'webfont': {
+        'path': 'resources/webfont/{version}/webfont.js.dec',
+        'type': 'application/javascript'
     }
 };
diff --git a/core/state-manager.js b/core/state-manager.js
index e186202..48fecbc 100644
--- a/core/state-manager.js
+++ b/core/state-manager.js
@@ -19,15 +19,6 @@
 
 var stateManager = {};
 
-/**
- * Constants
- */
-
-const BLOCKING_ACTION = 'blocking';
-const HOST_PREFIX = '*://';
-const HOST_SUFFIX = '/*';
-const REQUEST_HEADERS = 'requestHeaders';
-
 /**
  * Public Methods
  */
@@ -47,22 +38,22 @@ stateManager.registerInjection = function (tabIdentifier, injection) {
         if (injectionCount > 0) {
 
             chrome.browserAction.setBadgeText({
-                tabId: tabIdentifier,
-                text: injectionCount.toString()
+                'tabId': tabIdentifier,
+                'text': injectionCount.toString()
             });
 
         } else {
 
             chrome.browserAction.setBadgeText({
-                tabId: tabIdentifier,
-                text: ''
+                'tabId': tabIdentifier,
+                'text': ''
             });
         }
     }
 
     if (isNaN(interceptor.amountInjected)) {
 
-        chrome.storage.local.get('amountInjected', function (items) {
+        chrome.storage.local.get(Setting.AMOUNT_INJECTED, function (items) {
 
             interceptor.amountInjected = items.amountInjected;
 
@@ -126,7 +117,7 @@ stateManager._createTab = function (tab) {
         let tab = stateManager.tabs[tabIdentifier].details || {};
         return interceptor.handleRequest(requestDetails, tabIdentifier, tab);
     
-    }, requestFilters, [BLOCKING_ACTION]);
+    }, requestFilters, [WebRequest.BLOCKING]);
 };
 
 stateManager._removeTab = function (tabIdentifier) {
@@ -147,8 +138,8 @@ stateManager._updateTab = function (details) {
     if (stateManager.showIconBadge === true) {
 
         chrome.browserAction.setBadgeText({
-            tabId: tabIdentifier,
-            text: ''
+            'tabId': tabIdentifier,
+            'text': ''
         });
     }
 
@@ -161,9 +152,9 @@ stateManager._stripMetadata = function (requestDetails) {
 
     for (let i = 0; i < requestDetails.requestHeaders.length; ++i) {
 
-        if (requestDetails.requestHeaders[i].name === 'Origin') {
+        if (requestDetails.requestHeaders[i].name === WebRequest.ORIGIN_HEADER) {
             requestDetails.requestHeaders.splice(i--, 1);
-        } else if (requestDetails.requestHeaders[i].name === 'Referer') {
+        } else if (requestDetails.requestHeaders[i].name === WebRequest.REFERER_HEADER) {
             requestDetails.requestHeaders.splice(i--, 1);
         }
     }
@@ -176,7 +167,7 @@ stateManager._stripMetadata = function (requestDetails) {
 stateManager._handleStorageChanged = function (changes) {
 
     if ('showIconBadge' in changes) {
-        
+
         stateManager.showIconBadge = changes.showIconBadge.newValue;
 
         if (changes.showIconBadge.newValue !== true) {
@@ -195,13 +186,13 @@ stateManager._handleStorageChanged = function (changes) {
 
         onBeforeSendHeaders.removeListener(stateManager._stripMetadata, {
             'urls': stateManager.validHosts
-        }, [BLOCKING_ACTION, REQUEST_HEADERS]);
+        }, [WebRequest.BLOCKING, WebRequest.HEADERS]);
 
         if (changes.stripMetadata.newValue !== false) {
-            
+
             onBeforeSendHeaders.addListener(stateManager._stripMetadata, {
                 'urls': stateManager.validHosts
-            }, [BLOCKING_ACTION, REQUEST_HEADERS]);
+            }, [WebRequest.BLOCKING, WebRequest.HEADERS]);
         }
     }
 };
@@ -209,8 +200,8 @@ stateManager._handleStorageChanged = function (changes) {
 stateManager._removeIconBadgeFromTab = function (tab) {
 
     chrome.browserAction.setBadgeText({
-        tabId: tab.id,
-        text: ''
+        'tabId': tab.id,
+        'text': ''
     });
 };
 
@@ -224,11 +215,7 @@ stateManager.validHosts = [];
 
 for (let mapping in mappings) {
 
-    if (!mappings.hasOwnProperty(mapping)) {
-        continue;
-    }
-
-    let supportedHost = HOST_PREFIX + mapping + HOST_SUFFIX;
+    let supportedHost = Address.ANY_PROTOCOL + mapping + Address.ANY_PATH;
     stateManager.validHosts.push(supportedHost);
 }
 
@@ -256,10 +243,10 @@ chrome.webRequest.onBeforeRequest.addListener(function (requestDetails) {
         }
     }
 
-}, {'types': ['main_frame'], 'urls': ['*://*/*']});
+}, {'types': ['main_frame'], 'urls': [Address.ANY]});
 
 chrome.webNavigation.onCommitted.addListener(stateManager._updateTab, {
-    url: [{urlContains: ':'}]
+    'url': [{'urlContains': ':'}]
 });
 
 chrome.webRequest.onErrorOccurred.addListener(function (requestDetails) {
@@ -268,7 +255,7 @@ chrome.webRequest.onErrorOccurred.addListener(function (requestDetails) {
         delete stateManager.requests[requestDetails.requestId];
     }
 
-}, {'urls': ['*://*/*']});
+}, {'urls': [Address.ANY]});
 
 chrome.webRequest.onBeforeRedirect.addListener(function (requestDetails) {
 
@@ -280,10 +267,10 @@ chrome.webRequest.onBeforeRedirect.addListener(function (requestDetails) {
         delete stateManager.requests[requestDetails.requestId];
     }
 
-}, {'urls': ['*://*/*']});
+}, {'urls': [Address.ANY]});
 
 chrome.webRequest.onBeforeSendHeaders.addListener(stateManager._stripMetadata, {
     'urls': stateManager.validHosts
-}, [BLOCKING_ACTION, REQUEST_HEADERS]);
+}, [WebRequest.BLOCKING, WebRequest.HEADERS]);
 
 chrome.storage.onChanged.addListener(stateManager._handleStorageChanged);
diff --git a/modules/internal/.gitkeep b/modules/internal/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/modules/internal/helpers.js b/modules/internal/helpers.js
new file mode 100644
index 0000000..c5798f7
--- /dev/null
+++ b/modules/internal/helpers.js
@@ -0,0 +1,169 @@
+/**
+ * Internal Helper Module
+ * Belongs to Decentraleyes.
+ *
+ * @author      Thomas Rientjes
+ * @since       2017-10-26
+ * @license     MPL 2.0
+ *
+ * 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';
+
+/**
+ * Helpers
+ */
+
+var helpers = {};
+
+/**
+ * Public Functions
+ */
+
+helpers.insertI18nContentIntoDocument = function (document) {
+
+    let scriptDirection, i18nElements;
+
+    scriptDirection = helpers.determineScriptDirection(navigator.language);
+    i18nElements = document.querySelectorAll('[data-i18n-content]');
+
+    i18nElements.forEach(function (i18nElement) {
+
+        let i18nMessageName = i18nElement.getAttribute('data-i18n-content');
+
+        i18nElement.innerText = chrome.i18n.getMessage(i18nMessageName);
+        i18nElement.setAttribute('dir', scriptDirection);
+    });
+};
+
+helpers.insertI18nTitlesIntoDocument = function (document) {
+
+    let scriptDirection, i18nElements;
+
+    scriptDirection = helpers.determineScriptDirection(navigator.language);
+    i18nElements = document.querySelectorAll('[data-i18n-title]');
+
+    i18nElements.forEach(function (i18nElement) {
+
+        let i18nMessageName = i18nElement.getAttribute('data-i18n-title');
+
+        i18nElement.setAttribute('title', chrome.i18n.getMessage(i18nMessageName));
+        i18nElement.setAttribute('dir', scriptDirection);
+    });
+};
+
+helpers.languageIsFullySupported = function (language) {
+
+    let languageSupported, supportedLanguages;
+
+    languageSupported = false;
+
+    supportedLanguages = [
+        'ar', 'bg', 'zh-CN', 'zh-TW', 'nl', 'en', 'et', 'fi', 'fr', 'de', 'he',
+        'hu', 'is', 'id', 'pl', 'pt-PT', 'ro', 'es', 'tr'
+    ];
+
+    for (let supportedLanguage of supportedLanguages) {
+
+        if (language.search(supportedLanguage) !== -1) {
+            languageSupported = true;
+        }
+    }
+
+    return languageSupported;
+};
+
+helpers.determineCdnName = function (domainName) {
+
+    switch (domainName) {
+
+    case 'ajax.googleapis.com':
+        return 'Google Hosted Libraries';
+    case 'ajax.aspnetcdn.com':
+        return 'Microsoft Ajax CDN';
+    case 'ajax.microsoft.com':
+        return 'Microsoft Ajax CDN [Deprecated]';
+    case 'cdnjs.cloudflare.com':
+        return 'CDNJS (Cloudflare)';
+    case 'code.jquery.com':
+        return 'jQuery CDN (MaxCDN)';
+    case 'cdn.jsdelivr.net':
+        return 'jsDelivr (MaxCDN)';
+    case 'yastatic.net':
+        return 'Yandex CDN';
+    case 'yandex.st':
+        return 'Yandex CDN [Deprecated]';
+    case 'libs.baidu.com':
+        return 'Baidu CDN';
+    case 'lib.sinaapp.com':
+        return 'Sina Public Resources';
+    case 'upcdn.b0.upaiyun.com':
+        return 'UpYun Library';
+    default:
+        return 'Unknown';
+    }
+};
+
+helpers.determineResourceName = function (filename) {
+
+    switch (filename) {
+
+    case 'angular.min.js.dec':
+        return 'AngularJS';
+    case 'backbone-min.js.dec':
+        return 'Backbone.js';
+    case 'dojo.js.dec':
+        return 'Dojo';
+    case 'ember.min.js.dec':
+        return 'Ember.js';
+    case 'ext-core.js.dec':
+        return 'Ext Core';
+    case 'jquery.min.js.dec':
+        return 'jQuery';
+    case 'jquery-ui.min.js.dec':
+        return 'jQuery UI';
+    case 'modernizr.min.js.dec':
+        return 'Modernizr';
+    case 'mootools-yui-compressed.js.dec':
+        return 'MooTools';
+    case 'prototype.js.dec':
+        return 'Prototype';
+    case 'scriptaculous.js.dec':
+        return 'Scriptaculous';
+    case 'swfobject.js.dec':
+        return 'SWFObject';
+    case 'underscore-min.js.dec':
+        return 'Underscore.js';
+    case 'webfont.js.dec':
+        return 'Web Font Loader';
+    default:
+        return 'Unknown';
+    }
+};
+
+helpers.determineScriptDirection = function (language) {
+
+    let rightToLeftLanguages, scriptDirection;
+
+    rightToLeftLanguages = ['ar', 'he'];
+
+    if (rightToLeftLanguages.indexOf(language) === -1) {
+        scriptDirection = 'ltr';
+    } else {
+        scriptDirection = 'rtl';
+    }
+
+    return scriptDirection;
+};
+
+helpers.formatVersion = function (version) {
+
+    if (version.indexOf('beta') === -1) {
+        return version;
+    } else {
+        return 'BETA';
+    }
+};
diff --git a/pages/background/background.html b/pages/background/background.html
index 06238b3..f61861d 100644
--- a/pages/background/background.html
+++ b/pages/background/background.html
@@ -10,6 +10,9 @@
 
     <body>
 
+        <script src="../../modules/internal/helpers.js"></script>
+
+        <script src="../../core/constants.js"></script>
         <script src="../../core/files.js"></script>
         <script src="../../core/resources.js"></script>
         <script src="../../core/mappings.js"></script>
diff --git a/pages/options/options.html b/pages/options/options.html
index f75f177..c184297 100644
--- a/pages/options/options.html
+++ b/pages/options/options.html
@@ -18,6 +18,9 @@
 
     <body>
 
+        <script src="../../core/constants.js"></script>
+        <script src="../../modules/internal/helpers.js"></script>
+
         <script src="options.js"></script>
 
         <section class="option">
diff --git a/pages/options/options.js b/pages/options/options.js
index 85988c1..ed6e8e2 100644
--- a/pages/options/options.js
+++ b/pages/options/options.js
@@ -19,55 +19,24 @@
 
 var options = {};
 
-/**
- * Constants
- */
-
-const WEB_PREFIX_VALUE = 'www.';
-const WEB_PREFIX_LENGTH = WEB_PREFIX_VALUE.length;
-const VALUE_SEPARATOR = ';';
-
 /**
  * Private Methods
  */
 
-options._determineScriptDirection = function (language) {
-
-    let rightToLeftLanguages, scriptDirection;
-
-    rightToLeftLanguages = ['ar', 'he'];
-
-    if (rightToLeftLanguages.indexOf(language) !== -1) {
-        scriptDirection = 'rtl';
-    } else {
-        scriptDirection = 'ltr';
-    }
-
-    return scriptDirection;
+options._getOptionElement = function (optionKey) {
+    return document.querySelector(`[data-option=${optionKey}]`);
 };
 
-options._languageIsFullySupported = function (language) {
+function _normalizeDomain (domain) {
 
-    let languageSupported, supportedLanguages;
-
-    languageSupported = false;
-
-    supportedLanguages = ['ar', 'bg', 'zh-CN', 'zh-TW', 'nl', 'en', 'et', 'fi',
-        'fr', 'de', 'he', 'hu', 'is', 'id', 'pl', 'pt-PT', 'ro', 'es', 'tr'];
-
-    for (let supportedLanguage of supportedLanguages) {
+    domain = domain.toLowerCase().trim();
 
-        if (language.search(supportedLanguage) !== -1) {
-            languageSupported = true;
-        }
+    if (domain.startsWith(Address.WWW_PREFIX)) {
+        domain = domain.slice(Address.WWW_PREFIX_LENGTH);
     }
 
-    return languageSupported;
-};
-
-options._getOptionElement = function (optionKey) {
-    return document.querySelector('[data-option=' + optionKey + ']');
-};
+    return domain;
+}
 
 /**
  * Initializations
@@ -75,13 +44,12 @@ options._getOptionElement = function (optionKey) {
 
 document.addEventListener('DOMContentLoaded', function () {
 
-    let i18nElements, scriptDirection, languageSupported, optionElements;
+    let scriptDirection, languageSupported, optionElements;
 
-    i18nElements = document.querySelectorAll('[data-i18n-content]');
-    scriptDirection = options._determineScriptDirection(navigator.language);
+    scriptDirection = helpers.determineScriptDirection(navigator.language);
     document.body.setAttribute('dir', scriptDirection);
 
-    languageSupported = options._languageIsFullySupported(navigator.language);
+    languageSupported = helpers.languageIsFullySupported(navigator.language);
 
     if (languageSupported === false) {
 
@@ -89,18 +57,14 @@ document.addEventListener('DOMContentLoaded', function () {
         localeNoticeElement.setAttribute('class', 'notice');
     }
 
-    i18nElements.forEach(function (i18nElement) {
-
-        let i18nMessageName = i18nElement.getAttribute('data-i18n-content');
-        i18nElement.innerText = chrome.i18n.getMessage(i18nMessageName);
-    });
+    helpers.insertI18nContentIntoDocument(document);
 
     optionElements = {
-        'showIconBadge': options._getOptionElement('showIconBadge'),
-        'blockMissing': options._getOptionElement('blockMissing'),
-        'disablePrefetch': options._getOptionElement('disablePrefetch'),
-        'stripMetadata': options._getOptionElement('stripMetadata'),
-        'whitelistedDomains': options._getOptionElement('whitelistedDomains')
+        'showIconBadge': options._getOptionElement(Setting.SHOW_ICON_BADGE),
+        'blockMissing': options._getOptionElement(Setting.BLOCK_MISSING),
+        'disablePrefetch': options._getOptionElement(Setting.DISABLE_PREFETCH),
+        'stripMetadata': options._getOptionElement(Setting.STRIP_METADATA),
+        'whitelistedDomains': options._getOptionElement(Setting.WHITELISTED_DOMAINS)
     };
 
     chrome.storage.local.get(Object.keys(optionElements), function (items) {
@@ -111,11 +75,11 @@ document.addEventListener('DOMContentLoaded', function () {
         domainWhitelist = '';
 
         Object.keys(whitelistedDomains).forEach(function (domain) {
-            domainWhitelist = domainWhitelist + domain + ';';
+            domainWhitelist = `${domainWhitelist}${domain};`;
         });
 
         domainWhitelist = domainWhitelist.slice(0, -1);
-        domainWhitelist = domainWhitelist.replace(/^;+|;+$/g, '');
+        domainWhitelist = domainWhitelist.replace(Whitelist.TRIM_EXPRESSION, '');
 
         optionElements.showIconBadge.checked = items.showIconBadge;
         optionElements.blockMissing.checked = items.blockMissing;
@@ -132,20 +96,20 @@ document.addEventListener('DOMContentLoaded', function () {
         optionType = target.getAttribute('type');
 
         switch (optionType) {
-            case 'checkbox':
-                optionValue = target.checked;
-                break;
-            default:
-                optionValue = target.value;
+        case 'checkbox':
+            optionValue = target.checked;
+            break;
+        default:
+            optionValue = target.value;
         }
 
-        if (optionKey === 'disablePrefetch') {
+        if (optionKey === Setting.DISABLE_PREFETCH) {
 
             if (optionValue === false) {
 
                 // Restore default values of related preference values.
                 chrome.privacy.network.networkPredictionEnabled.clear({});
-            
+
             } else {
 
                 chrome.privacy.network.networkPredictionEnabled.set({
@@ -154,13 +118,13 @@ document.addEventListener('DOMContentLoaded', function () {
             }
         }
 
-        if (optionKey === 'whitelistedDomains') {
+        if (optionKey === Setting.WHITELISTED_DOMAINS) {
 
             let domainWhitelist = optionValue;
-            
+
             optionValue = {};
 
-            domainWhitelist.split(VALUE_SEPARATOR).forEach(function (domain) {
+            domainWhitelist.split(Whitelist.VALUE_SEPARATOR).forEach(function (domain) {
                 optionValue[_normalizeDomain(domain)] = true;
             });
         }
@@ -176,18 +140,3 @@ document.addEventListener('DOMContentLoaded', function () {
     optionElements.stripMetadata.addEventListener('change', optionChangedHandler);
     optionElements.whitelistedDomains.addEventListener('keyup', optionChangedHandler);
 });
-
-/**
- * Private Methods
- */
-
-function _normalizeDomain(domain) {
-
-    domain = domain.toLowerCase().trim();
-
-    if (domain.startsWith(WEB_PREFIX_VALUE)) {
-        domain = domain.slice(WEB_PREFIX_LENGTH);
-    }
-
-    return domain;
-}
diff --git a/pages/popup/popup.html b/pages/popup/popup.html
index 53dab58..9b82ff5 100644
--- a/pages/popup/popup.html
+++ b/pages/popup/popup.html
@@ -18,6 +18,9 @@
 
 <body>
 
+    <script src="../../core/constants.js"></script>
+    <script src="../../modules/internal/helpers.js"></script>
+
     <script src="popup.js"></script>
 
     <header>
@@ -61,7 +64,7 @@
 
         <span id="testing-utility-link" class="link-text">decentraleyes.org/test</span>
 
-        <div id="options-button" class="button">
+        <div id="options-button" class="button" data-i18n-title="optionsTitle">
             <i class="fas fa-cog" data-fa-transform="grow-2"></i>
         </div>
 
diff --git a/pages/popup/popup.js b/pages/popup/popup.js
index fc7c623..b09f312 100644
--- a/pages/popup/popup.js
+++ b/pages/popup/popup.js
@@ -19,73 +19,29 @@
 
 var popup = {};
 
-/**
- * Constants
- */
-
-const WEB_DOMAIN_EXPRESSION = /:\/\/(.[^\/]+)(.*)/;
-const WEB_PREFIX_VALUE = 'www.';
-const WEB_PREFIX_LENGTH = WEB_PREFIX_VALUE.length;
-
-/**
- * Private Methods
- */
-
-popup._determineScriptDirection = function (language) {
-
-    let rightToLeftLanguages, scriptDirection;
-
-    rightToLeftLanguages = ['ar', 'he'];
-
-    if (rightToLeftLanguages.indexOf(language) !== -1) {
-        scriptDirection = 'rtl';
-    } else {
-        scriptDirection = 'ltr';
-    }
-
-    return scriptDirection;
-};
-
 /**
  * Initializations
  */
 
 document.addEventListener('DOMContentLoaded', function () {
 
-    let version, optionsButtonElement, optionsTitle, scriptDirection, i18nElements;
-
-    version = chrome.runtime.getManifest().version;
-
-    if (version.indexOf('beta') !== -1) {
-        version = 'BETA';
-    }
+    let version, optionsButtonElement, scriptDirection;
 
+    version = helpers.formatVersion(browser.runtime.getManifest().version);
     document.getElementById('version-label').innerText = version;
 
+    scriptDirection = helpers.determineScriptDirection(navigator.language);
     optionsButtonElement = document.getElementById('options-button');
-    optionsTitle = chrome.i18n.getMessage('optionsTitle');
-
-    scriptDirection = popup._determineScriptDirection(navigator.language);
-
-    optionsButtonElement.setAttribute('title', optionsTitle);
-    optionsButtonElement.setAttribute('dir', scriptDirection);
 
-    i18nElements = document.querySelectorAll('[data-i18n-content]');
-
-    i18nElements.forEach(function (i18nElement) {
-
-        let i18nMessageName = i18nElement.getAttribute('data-i18n-content');
-
-        i18nElement.innerText = chrome.i18n.getMessage(i18nMessageName);
-        i18nElement.setAttribute('dir', scriptDirection);
-    });
+    helpers.insertI18nContentIntoDocument(document);
+    helpers.insertI18nTitlesIntoDocument(document);
 
     chrome.storage.local.get('amountInjected', function (items) {
 
         let amountInjected = items.amountInjected || 0;
         document.getElementById('injection-counter').innerText = amountInjected;
 
-        chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
+        chrome.tabs.query({'active': true, 'currentWindow': true}, function (tabs) {
 
             chrome.runtime.getBackgroundPage(function (backgroundPage) {
 
@@ -101,7 +57,7 @@ document.addEventListener('DOMContentLoaded', function () {
                 injectionOverview = {};
 
                 try {
-                    domain = tabs[0].url.match(WEB_DOMAIN_EXPRESSION)[1];
+                    domain = tabs[0].url.match(Address.DOMAIN_EXPRESSION)[1];
                 } catch (exception) {
                     domain = null;
                 }
@@ -114,24 +70,22 @@ document.addEventListener('DOMContentLoaded', function () {
                     protectionToggleElement = document.getElementById('protection-toggle-button');
                     domainIndicatorElement = document.getElementById('domain-indicator');
 
-                    if (domain.startsWith(WEB_PREFIX_VALUE)) {
-                        domain = domain.slice(WEB_PREFIX_LENGTH);
+                    if (domain.startsWith(Address.WWW_PREFIX)) {
+                        domain = domain.slice(Address.WWW_PREFIX_LENGTH);
                     }
 
                     domainIndicatorElement.innerText = domain;
 
-                    if (!backgroundPage.requestAnalyzer.whitelistedDomains[domain]) {
+                    if (backgroundPage.requestAnalyzer.whitelistedDomains[domain]) {
 
-                        protectionToggleElement.setAttribute('class', 'button button-toggle active');
+                        protectionToggleElement.setAttribute('class', 'button button-toggle');
 
-                        let disableProtectionTitle = chrome.i18n.getMessage('disableProtectionTitle');
-                        
-                        protectionToggleElement.setAttribute('title', disableProtectionTitle);
-                        protectionToggleElement.setAttribute('dir', scriptDirection);
+                        let enableProtectionTitle = chrome.i18n.getMessage('enableProtectionTitle');
+                        protectionToggleElement.setAttribute('title', enableProtectionTitle);
 
                         protectionToggleElement.addEventListener('click', function () {
 
-                            backgroundPage.stateManager.addDomainToWhitelist(domain).then(function () {
+                            backgroundPage.stateManager.deleteDomainFromWhitelist(domain).then(function () {
 
                                 chrome.tabs.reload(tabs[0].id, {
                                     'bypassCache': true
@@ -143,14 +97,16 @@ document.addEventListener('DOMContentLoaded', function () {
 
                     } else {
 
-                        protectionToggleElement.setAttribute('class', 'button button-toggle');
+                        protectionToggleElement.setAttribute('class', 'button button-toggle active');
 
-                        let enableProtectionTitle = chrome.i18n.getMessage('enableProtectionTitle');
-                        protectionToggleElement.setAttribute('title', enableProtectionTitle);
+                        let disableProtectionTitle = chrome.i18n.getMessage('disableProtectionTitle');
+
+                        protectionToggleElement.setAttribute('title', disableProtectionTitle);
+                        protectionToggleElement.setAttribute('dir', scriptDirection);
 
                         protectionToggleElement.addEventListener('click', function () {
 
-                            backgroundPage.stateManager.deleteDomainFromWhitelist(domain).then(function () {
+                            backgroundPage.stateManager.addDomainToWhitelist(domain).then(function () {
 
                                 chrome.tabs.reload(tabs[0].id, {
                                     'bypassCache': true
@@ -196,42 +152,7 @@ document.addEventListener('DOMContentLoaded', function () {
                     badgeTextNode = document.createTextNode(cdn.length);
                     badgeElement.appendChild(badgeTextNode);
 
-                    switch (injectionSource) {
-
-                    case 'ajax.googleapis.com':
-                        cdnName = 'Google Hosted Libraries';
-                        break;
-                    case 'ajax.aspnetcdn.com':
-                        cdnName = 'Microsoft Ajax CDN';
-                        break;
-                    case 'ajax.microsoft.com':
-                        cdnName = 'Microsoft Ajax CDN [Deprecated]';
-                        break;
-                    case 'cdnjs.cloudflare.com':
-                        cdnName = 'CDNJS (Cloudflare)';
-                        break;
-                    case 'code.jquery.com':
-                        cdnName = 'jQuery CDN (MaxCDN)';
-                        break;
-                    case 'cdn.jsdelivr.net':
-                        cdnName = 'jsDelivr (MaxCDN)';
-                        break;
-                    case 'yastatic.net':
-                        cdnName = 'Yandex CDN';
-                        break;
-                    case 'yandex.st':
-                        cdnName = 'Yandex CDN [Deprecated]';
-                        break;
-                    case 'libs.baidu.com':
-                        cdnName = 'Baidu CDN';
-                        break;
-                    case 'lib.sinaapp.com':
-                        cdnName = 'Sina Public Resources';
-                        break;
-                    case 'upcdn.b0.upaiyun.com':
-                        cdnName = 'UpYun Library';
-                        break;
-                    }
+                    cdnName = helpers.determineCdnName(injectionSource);
 
                     cdnNameTextNode = document.createTextNode(cdnName);
 
@@ -256,59 +177,15 @@ document.addEventListener('DOMContentLoaded', function () {
                         resourcePathDetails = injection.path.split('/');
                         resourceFilename = resourcePathDetails[resourcePathDetails.length - 1];
 
-                        switch (resourceFilename) {
-
-                        case 'angular.min.js.dec':
-                            resourceName = 'AngularJS';
-                            break;
-                        case 'backbone-min.js.dec':
-                            resourceName = 'Backbone.js';
-                            break;
-                        case 'dojo.js.dec':
-                            resourceName = 'Dojo';
-                            break;
-                        case 'ember.min.js.dec':
-                            resourceName = 'Ember.js';
-                            break;
-                        case 'ext-core.js.dec':
-                            resourceName = 'Ext Core';
-                            break;
-                        case 'jquery.min.js.dec':
-                            resourceName = 'jQuery';
-                            break;
-                        case 'jquery-ui.min.js.dec':
-                            resourceName = 'jQuery UI';
-                            break;
-                        case 'modernizr.min.js.dec':
-                            resourceName = 'Modernizr';
-                            break;
-                        case 'mootools-yui-compressed.js.dec':
-                            resourceName = 'MooTools';
-                            break;
-                        case 'prototype.js.dec':
-                            resourceName = 'Prototype';
-                            break;
-                        case 'scriptaculous.js.dec':
-                            resourceName = 'Scriptaculous';
-                            break;
-                        case 'swfobject.js.dec':
-                            resourceName = 'SWFObject';
-                            break;
-                        case 'underscore-min.js.dec':
-                            resourceName = 'Underscore.js';
-                            break;
-                        case 'webfont.js.dec':
-                            resourceName = 'Web Font Loader';
-                            break;
-                        }
-
-                        resourceNameTextNode = document.createTextNode('- ' + resourceName);
+                        resourceName = helpers.determineResourceName(resourceFilename);
+
+                        resourceNameTextNode = document.createTextNode(`- ${resourceName}`);
                         subListItemElement.appendChild(resourceNameTextNode);
 
                         sideNoteElement = document.createElement('span');
                         sideNoteElement.setAttribute('class', 'side-note');
 
-                        sideNoteTextNode = document.createTextNode(' v' + injection.version);
+                        sideNoteTextNode = document.createTextNode(` v${injection.version}`);
 
                         sideNoteElement.appendChild(sideNoteTextNode);
                         subListItemElement.appendChild(sideNoteElement);
@@ -326,7 +203,7 @@ document.addEventListener('DOMContentLoaded', function () {
         });
     });
 
-    document.getElementById('options-button').addEventListener('click', function () {
+    optionsButtonElement.addEventListener('mouseup', function () {
         chrome.runtime.openOptionsPage();
         return window.close();
     });
-- 
GitLab