diff --git a/src/css/whitelist.css b/src/css/whitelist.css index b017cf0cd..b86296046 100644 --- a/src/css/whitelist.css +++ b/src/css/whitelist.css @@ -5,36 +5,7 @@ div > p:last-child { margin-bottom: 0; } #whitelist { - border: 1px solid gray; height: 60vh; - margin: 0; - padding: 1px; - position: relative; - resize: vertical; - } -#whitelist.invalid { - border-color: red; - } -#whitelist textarea { - border: none; - box-sizing: border-box; - height: 100%; - padding: 0.4em; - resize: none; text-align: left; - white-space: pre; width: 100%; } -#whitelist textarea + div { - background-color: red; - bottom: 0; - color: white; - display: none; - padding: 2px 4px; - pointer-events: none; - position: absolute; - right: 0; -} -#whitelist.invalid textarea + div { - display: block; -} diff --git a/src/js/messaging.js b/src/js/messaging.js index d4c1dc17d..95e716751 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -135,7 +135,11 @@ var onMessage = function(request, sender, callback) { break; case 'getWhitelist': - response = µb.stringFromWhitelist(µb.netWhitelist); + response = { + whitelist: µb.stringFromWhitelist(µb.netWhitelist), + reBadHostname: µb.reWhitelistBadHostname.source, + reHostnameExtractor: µb.reWhitelistHostnameExtractor.source + }; break; case 'launchElementPicker': @@ -985,10 +989,6 @@ var onMessage = function(request, sender, callback) { resetUserData(); break; - case 'validateWhitelistString': - response = µb.validateWhitelistString(request.raw); - break; - case 'writeHiddenSettings': µb.hiddenSettings = µb.hiddenSettingsFromString(request.content); µb.saveHiddenSettings(); diff --git a/src/js/ublock.js b/src/js/ublock.js index 55a7ebbd3..8f5698e5e 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -219,7 +219,7 @@ var matchBucket = function(url, hostname, bucket, start) { } // Plain hostname else if ( line.indexOf('/') === -1 ) { - if ( reInvalidHostname.test(line) ) { + if ( this.reWhitelistBadHostname.test(line) ) { key = '#'; directive = '# ' + line; } else { @@ -242,7 +242,7 @@ var matchBucket = function(url, hostname, bucket, start) { // label (or else it would be just impossible to make an efficient // dict. else { - matches = reHostnameExtractor.exec(line); + matches = this.reWhitelistHostnameExtractor.exec(line); if ( !matches || matches.length !== 2 ) { key = '#'; directive = '# ' + line; @@ -266,27 +266,8 @@ var matchBucket = function(url, hostname, bucket, start) { return whitelist; }; -µBlock.validateWhitelistString = function(s) { - var lineIter = new this.LineIterator(s), line; - while ( !lineIter.eot() ) { - line = lineIter.next().trim(); - if ( line === '' ) { continue; } - if ( line.startsWith('#') ) { continue; } // Comment - if ( line.indexOf('/') === -1 ) { // Plain hostname - if ( reInvalidHostname.test(line) ) { return false; } - continue; - } - if ( line.length > 2 && line.startsWith('/') && line.endsWith('/') ) { // Regex-based - try { new RegExp(line.slice(1, -1)); } catch(ex) { return false; } - continue; - } - if ( reHostnameExtractor.test(line) === false ) { return false; } // URL - } - return true; -}; - -var reInvalidHostname = /[^a-z0-9.\-\[\]:]/, - reHostnameExtractor = /([a-z0-9\[][a-z0-9.\-]*[a-z0-9\]])(?::[\d*]+)?\/(?:[^\x00-\x20\/]|$)[^\x00-\x20]*$/; +µBlock.reWhitelistBadHostname = /[^a-z0-9.\-\[\]:]/; +µBlock.reWhitelistHostnameExtractor = /([a-z0-9\[][a-z0-9.\-]*[a-z0-9\]])(?::[\d*]+)?\/(?:[^\x00-\x20\/]|$)[^\x00-\x20]*$/; /******************************************************************************/ diff --git a/src/js/whitelist.js b/src/js/whitelist.js index 52a3a4d7c..c804b8c7b 100644 --- a/src/js/whitelist.js +++ b/src/js/whitelist.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2016 Raymond Hill + Copyright (C) 2014-2018 Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,79 +19,94 @@ Home: https://github.com/gorhill/uBlock */ -/* global uDom, uBlockDashboard */ - -/******************************************************************************/ - -(function() { +/* global CodeMirror, uDom, uBlockDashboard */ 'use strict'; /******************************************************************************/ +(function() { + +/******************************************************************************/ + +CodeMirror.defineMode("ubo-whitelist-directives", function() { + var reComment = /^\s*#/, + reRegex = /^\/.+\/$/; + + return { + token: function(stream) { + var line = stream.string.trim(); + stream.skipToEnd(); + if ( reBadHostname === undefined ) { + return null; + } + if ( reComment.test(line) ) { + return 'comment'; + } + if ( line.indexOf('/') === -1 ) { + return reBadHostname.test(line) ? 'error' : null; + } + if ( reRegex.test(line) ) { + try { + new RegExp(line.slice(1, -1)); + } catch(ex) { + return 'error'; + } + return null; + } + return reHostnameExtractor.test(line) ? null : 'error'; + } + }; +}); + +var reBadHostname, + reHostnameExtractor; + +/******************************************************************************/ + var messaging = vAPI.messaging, - cachedWhitelist = ''; + cachedWhitelist = '', + noopFunc = function(){}; + +var cmEditor = new CodeMirror( + document.getElementById('whitelist'), + { + autofocus: true, + inputStyle: 'contenteditable', + lineNumbers: true, + lineWrapping: true, + styleActiveLine: true + } +); /******************************************************************************/ -var getTextareaNode = function() { - var me = getTextareaNode, - node = me.theNode; - if ( node === undefined ) { - node = me.theNode = uDom.nodeFromSelector('#whitelist textarea'); - } - return node; +var whitelistChanged = function() { + var whitelistElem = uDom.nodeFromId('whitelist'); + var bad = whitelistElem.querySelector('.cm-error') !== null; + var changedWhitelist = cmEditor.getValue().trim(); + var changed = changedWhitelist !== cachedWhitelist; + uDom.nodeFromId('whitelistApply').disabled = !changed || bad; + uDom.nodeFromId('whitelistRevert').disabled = !changed; + CodeMirror.commands.save = changed && !bad ? applyChanges : noopFunc; }; -var setErrorNodeHorizontalOffset = function(px) { - var me = setErrorNodeHorizontalOffset, - offset = me.theOffset || 0; - if ( px === offset ) { return; } - var node = me.theNode; - if ( node === undefined ) { - node = me.theNode = uDom.nodeFromSelector('#whitelist textarea + div'); - } - node.style.right = px + 'px'; - me.theOffset = px; -}; - -/******************************************************************************/ - -var whitelistChanged = (function() { - var changedWhitelist, changed, timer; - - var updateUI = function(good) { - uDom.nodeFromId('whitelistApply').disabled = changed || !good; - uDom.nodeFromId('whitelistRevert').disabled = changed; - uDom.nodeFromId('whitelist').classList.toggle('invalid', !good); - }; - - var validate = function() { - timer = undefined; - messaging.send( - 'dashboard', - { what: 'validateWhitelistString', raw: changedWhitelist }, - updateUI - ); - }; - - return function() { - changedWhitelist = getTextareaNode().value.trim(); - changed = changedWhitelist === cachedWhitelist; - if ( timer !== undefined ) { clearTimeout(timer); } - timer = vAPI.setTimeout(validate, 251); - var textarea = getTextareaNode(); - setErrorNodeHorizontalOffset(textarea.offsetWidth - textarea.clientWidth); - }; -})(); +cmEditor.on('changes', whitelistChanged); /******************************************************************************/ var renderWhitelist = function() { - var onRead = function(whitelist) { - cachedWhitelist = whitelist.trim(); - getTextareaNode().value = cachedWhitelist + '\n'; - whitelistChanged(); + var onRead = function(details) { + var first = reBadHostname === undefined; + if ( first ) { + reBadHostname = new RegExp(details.reBadHostname); + reHostnameExtractor = new RegExp(details.reHostnameExtractor); + } + cachedWhitelist = details.whitelist.trim(); + cmEditor.setValue(cachedWhitelist + '\n'); + if ( first ) { + cmEditor.clearHistory(); + } }; messaging.send('dashboard', { what: 'getWhitelist' }, onRead); }; @@ -100,17 +115,16 @@ var renderWhitelist = function() { var handleImportFilePicker = function() { var fileReaderOnLoadHandler = function() { - var textarea = getTextareaNode(); - textarea.value = [textarea.value.trim(), this.result.trim()].join('\n').trim(); - whitelistChanged(); + cmEditor.setValue( + [ + cmEditor.getValue().trim(), + this.result.trim() + ].join('\n').trim() + ); }; var file = this.files[0]; - if ( file === undefined || file.name === '' ) { - return; - } - if ( file.type.indexOf('text') !== 0 ) { - return; - } + if ( file === undefined || file.name === '' ) { return; } + if ( file.type.indexOf('text') !== 0 ) { return; } var fr = new FileReader(); fr.onload = fileReaderOnLoadHandler; fr.readAsText(file); @@ -130,7 +144,7 @@ var startImportFilePicker = function() { /******************************************************************************/ var exportWhitelistToFile = function() { - var val = getTextareaNode().value.trim(); + var val = cmEditor.getValue().trim(); if ( val === '' ) { return; } var filename = vAPI.i18n('whitelistExportFilename') .replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString()) @@ -144,35 +158,35 @@ var exportWhitelistToFile = function() { /******************************************************************************/ var applyChanges = function() { - cachedWhitelist = getTextareaNode().value.trim(); - var request = { - what: 'setWhitelist', - whitelist: cachedWhitelist - }; - messaging.send('dashboard', request, renderWhitelist); + cachedWhitelist = cmEditor.getValue().trim(); + messaging.send( + 'dashboard', + { + what: 'setWhitelist', + whitelist: cachedWhitelist + }, + renderWhitelist + ); }; var revertChanges = function() { - getTextareaNode().value = cachedWhitelist + '\n'; - whitelistChanged(); + var content = cachedWhitelist; + if ( content !== '' ) { content += '\n'; } + cmEditor.setValue(content); }; /******************************************************************************/ var getCloudData = function() { - return getTextareaNode().value; + return cmEditor.getValue(); }; var setCloudData = function(data, append) { - if ( typeof data !== 'string' ) { - return; - } - var textarea = getTextareaNode(); + if ( typeof data !== 'string' ) { return; } if ( append ) { - data = uBlockDashboard.mergeNewLines(textarea.value.trim(), data); + data = uBlockDashboard.mergeNewLines(cmEditor.getValue().trim(), data); } - textarea.value = data.trim() + '\n'; - whitelistChanged(); + cmEditor.setValue(data.trim() + '\n'); }; self.cloud.onPush = getCloudData; @@ -183,7 +197,6 @@ self.cloud.onPull = setCloudData; uDom('#importWhitelistFromFile').on('click', startImportFilePicker); uDom('#importFilePicker').on('change', handleImportFilePicker); uDom('#exportWhitelistToFile').on('click', exportWhitelistToFile); -uDom('#whitelist textarea').on('input', whitelistChanged); uDom('#whitelistApply').on('click', applyChanges); uDom('#whitelistRevert').on('click', revertChanges); diff --git a/src/whitelist.html b/src/whitelist.html index b2d724fa3..344472511 100644 --- a/src/whitelist.html +++ b/src/whitelist.html @@ -4,10 +4,15 @@
-
+ + + + + + + + +