diff --git a/ChromeVersion/clearurls.js b/ChromeVersion/clearurls.js new file mode 100644 index 0000000..9b3855e --- /dev/null +++ b/ChromeVersion/clearurls.js @@ -0,0 +1,892 @@ +/* +* ################################################################## +* # Fetch Rules & Exception from URL # +* ################################################################## +*/ +var providers = []; +var prvKeys = []; +var badges = []; +var tabid = 0; +var siteBlockedAlert = 'javascript:void(0)'; +var dataHash; +var localDataHash; +var os; +var currentURL; + +var storage = []; + +getDataFromDisk(); + +function start(items) +{ + initStorage(items); + + /** + * Save OS Version + */ + chrome.runtime.getPlatformInfo(function(info) { + + os = info.os; + + + /** + * Initialize the JSON provider object keys. + * + * @param {JSON Object} obj + */ + function getKeys(obj){ + for(var key in obj){ + prvKeys.push(key); + } + } + + /** + * Initialize the providers form the JSON object. + * + */ + function createProviders() + { + data = storage.ClearURLsData; + + for(var p = 0; p < prvKeys.length; p++) + { + //Create new provider + providers.push(new Provider(prvKeys[p],data.providers[prvKeys[p]].completeProvider)); + + //Add URL Pattern + providers[p].setURLPattern(data.providers[prvKeys[p]].urlPattern); + + //Add rules to provider + for(var r = 0; r < data.providers[prvKeys[p]].rules.length; r++) + { + providers[p].addRule(data.providers[prvKeys[p]].rules[r]); + } + + //Add exceptions to provider + for(var e = 0; e < data.providers[prvKeys[p]].exceptions.length; e++) + { + providers[p].addException(data.providers[prvKeys[p]].exceptions[e]); + } + + //Add redirections to provider + for(var re = 0; re < data.providers[prvKeys[p]].redirections.length; re++) + { + providers[p].addRedirection(data.providers[prvKeys[p]].redirections[re]); + } + } + } + + /** + * Convert the external data to Objects and + * call the create provider function. + * + * @param {String} retrievedText - pure data form github + */ + function toObject(retrievedText) { + getKeys(storage.ClearURLsData.providers); + createProviders(); + } + + /** + * Load local saved data, if the browser is offline or + * some other network trouble. + */ + function loadOldDataFromStore() + { + localDataHash = storage.dataHash; + } + + /** + * Save the hash status to the local storage. + * The status can have the following values: + * 1 "up to date" + * 2 "updated" + * 3 "update available" + * @param status_code the number for the status + */ + function storeHashStatus(status_code) + { + switch(status_code) + { + case 1: status_code = "hash_status_code_1"; + break; + case 2: status_code = "hash_status_code_2"; + break; + case 3: status_code = "hash_status_code_3"; + break; + default: status_code = "hash_status_code_4"; + } + + storage.hashStatus = status_code; + } + + /** + * Get the hash for the rule file on github. + * Check the hash with the hash form the local file. + * If the hash has changed, then download the new rule file. + * Else do nothing. + */ + function getHash() + { + //Get the target hash from github + fetch(storage.hashURL) + .then(function(response){ + var responseTextHash = response.clone().text().then(function(responseTextHash){ + if(response.ok) + { + dataHash = responseTextHash; + + if($.trim(dataHash) !== $.trim(localDataHash)) + { + fetchFromURL(); + } + else { + toObject(storage.ClearURLsData); + storeHashStatus(1); + } + } + else { + dataHash = false; + } + }); + }); + } + + /** + * Fetch the Rules & Exception from github. + */ + function fetchFromURL() + { + fetch(storage.ruleURL) + .then(checkResponse); + + function checkResponse(response) + { + var responseText = response.clone().text().then(function(responseText){ + if(response.ok) + { + var downloadedFileHash = $.sha256(responseText); + + if($.trim(downloadedFileHash) === $.trim(dataHash)) + { + storage.ClearURLsData = responseText; + storage.dataHash = downloadedFileHash; + storeHashStatus(2); + } + else { + storeHashStatus(3); + } + storage.ClearURLsData = JSON.parse(storage.ClearURLsData); + toObject(storage.ClearURLsData); + } + }); + } + } + + // ################################################################## + + /* + * ################################################################## + * # Supertyp Provider # + * ################################################################## + */ + /** + * Declare constructor + * + * @param {String} _name Provider name + * @param {boolean} completeProvider Set URL Pattern as rule + */ + function Provider(_name,_completeProvider = false){ + var name = _name; + var urlPattern; + var rules = []; + var exceptions = []; + var canceling = _completeProvider; + var redirections = []; + + if(_completeProvider){ + rules.push(".*"); + } + + /** + * Returns the provider name. + * @return {String} + */ + this.getName = function() { + return name; + }; + + /** + * Add URL pattern. + * + * @require urlPatterns as RegExp + */ + this.setURLPattern = function(urlPatterns) { + urlPattern = new RegExp(urlPatterns, "i"); + }; + + /** + * Return if the Provider Request is canceled + * @return {Boolean} isCanceled + */ + this.isCaneling = function() { + return canceling; + }; + + /** + * Check the url is matching the ProviderURL. + * + * @return {boolean} ProviderURL as RegExp + */ + this.matchURL = function(url) { + return !(this.matchException(url)) && urlPattern.test(url); + }; + + /** + * Add a rule to the rule array. + * + * @param String rule RegExp as string + */ + this.addRule = function(rule) { + rules.push(rule); + }; + + /** + * Return all rules as an array. + * + * @return Array RegExp strings + */ + this.getRules = function() { + return rules; + }; + + /** + * Add a exception to the exceptions array. + * + * @param String exception RegExp as string + */ + this.addException = function(exception) { + exceptions.push(exception); + }; + + /** + * Private helper method to check if the url + * an exception. + * + * @param {String} url RegExp as string + * @return {boolean} if matching? true: false + */ + this.matchException = function(url) { + var result = false; + + //Add the site blocked alert to every exception + if(url == siteBlockedAlert) return true; + + for (var i = 0; i < exceptions.length; i++) { + if(result) { break; } + + exception_regex = new RegExp(exceptions[i], "i"); + result = exception_regex.test(url); + } + + return result; + }; + + /** + * Add a redirection to the redirections array. + * + * @param String redirection RegExp as string + */ + this.addRedirection = function(redirection) { + redirections.push(redirection); + }; + + /** + * Return all redirection. + * + * @return url + */ + this.getRedirection = function(url) { + var re = null; + + for(var i = 0; i < redirections.length; i++) + { + result = (url.match(new RegExp(redirections[i], "i"))); + + if (result && result.length > 0) + { + re = (new RegExp(redirections[i], "i")).exec(url)[1]; + + break; + } + } + + return re; + }; + } + // ################################################################## + + /** + * Helper function which remove the tracking fields + * for each provider given as parameter. + * + * @param {Provider} provider Provider-Object + * @param {webRequest} request webRequest-Object + * @return {Array} Array with changes and url fields + */ + function removeFieldsFormURL(provider, request) + { + var url = request.url; + var domain = url.replace(new RegExp("\\?.*", "i"), ""); + var fields = ""; + var rules = provider.getRules(); + var changes = false; + var cancel = false; + + /* + * Expand the url by provider redirections. So no tracking on + * url redirections form sites to sites. + */ + var re = provider.getRedirection(url); + if(re !== null) + { + url = decodeURIComponent(re); + //Log the action + pushToLog(request.url, re, translate('log_redirect')); + + return { + "redirect": true, + "url": url + }; + } + + /** + * Only test for matches, if there are fields that can be cleaned. + */ + if(existsFields(url)) + { + /** + * It must be non-greedy, because by default .* will match + * all ? chars. So the replace function delete everything + * before the last ?. With adding a ? on the quantifier *, + * we fixed this problem. + */ + fields = url.replace(new RegExp(".*?\\?", "i"), ""); + + for (var i = 0; i < rules.length; i++) { + var beforReplace = fields; + + fields = fields.replace(new RegExp(rules[i], "i"), ""); + + if(beforReplace != fields) + { + //Log the action + pushToLog(domain+"?"+beforReplace, domain+"?"+fields, rules[i]); + + if(badges[tabid] == null) + { + badges[tabid] = 0; + } + + increaseURLCounter(); + + if(!checkOSAndroid()) + { + if(storage.badgedStatus) { + browser.browserAction.setBadgeText({text: (++badges[tabid]).toString(), tabId: tabid}); + } + else + { + browser.browserAction.setBadgeText({text: "", tabId: tabid}); + } + } + + changes = true; + } + } + url = domain+"?"+fields; + } + else { + if(domain != url) + { + url = domain; + changes = true; + } + } + + if(provider.isCaneling()){ + pushToLog(request.url, request.url, translate('log_domain_blocked')); + if(badges[tabid] == null) + { + badges[tabid] = 0; + } + + increaseURLCounter(); + + if(!checkOSAndroid()) + { + if(storage.badgedStatus) { + browser.browserAction.setBadgeText({text: (++badges[tabid]).toString(), tabId: tabid}); + } + else + { + browser.browserAction.setBadgeText({text: "", tabId: tabid}); + } + } + + cancel = true; + } + + return { + "changes": changes, + "url": url, + "cancel": cancel + }; + } + + /** + * Return the number of parameters query strings. + * @param {String} url URL as String + * @return {int} Number of Parameters + */ + function countFields(url) + { + var matches = (url.match(/[^\/|\?|&]+=[^\/|\?|&]+/gi) || []); + var count = matches.length; + + return count; + } + + /** + * Returns true if fields exists. + * @param {String} url URL as String + * @return {boolean} + */ + function existsFields(url) + { + var matches = (url.match(/\?.+/i) || []); + var count = matches.length; + + return (count > 0); + } + + /** + * Function which called from the webRequest to + * remove the tracking fields from the url. + * + * @param {webRequest} request webRequest-Object + * @return {Array} redirectUrl or none + */ + function clearUrl(request) + { + var URLbeforeReplaceCount = countFields(request.url); + + //Add Fields form Request to global url counter + increaseGlobalURLCounter(URLbeforeReplaceCount); + + if(storage.globalStatus){ + + var result = { + "changes": false, + "url": "", + "redirect": false, + "cancel": false + }; + + /* + * Call for every provider the removeFieldsFormURL method. + */ + for (var i = 0; i < providers.length; i++) { + + if(providers[i].matchURL(request.url)) + { + result = removeFieldsFormURL(providers[i], request); + } + + /* + * Expand urls and bypass tracking. + * Cancel the active request. + */ + if(result.redirect) + { + browser.tabs.update(request.tabId, {url: result.url}); + return {cancel: true}; + } + + /* + * Cancel the Request and redirect to the site blocked alert page, + * to inform the user about the full url blocking. + */ + if(result.cancel){ + return { + redirectUrl: siteBlockedAlert + }; + } + + /* + * Ensure that the function go not into + * a loop. + */ + if(result.changes){ + return { + redirectUrl: result.url + }; + } + } + } + + // Default case + return {}; + } + + /** + * Function to log all activities from ClearUrls. + * Only logging when activated. + * The log is only temporary saved in the cache and will + * permanently saved with the saveLogOnClose function. + * + * @param beforeProcessing the url before the clear process + * @param afterProcessing the url after the clear process + * @param rule the rule that triggered the process + */ + function pushToLog(beforeProcessing, afterProcessing, rule) + { + if(storage.loggingStatus) + { + storage.log.log.push( + { + "before": beforeProcessing, + "after": afterProcessing, + "rule": rule, + "timestamp": Date.now() + } + ); + } + } + + /** + * Call loadOldDataFromStore, getHash, counter, status and log functions + */ + + loadOldDataFromStore(); + getHash(); + setBadgedStatus(); + + /** + * Call by each tab is updated. + * And if url has changed. + */ + function handleUpdated(tabId, changeInfo, tabInfo) { + if(changeInfo.url) + { + delete badges[tabId]; + } + currentURL = tabInfo.url; + } + + /** + * Call by each tab is updated. + */ + browser.tabs.onUpdated.addListener(handleUpdated); + + /** + * Call by each tab change to set the actual tab id + */ + function handleActivated(activeInfo) { + tabid = activeInfo.tabId; + browser.tabs.get(tabid).then(function (tab) { + currentURL = tab.url; + }); + } + + /** + * Call by each tab change. + */ + browser.tabs.onActivated.addListener(handleActivated); + + /** + * Check the request. + */ + function promise(requestDetails) + { + if(isDataURL(requestDetails)) + { + return {}; + } + else { + var ret = clearUrl(requestDetails); + return ret; + } + + } + + /** + * To prevent long loading on data urls + * we will check here for data urls. + * + * @type {requestDetails} + * @return {boolean} + */ + function isDataURL(requestDetails) { + var s = requestDetails.url; + + return s.substring(0,4) == "data"; + } + + /** + * Call by each Request and checking the url. + * + * @type {Array} + */ + browser.webRequest.onBeforeRequest.addListener( + promise, + {urls: [""], types: getData("types")}, + ["blocking"] + ); + }); +} + +/** +* Save every minute the temporary data to the disk. +*/ +setInterval(saveOnExit, 60000); + +/** +* Get the badged status from the browser storage and put the value +* into a local variable. +* +*/ +function setBadgedStatus() +{ + if(!checkOSAndroid() && storage.badgedStatus){ + browser.browserAction.setBadgeBackgroundColor({ + 'color': '#'+storage.badged_color + }); + } +} + +/** +* Change the icon. +*/ +function changeIcon() +{ + if(storage.globalStatus){ + browser.browserAction.setIcon({path: "img/clearurls.svg"}); + } else{ + browser.browserAction.setIcon({path: "img/clearurls_gray.svg"}); + } +} + +/** +* Check if it is an android device. +* @return bool +*/ +function checkOSAndroid() +{ + if(os == "android") + { + return true; + } + else{ + return false; + } +} + +/** +* Increase by {number} the GlobalURLCounter +* @param {int} number +*/ +function increaseGlobalURLCounter(number) +{ + if(storage.statisticsStatus) + { + storage.globalurlcounter += number; + } +} + +/** +* Increase by one the URLCounter +*/ +function increaseURLCounter() +{ + if(storage.statisticsStatus) + { + storage.globalCounter++; + } +} + + +/** +* Writes the storage variable to the disk. +*/ +function saveOnExit() +{ + var json = {}; + + Object.entries(storage).forEach(([key, value]) => { + switch (key) { + case "ClearURLsData": + case "log": + json[key] = JSON.stringify(value); + break; + case "types": + json[key] = value.toString(); + break; + default: + json[key] = value; + } + }); + console.log(translate('core_save_on_disk')); + browser.storage.local.set(json); +} + +/** +* Save the value under the key on the disk. +* @param {String} key +* @param {Object} value +*/ +function saveOnDisk(key, value) +{ + browser.storage.local.set({key: value}); +} + +/** +* Retrieve everything and save on the RAM. +*/ +function getDataFromDisk() +{ + browser.storage.local.get().then(start, error); +} + +/** +* Get the value under the key. +* @param {String} key +* @return {Object} +*/ +function getData(key) +{ + return storage[key]; +} + +/** +* Save the value under the key on the RAM. +* @param {String} key +* @param {Object} value +*/ +function setData(key, value) +{ + switch (key) { + case "ClearURLsData": + case "log": + storage[key] = JSON.parse(value); + break; + case "hashURL": + case "ruleURL": + storage[key] = replaceOldGithubURLs(value); + break; + case "types": + storage[key] = value.split(','); + break; + default: + storage[key] = value; + } +} + +/** +* Translate a string with the i18n API. +* +* @param {string} string Name of the attribute used for localization +*/ +function translate(string) +{ + return browser.i18n.getMessage(string); +} + + +/** +* Write error on console. +*/ +function error() +{ + console.log(translate('core_error')); +} + +/** +* Set default values, if the storage is empty. +* @param {Object} items +*/ +function initStorage(items) +{ + initSettings(); + + if(!isEmpty(items)) { + Object.entries(items).forEach(([key, value]) => { + setData(key, value); + }); + } +} + +/** +* Set default values for the settings. +*/ +function initSettings() +{ + storage.ClearURLsData = []; + storage.dataHash = ""; + storage.badgedStatus = true; + storage.globalStatus = true; + storage.globalurlcounter = 0; + storage.globalCounter = 0; + storage.hashStatus = "error"; + storage.loggingStatus = false; + storage.log = {"log": []}; + storage.statisticsStatus = true; + storage.badged_color = "ffa500"; + storage.hashURL = "https://gitlab.com/KevinRoebert/ClearUrls/raw/master/data/rules.hash"; + storage.ruleURL = "https://gitlab.com/KevinRoebert/ClearUrls/raw/master/data/data.json"; + storage.types = ["main_frame", "sub_frame", "xmlhttprequest"]; + storage.reportServer = "https://clearurls.xn--rb-fka.it"; +} + +/** +* Reloads the extension. +*/ +function reload() +{ + browser.runtime.reload(); +} + +/** +* Replace the old GitHub URLs with the +* new GitLab URLs. +*/ +function replaceOldGithubURLs(url) +{ + switch (url) { + case "https://raw.githubusercontent.com/KevinRoebert/ClearUrls/master/data/rules.hash?flush_cache=true": + return "https://gitlab.com/KevinRoebert/ClearUrls/raw/master/data/rules.hash"; + case "https://raw.githubusercontent.com/KevinRoebert/ClearUrls/master/data/data.json?flush_cache=true": + return "https://gitlab.com/KevinRoebert/ClearUrls/raw/master/data/data.json"; + default: + return url; + } +} + +/** +* Check if an object is empty. +* @param {Object} obj +* @return {Boolean} +*/ +function isEmpty(obj) +{ + return (Object.getOwnPropertyNames(obj).length === 0); +} + +/** + * Returns the current URL. + * @return {String} [description] + */ +function getCurrentURL() +{ + return currentURL; +} diff --git a/ChromeVersion/css/core.css b/ChromeVersion/css/core.css new file mode 100644 index 0000000..35a54fd --- /dev/null +++ b/ChromeVersion/css/core.css @@ -0,0 +1,17 @@ +body { + font-size: 13px; + width: 200px; +} + +.small-version { + font-size: 10px; +} + +.navbar-header { + margin-top: 0; + margin-bottom: 8px; +} + +.col-sm-1 { + margin-top: -10px; +} diff --git a/ChromeVersion/manifest.json b/ChromeVersion/manifest.json new file mode 100644 index 0000000..8f2a218 --- /dev/null +++ b/ChromeVersion/manifest.json @@ -0,0 +1,69 @@ +{ + "manifest_version": 2, + "name": "ClearURLs", + "version": "1.3.3.13", + "author": "Kevin R.", + "description": "Remove tracking elements form URLs.", + "homepage_url": "https://gitlab.com/KevinRoebert/ClearUrls", + "default_locale": "en", + "icons": { + "16": "img/clearurls.png", + "19": "img/clearurls.png", + "20": "img/clearurls.png", + "24": "img/clearurls.png", + "30": "img/clearurls.png", + "32": "img/clearurls.png", + "38": "img/clearurls.png", + "48": "img/clearurls.png", + "64": "img/clearurls.png", + "96": "img/clearurls.png", + "128": "img/clearurls.png" + + }, + "browser_action": { + "browser_style": true, + "default_icon": { + "16": "img/clearurls.png", + "19": "img/clearurls.png", + "20": "img/clearurls.png", + "24": "img/clearurls.png", + "30": "img/clearurls.png", + "32": "img/clearurls.png", + "38": "img/clearurls.png", + "48": "img/clearurls.png", + "64": "img/clearurls.png", + "96": "img/clearurls.png", + "128": "img/clearurls.png" + + }, + "default_title": "ClearURLs Add-on", + "default_popup": "html/popup.html" + }, + "permissions": [ + "*://*/*", + "", + "webRequest", + "webRequestBlocking", + "storage", + "tabs" + ], + "background": { + "scripts": [ + "browser-polyfill.js", + "external_js/jquery-3.2.1.min.js", + "external_js/sha256.jquery.js", + "clearurls.js" + ] + }, + "content_scripts": [ + { + "matches": [""], + "js": [ + "browser-polyfill.js" + ] + } + ], + "options_ui": { + "page": "html/settings.html" + } +} diff --git a/browser-polyfill.js b/browser-polyfill.js new file mode 100644 index 0000000..b3dd82d --- /dev/null +++ b/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} + * 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} + */ + 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} 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/core_js/popup_new.js b/core_js/popup_new.js index b2e94e5..69169b0 100644 --- a/core_js/popup_new.js +++ b/core_js/popup_new.js @@ -252,10 +252,14 @@ function reportURL() $.ajax({ url: reportServer+'/report_url.php?url='+encodeURI(currentURL), success: function(result) { - window.alert(translate('success_report_url')); + BootstrapDialog.show({ + message: translate('success_report_url') + }); }, error: function(result) { - window.alert(translate('error_report_url')); + BootstrapDialog.show({ + message: translate('error_report_url') + }); } }); } diff --git a/css/bootstrap-dialog.min.css b/css/bootstrap-dialog.min.css new file mode 100644 index 0000000..c9000fc --- /dev/null +++ b/css/bootstrap-dialog.min.css @@ -0,0 +1 @@ +.bootstrap-dialog .modal-header{border-top-left-radius:4px;border-top-right-radius:4px}.bootstrap-dialog .bootstrap-dialog-title{color:#fff;display:inline-block;font-size:16px}.bootstrap-dialog .bootstrap-dialog-message{font-size:14px}.bootstrap-dialog .bootstrap-dialog-button-icon{margin-right:3px}.bootstrap-dialog .bootstrap-dialog-close-button{font-size:20px;float:right;opacity:.9;filter:alpha(opacity=90)}.bootstrap-dialog .bootstrap-dialog-close-button:hover{cursor:pointer;opacity:1;filter:alpha(opacity=100)}.bootstrap-dialog.type-default .modal-header{background-color:#fff}.bootstrap-dialog.type-default .bootstrap-dialog-title{color:#333}.bootstrap-dialog.type-info .modal-header{background-color:#5bc0de}.bootstrap-dialog.type-primary .modal-header{background-color:#337ab7}.bootstrap-dialog.type-success .modal-header{background-color:#5cb85c}.bootstrap-dialog.type-warning .modal-header{background-color:#f0ad4e}.bootstrap-dialog.type-danger .modal-header{background-color:#d9534f}.bootstrap-dialog.size-large .bootstrap-dialog-title{font-size:24px}.bootstrap-dialog.size-large .bootstrap-dialog-close-button{font-size:30px}.bootstrap-dialog.size-large .bootstrap-dialog-message{font-size:18px}.bootstrap-dialog .icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}} \ No newline at end of file diff --git a/external_js/bootstrap-dialog.min.js b/external_js/bootstrap-dialog.min.js new file mode 100644 index 0000000..8a0d72a --- /dev/null +++ b/external_js/bootstrap-dialog.min.js @@ -0,0 +1 @@ +!function(t,e){"use strict";if("undefined"!=typeof module&&module.exports){var n="undefined"!=typeof process,o=n&&"electron"in process.versions;o?t.BootstrapDialog=e(t.jQuery):module.exports=e(require("jquery"),require("bootstrap"))}else"function"==typeof define&&define.amd?define("bootstrap-dialog",["jquery","bootstrap"],function(t){return e(t)}):t.BootstrapDialog=e(t.jQuery)}(this,function(t){"use strict";var e=t.fn.modal.Constructor,n=function(t,n){e.call(this,t,n)};n.getModalVersion=function(){var e=null;return e="undefined"==typeof t.fn.modal.Constructor.VERSION?"v3.1":/3\.2\.\d+/.test(t.fn.modal.Constructor.VERSION)?"v3.2":/3\.3\.[1,2]/.test(t.fn.modal.Constructor.VERSION)?"v3.3":"v3.3.4"},n.ORIGINAL_BODY_PADDING=parseInt(t("body").css("padding-right")||0,10),n.METHODS_TO_OVERRIDE={},n.METHODS_TO_OVERRIDE["v3.1"]={},n.METHODS_TO_OVERRIDE["v3.2"]={hide:function(e){if(e&&e.preventDefault(),e=t.Event("hide.bs.modal"),this.$element.trigger(e),this.isShown&&!e.isDefaultPrevented()){this.isShown=!1;var n=this.getGlobalOpenedDialogs();0===n.length&&this.$body.removeClass("modal-open"),this.resetScrollbar(),this.escape(),t(document).off("focusin.bs.modal"),this.$element.removeClass("in").attr("aria-hidden",!0).off("click.dismiss.bs.modal"),t.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",t.proxy(this.hideModal,this)).emulateTransitionEnd(300):this.hideModal()}}},n.METHODS_TO_OVERRIDE["v3.3"]={setScrollbar:function(){var t=n.ORIGINAL_BODY_PADDING;this.bodyIsOverflowing&&this.$body.css("padding-right",t+this.scrollbarWidth)},resetScrollbar:function(){var t=this.getGlobalOpenedDialogs();0===t.length&&this.$body.css("padding-right",n.ORIGINAL_BODY_PADDING)},hideModal:function(){this.$element.hide(),this.backdrop(t.proxy(function(){var t=this.getGlobalOpenedDialogs();0===t.length&&this.$body.removeClass("modal-open"),this.resetAdjustments(),this.resetScrollbar(),this.$element.trigger("hidden.bs.modal")},this))}},n.METHODS_TO_OVERRIDE["v3.3.4"]=t.extend({},n.METHODS_TO_OVERRIDE["v3.3"]),n.prototype={constructor:n,getGlobalOpenedDialogs:function(){var e=[];return t.each(o.dialogs,function(t,n){n.isRealized()&&n.isOpened()&&e.push(n)}),e}},n.prototype=t.extend(n.prototype,e.prototype,n.METHODS_TO_OVERRIDE[n.getModalVersion()]);var o=function(e){this.defaultOptions=t.extend(!0,{id:o.newGuid(),buttons:[],data:{},onshow:null,onshown:null,onhide:null,onhidden:null},o.defaultOptions),this.indexedButtons={},this.registeredButtonHotkeys={},this.draggableData={isMouseDown:!1,mouseOffset:{}},this.realized=!1,this.opened=!1,this.initOptions(e),this.holdThisInstance()};return o.BootstrapDialogModal=n,o.NAMESPACE="bootstrap-dialog",o.TYPE_DEFAULT="type-default",o.TYPE_INFO="type-info",o.TYPE_PRIMARY="type-primary",o.TYPE_SUCCESS="type-success",o.TYPE_WARNING="type-warning",o.TYPE_DANGER="type-danger",o.DEFAULT_TEXTS={},o.DEFAULT_TEXTS[o.TYPE_DEFAULT]="Information",o.DEFAULT_TEXTS[o.TYPE_INFO]="Information",o.DEFAULT_TEXTS[o.TYPE_PRIMARY]="Information",o.DEFAULT_TEXTS[o.TYPE_SUCCESS]="Success",o.DEFAULT_TEXTS[o.TYPE_WARNING]="Warning",o.DEFAULT_TEXTS[o.TYPE_DANGER]="Danger",o.DEFAULT_TEXTS.OK="OK",o.DEFAULT_TEXTS.CANCEL="Cancel",o.DEFAULT_TEXTS.CONFIRM="Confirmation",o.SIZE_NORMAL="size-normal",o.SIZE_SMALL="size-small",o.SIZE_WIDE="size-wide",o.SIZE_LARGE="size-large",o.BUTTON_SIZES={},o.BUTTON_SIZES[o.SIZE_NORMAL]="",o.BUTTON_SIZES[o.SIZE_SMALL]="",o.BUTTON_SIZES[o.SIZE_WIDE]="",o.BUTTON_SIZES[o.SIZE_LARGE]="btn-lg",o.ICON_SPINNER="glyphicon glyphicon-asterisk",o.defaultOptions={type:o.TYPE_PRIMARY,size:o.SIZE_NORMAL,cssClass:"",title:null,message:null,nl2br:!0,closable:!0,closeByBackdrop:!0,closeByKeyboard:!0,spinicon:o.ICON_SPINNER,autodestroy:!0,draggable:!1,animate:!0,description:"",tabindex:-1},o.configDefaultOptions=function(e){o.defaultOptions=t.extend(!0,o.defaultOptions,e)},o.dialogs={},o.openAll=function(){t.each(o.dialogs,function(t,e){e.open()})},o.closeAll=function(){t.each(o.dialogs,function(t,e){e.close()})},o.getDialog=function(t){var e=null;return"undefined"!=typeof o.dialogs[t]&&(e=o.dialogs[t]),e},o.setDialog=function(t){return o.dialogs[t.getId()]=t,t},o.addDialog=function(t){return o.setDialog(t)},o.moveFocus=function(){var e=null;t.each(o.dialogs,function(t,n){e=n}),null!==e&&e.isRealized()&&e.getModal().focus()},o.METHODS_TO_OVERRIDE={},o.METHODS_TO_OVERRIDE["v3.1"]={handleModalBackdropEvent:function(){return this.getModal().on("click",{dialog:this},function(t){t.target===this&&t.data.dialog.isClosable()&&t.data.dialog.canCloseByBackdrop()&&t.data.dialog.close()}),this},updateZIndex:function(){var e=1040,n=1050,i=0;t.each(o.dialogs,function(t,e){i++});var s=this.getModal(),a=s.data("bs.modal").$backdrop;return s.css("z-index",n+20*(i-1)),a.css("z-index",e+20*(i-1)),this},open:function(){return!this.isRealized()&&this.realize(),this.getModal().modal("show"),this.updateZIndex(),this}},o.METHODS_TO_OVERRIDE["v3.2"]={handleModalBackdropEvent:o.METHODS_TO_OVERRIDE["v3.1"].handleModalBackdropEvent,updateZIndex:o.METHODS_TO_OVERRIDE["v3.1"].updateZIndex,open:o.METHODS_TO_OVERRIDE["v3.1"].open},o.METHODS_TO_OVERRIDE["v3.3"]={},o.METHODS_TO_OVERRIDE["v3.3.4"]=t.extend({},o.METHODS_TO_OVERRIDE["v3.1"]),o.prototype={constructor:o,initOptions:function(e){return this.options=t.extend(!0,this.defaultOptions,e),this},holdThisInstance:function(){return o.addDialog(this),this},initModalStuff:function(){return this.setModal(this.createModal()).setModalDialog(this.createModalDialog()).setModalContent(this.createModalContent()).setModalHeader(this.createModalHeader()).setModalBody(this.createModalBody()).setModalFooter(this.createModalFooter()),this.getModal().append(this.getModalDialog()),this.getModalDialog().append(this.getModalContent()),this.getModalContent().append(this.getModalHeader()).append(this.getModalBody()).append(this.getModalFooter()),this},createModal:function(){var e=t('');return e.prop("id",this.getId()),e.attr("aria-labelledby",this.getId()+"_title"),e},getModal:function(){return this.$modal},setModal:function(t){return this.$modal=t,this},createModalDialog:function(){return t('')},getModalDialog:function(){return this.$modalDialog},setModalDialog:function(t){return this.$modalDialog=t,this},createModalContent:function(){return t('')},getModalContent:function(){return this.$modalContent},setModalContent:function(t){return this.$modalContent=t,this},createModalHeader:function(){return t('')},getModalHeader:function(){return this.$modalHeader},setModalHeader:function(t){return this.$modalHeader=t,this},createModalBody:function(){return t('')},getModalBody:function(){return this.$modalBody},setModalBody:function(t){return this.$modalBody=t,this},createModalFooter:function(){return t('')},getModalFooter:function(){return this.$modalFooter},setModalFooter:function(t){return this.$modalFooter=t,this},createDynamicContent:function(t){var e=null;return e="function"==typeof t?t.call(t,this):t,"string"==typeof e&&(e=this.formatStringContent(e)),e},formatStringContent:function(t){return this.options.nl2br?t.replace(/\r\n/g,"
").replace(/[\r\n]/g,"
"):t},setData:function(t,e){return this.options.data[t]=e,this},getData:function(t){return this.options.data[t]},setId:function(t){return this.options.id=t,this},getId:function(){return this.options.id},getType:function(){return this.options.type},setType:function(t){return this.options.type=t,this.updateType(),this},updateType:function(){if(this.isRealized()){var t=[o.TYPE_DEFAULT,o.TYPE_INFO,o.TYPE_PRIMARY,o.TYPE_SUCCESS,o.TYPE_WARNING,o.TYPE_DANGER];this.getModal().removeClass(t.join(" ")).addClass(this.getType())}return this},getSize:function(){return this.options.size},setSize:function(t){return this.options.size=t,this.updateSize(),this},updateSize:function(){if(this.isRealized()){var e=this;this.getModal().removeClass(o.SIZE_NORMAL).removeClass(o.SIZE_SMALL).removeClass(o.SIZE_WIDE).removeClass(o.SIZE_LARGE),this.getModal().addClass(this.getSize()),this.getModalDialog().removeClass("modal-sm"),this.getSize()===o.SIZE_SMALL&&this.getModalDialog().addClass("modal-sm"),this.getModalDialog().removeClass("modal-lg"),this.getSize()===o.SIZE_WIDE&&this.getModalDialog().addClass("modal-lg"),t.each(this.options.buttons,function(n,o){var i=e.getButton(o.id),s=["btn-lg","btn-sm","btn-xs"],a=!1;if("string"==typeof o.cssClass){var d=o.cssClass.split(" ");t.each(d,function(e,n){-1!==t.inArray(n,s)&&(a=!0)})}a||(i.removeClass(s.join(" ")),i.addClass(e.getButtonSize()))})}return this},getCssClass:function(){return this.options.cssClass},setCssClass:function(t){return this.options.cssClass=t,this},getTitle:function(){return this.options.title},setTitle:function(t){return this.options.title=t,this.updateTitle(),this},updateTitle:function(){if(this.isRealized()){var t=null!==this.getTitle()?this.createDynamicContent(this.getTitle()):this.getDefaultText();this.getModalHeader().find("."+this.getNamespace("title")).html("").append(t).prop("id",this.getId()+"_title")}return this},getMessage:function(){return this.options.message},setMessage:function(t){return this.options.message=t,this.updateMessage(),this},updateMessage:function(){if(this.isRealized()){var t=this.createDynamicContent(this.getMessage());this.getModalBody().find("."+this.getNamespace("message")).html("").append(t)}return this},isClosable:function(){return this.options.closable},setClosable:function(t){return this.options.closable=t,this.updateClosable(),this},setCloseByBackdrop:function(t){return this.options.closeByBackdrop=t,this},canCloseByBackdrop:function(){return this.options.closeByBackdrop},setCloseByKeyboard:function(t){return this.options.closeByKeyboard=t,this},canCloseByKeyboard:function(){return this.options.closeByKeyboard},isAnimate:function(){return this.options.animate},setAnimate:function(t){return this.options.animate=t,this},updateAnimate:function(){return this.isRealized()&&this.getModal().toggleClass("fade",this.isAnimate()),this},getSpinicon:function(){return this.options.spinicon},setSpinicon:function(t){return this.options.spinicon=t,this},addButton:function(t){return this.options.buttons.push(t),this},addButtons:function(e){var n=this;return t.each(e,function(t,e){n.addButton(e)}),this},getButtons:function(){return this.options.buttons},setButtons:function(t){return this.options.buttons=t,this.updateButtons(),this},getButton:function(t){return"undefined"!=typeof this.indexedButtons[t]?this.indexedButtons[t]:null},getButtonSize:function(){return"undefined"!=typeof o.BUTTON_SIZES[this.getSize()]?o.BUTTON_SIZES[this.getSize()]:""},updateButtons:function(){return this.isRealized()&&(0===this.getButtons().length?this.getModalFooter().hide():this.getModalFooter().show().find("."+this.getNamespace("footer")).html("").append(this.createFooterButtons())),this},isAutodestroy:function(){return this.options.autodestroy},setAutodestroy:function(t){this.options.autodestroy=t},getDescription:function(){return this.options.description},setDescription:function(t){return this.options.description=t,this},setTabindex:function(t){return this.options.tabindex=t,this},getTabindex:function(){return this.options.tabindex},updateTabindex:function(){return this.isRealized()&&this.getModal().attr("tabindex",this.getTabindex()),this},getDefaultText:function(){return o.DEFAULT_TEXTS[this.getType()]},getNamespace:function(t){return o.NAMESPACE+"-"+t},createHeaderContent:function(){var e=t("
");return e.addClass(this.getNamespace("header")),e.append(this.createTitleContent()),e.prepend(this.createCloseButton()),e},createTitleContent:function(){var e=t("
");return e.addClass(this.getNamespace("title")),e},createCloseButton:function(){var e=t("
");e.addClass(this.getNamespace("close-button"));var n=t('');return e.append(n),e.on("click",{dialog:this},function(t){t.data.dialog.close()}),e},createBodyContent:function(){var e=t("
");return e.addClass(this.getNamespace("body")),e.append(this.createMessageContent()),e},createMessageContent:function(){var e=t("
");return e.addClass(this.getNamespace("message")),e},createFooterContent:function(){var e=t("
");return e.addClass(this.getNamespace("footer")),e},createFooterButtons:function(){var e=this,n=t("
");return n.addClass(this.getNamespace("footer-buttons")),this.indexedButtons={},t.each(this.options.buttons,function(t,i){i.id||(i.id=o.newGuid());var s=e.createButton(i);e.indexedButtons[i.id]=s,n.append(s)}),n},createButton:function(e){var n=t('');return n.prop("id",e.id),n.data("button",e),"undefined"!=typeof e.icon&&""!==t.trim(e.icon)&&n.append(this.createButtonIcon(e.icon)),"undefined"!=typeof e.label&&n.append(e.label),n.addClass("undefined"!=typeof e.cssClass&&""!==t.trim(e.cssClass)?e.cssClass:"btn-default"),"undefined"!=typeof e.hotkey&&(this.registeredButtonHotkeys[e.hotkey]=n),n.on("click",{dialog:this,$button:n,button:e},function(t){var e=t.data.dialog,n=t.data.$button,o=n.data("button");return o.autospin&&n.toggleSpin(!0),"function"==typeof o.action?o.action.call(n,e,t):void 0}),this.enhanceButton(n),"undefined"!=typeof e.enabled&&n.toggleEnable(e.enabled),n},enhanceButton:function(t){return t.dialog=this,t.toggleEnable=function(t){var e=this;return"undefined"!=typeof t?e.prop("disabled",!t).toggleClass("disabled",!t):e.prop("disabled",!e.prop("disabled")),e},t.enable=function(){var t=this;return t.toggleEnable(!0),t},t.disable=function(){var t=this;return t.toggleEnable(!1),t},t.toggleSpin=function(e){var n=this,o=n.dialog,i=n.find("."+o.getNamespace("button-icon"));return"undefined"==typeof e&&(e=!(t.find(".icon-spin").length>0)),e?(i.hide(),t.prepend(o.createButtonIcon(o.getSpinicon()).addClass("icon-spin"))):(i.show(),t.find(".icon-spin").remove()),n},t.spin=function(){var t=this;return t.toggleSpin(!0),t},t.stopSpin=function(){var t=this;return t.toggleSpin(!1),t},this},createButtonIcon:function(e){var n=t("");return n.addClass(this.getNamespace("button-icon")).addClass(e),n},enableButtons:function(e){return t.each(this.indexedButtons,function(t,n){n.toggleEnable(e)}),this},updateClosable:function(){return this.isRealized()&&this.getModalHeader().find("."+this.getNamespace("close-button")).toggle(this.isClosable()),this},onShow:function(t){return this.options.onshow=t,this},onShown:function(t){return this.options.onshown=t,this},onHide:function(t){return this.options.onhide=t,this},onHidden:function(t){return this.options.onhidden=t,this},isRealized:function(){return this.realized},setRealized:function(t){return this.realized=t,this},isOpened:function(){return this.opened},setOpened:function(t){return this.opened=t,this},handleModalEvents:function(){return this.getModal().on("show.bs.modal",{dialog:this},function(t){var e=t.data.dialog;if(e.setOpened(!0),e.isModalEvent(t)&&"function"==typeof e.options.onshow){var n=e.options.onshow(e);return n===!1&&e.setOpened(!1),n}}),this.getModal().on("shown.bs.modal",{dialog:this},function(t){var e=t.data.dialog;e.isModalEvent(t)&&"function"==typeof e.options.onshown&&e.options.onshown(e)}),this.getModal().on("hide.bs.modal",{dialog:this},function(t){var e=t.data.dialog;if(e.setOpened(!1),e.isModalEvent(t)&&"function"==typeof e.options.onhide){var n=e.options.onhide(e);return n===!1&&e.setOpened(!0),n}}),this.getModal().on("hidden.bs.modal",{dialog:this},function(e){var n=e.data.dialog;n.isModalEvent(e)&&"function"==typeof n.options.onhidden&&n.options.onhidden(n),n.isAutodestroy()&&(n.setRealized(!1),delete o.dialogs[n.getId()],t(this).remove()),o.moveFocus()}),this.handleModalBackdropEvent(),this.getModal().on("keyup",{dialog:this},function(t){27===t.which&&t.data.dialog.isClosable()&&t.data.dialog.canCloseByKeyboard()&&t.data.dialog.close()}),this.getModal().on("keyup",{dialog:this},function(e){var n=e.data.dialog;if("undefined"!=typeof n.registeredButtonHotkeys[e.which]){var o=t(n.registeredButtonHotkeys[e.which]);!o.prop("disabled")&&o.focus().trigger("click")}}),this},handleModalBackdropEvent:function(){return this.getModal().on("click",{dialog:this},function(e){t(e.target).hasClass("modal-backdrop")&&e.data.dialog.isClosable()&&e.data.dialog.canCloseByBackdrop()&&e.data.dialog.close()}),this},isModalEvent:function(t){return"undefined"!=typeof t.namespace&&"bs.modal"===t.namespace},makeModalDraggable:function(){return this.options.draggable&&(this.getModalHeader().addClass(this.getNamespace("draggable")).on("mousedown",{dialog:this},function(t){var e=t.data.dialog;e.draggableData.isMouseDown=!0;var n=e.getModalDialog().offset();e.draggableData.mouseOffset={top:t.clientY-n.top,left:t.clientX-n.left}}),this.getModal().on("mouseup mouseleave",{dialog:this},function(t){t.data.dialog.draggableData.isMouseDown=!1}),t("body").on("mousemove",{dialog:this},function(t){var e=t.data.dialog;e.draggableData.isMouseDown&&e.getModalDialog().offset({top:t.clientY-e.draggableData.mouseOffset.top,left:t.clientX-e.draggableData.mouseOffset.left})})),this},realize:function(){return this.initModalStuff(),this.getModal().addClass(o.NAMESPACE).addClass(this.getCssClass()),this.updateSize(),this.getDescription()&&this.getModal().attr("aria-describedby",this.getDescription()),this.getModalFooter().append(this.createFooterContent()),this.getModalHeader().append(this.createHeaderContent()),this.getModalBody().append(this.createBodyContent()),this.getModal().data("bs.modal",new n(this.getModal(),{backdrop:"static",keyboard:!1,show:!1})),this.makeModalDraggable(),this.handleModalEvents(),this.setRealized(!0),this.updateButtons(),this.updateType(),this.updateTitle(),this.updateMessage(),this.updateClosable(),this.updateAnimate(),this.updateSize(),this.updateTabindex(),this},open:function(){return!this.isRealized()&&this.realize(),this.getModal().modal("show"),this},close:function(){return!this.isRealized()&&this.realize(),this.getModal().modal("hide"),this}},o.prototype=t.extend(o.prototype,o.METHODS_TO_OVERRIDE[n.getModalVersion()]),o.newGuid=function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(t){var e=16*Math.random()|0,n="x"===t?e:3&e|8;return n.toString(16)})},o.show=function(t){return new o(t).open()},o.alert=function(){var e={},n={type:o.TYPE_PRIMARY,title:null,message:null,closable:!1,draggable:!1,buttonLabel:o.DEFAULT_TEXTS.OK,callback:null};return e="object"==typeof arguments[0]&&arguments[0].constructor==={}.constructor?t.extend(!0,n,arguments[0]):t.extend(!0,n,{message:arguments[0],callback:"undefined"!=typeof arguments[1]?arguments[1]:null}),new o({type:e.type,title:e.title,message:e.message,closable:e.closable,draggable:e.draggable,data:{callback:e.callback},onhide:function(t){!t.getData("btnClicked")&&t.isClosable()&&"function"==typeof t.getData("callback")&&t.getData("callback")(!1)},buttons:[{label:e.buttonLabel,action:function(t){return t.setData("btnClicked",!0),"function"==typeof t.getData("callback")&&t.getData("callback").call(this,!0)===!1?!1:t.close()}}]}).open()},o.confirm=function(){var e={},n={type:o.TYPE_PRIMARY,title:null,message:null,closable:!1,draggable:!1,btnCancelLabel:o.DEFAULT_TEXTS.CANCEL,btnOKLabel:o.DEFAULT_TEXTS.OK,btnOKClass:null,callback:null};return e="object"==typeof arguments[0]&&arguments[0].constructor==={}.constructor?t.extend(!0,n,arguments[0]):t.extend(!0,n,{message:arguments[0],closable:!1,buttonLabel:o.DEFAULT_TEXTS.OK,callback:"undefined"!=typeof arguments[1]?arguments[1]:null}),null===e.btnOKClass&&(e.btnOKClass=["btn",e.type.split("-")[1]].join("-")),new o({type:e.type,title:e.title,message:e.message,closable:e.closable,draggable:e.draggable,data:{callback:e.callback},buttons:[{label:e.btnCancelLabel,action:function(t){return"function"==typeof t.getData("callback")&&t.getData("callback").call(this,!1)===!1?!1:t.close()}},{label:e.btnOKLabel,cssClass:e.btnOKClass,action:function(t){return"function"==typeof t.getData("callback")&&t.getData("callback").call(this,!0)===!1?!1:t.close()}}]}).open()},o.warning=function(t,e){return new o({type:o.TYPE_WARNING,message:t}).open()},o.danger=function(t,e){return new o({type:o.TYPE_DANGER,message:t}).open()},o.success=function(t,e){return new o({type:o.TYPE_SUCCESS,message:t}).open()},o}); \ No newline at end of file diff --git a/html/popup.html b/html/popup.html index e9b48e6..74e67b7 100644 --- a/html/popup.html +++ b/html/popup.html @@ -8,6 +8,7 @@ + @@ -42,7 +43,7 @@ - +
@@ -122,7 +123,7 @@
+ id="hashStatus" class="btn btn-primary btn-xs" target="_blank">

@@ -151,8 +152,10 @@ + + diff --git a/img/clearurls.png b/img/clearurls.png new file mode 100644 index 0000000..0f4c3b6 Binary files /dev/null and b/img/clearurls.png differ diff --git a/img/clearurls_gray.png b/img/clearurls_gray.png new file mode 100644 index 0000000..61e5e91 Binary files /dev/null and b/img/clearurls_gray.png differ diff --git a/manifest.json b/manifest.json index 10c149f..879b9f6 100644 --- a/manifest.json +++ b/manifest.json @@ -54,6 +54,7 @@ ], "background": { "scripts": [ + "browser-polyfill.js", "external_js/jquery-3.2.1.min.js", "external_js/sha256.jquery.js", "clearurls.js" @@ -61,7 +62,10 @@ }, "content_scripts": [ { - "matches": [""] + "matches": [""], + "js": [ + "browser-polyfill.js" + ] } ], "options_ui": {