From 822e0a133de99158f3ff76c46e127f9295d40f8c Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 19 Jun 2019 18:28:44 -0400 Subject: [PATCH] Provide visual feedback for invalid entries in "My rules" Related issue: - https://github.com/gorhill/uBlock/issues/1039 --- src/dyna-rules.html | 2 + src/js/codemirror/ubo-dynamic-filtering.js | 187 +++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 src/js/codemirror/ubo-dynamic-filtering.js diff --git a/src/dyna-rules.html b/src/dyna-rules.html index 87bc10a0c..12353f354 100644 --- a/src/dyna-rules.html +++ b/src/dyna-rules.html @@ -48,6 +48,8 @@ + + diff --git a/src/js/codemirror/ubo-dynamic-filtering.js b/src/js/codemirror/ubo-dynamic-filtering.js new file mode 100644 index 000000000..cc493e484 --- /dev/null +++ b/src/js/codemirror/ubo-dynamic-filtering.js @@ -0,0 +1,187 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +/* global CodeMirror */ + +'use strict'; + +CodeMirror.defineMode('ubo-dynamic-filtering', ( ) => { + + const validSwitches = new Set([ + 'no-strict-blocking:', + 'no-popups:', + 'no-cosmetic-filtering:', + 'no-remote-fonts:', + 'no-large-media:', + 'no-csp-reports:', + 'no-scripting:', + ]); + const validSwitcheStates = new Set([ + 'true', + 'false', + ]); + const validTypes = new Set([ + '*', + '3p', + 'image', + 'inline-script', + '1p-script', + '3p-script', + '3p-frame', + ]); + const validActions = new Set([ + 'block', + 'allow', + 'noop', + ]); + const reIsNotHostname = /[:/#?*]/; + const tokens = []; + + const isSwitchRule = ( ) => { + const token = tokens[0]; + return token.charCodeAt(token.length-1) === 0x3A /* ':' */; + }; + + const isURLRule = ( ) => { + return /^[\w-_]+:\/\/[^/]+\//.test(tokens[1]); + }; + + const skipToEnd = (stream, style = null) => { + stream.skipToEnd(); + return style; + }; + + const token = stream => { + if ( stream.sol() ) { tokens.length = 0; } + stream.eatSpace(); + const match = stream.match(/\S+/); + if ( Array.isArray(match) === false ) { + return skipToEnd(stream); + } + if ( tokens.length === 4 ) { + return skipToEnd(stream, 'error'); + } + const token = match[0]; + tokens.push(token); + // Field 1: per-site switch or hostname + if ( tokens.length === 1 ) { + if ( isSwitchRule(token) ) { + if ( validSwitches.has(token) === false ) { + return skipToEnd(stream, 'error'); + } + } else if ( reIsNotHostname.test(token) && token !== '*' ) { + return skipToEnd(stream, 'error'); + } + return null; + } + // Field 2: hostname or url + if ( tokens.length === 2 ) { + if ( isSwitchRule(tokens[0]) ) { + if ( reIsNotHostname.test(token) && token !== '*' ) { + return skipToEnd(stream, 'error'); + } + } + if ( + reIsNotHostname.test(token) && + token !== '*' && + isURLRule(token) === false + ) { + return skipToEnd(stream, 'error'); + } + return null; + } + // Field 3 + if ( tokens.length === 3 ) { + if ( isSwitchRule(tokens[0]) ) { + if ( validSwitcheStates.has(token) === false ) { + return skipToEnd(stream, 'error'); + } + return null; + } + if ( isURLRule(tokens[1]) === false ) { + if ( + tokens[1] !== '*' && token !== '*' || + tokens[1] === '*' && validTypes.has(token) === false + ) { + return skipToEnd(stream, 'error'); + } + } + return null; + } + // Field 4 + if ( tokens.length === 4 ) { + if ( + isSwitchRule(tokens[0]) || + validActions.has(token) === false + ) { + return skipToEnd(stream, 'error'); + } + return null; + } + return skipToEnd(stream); + }; + + return { token }; +}); + +/* +Code below is to address +https://github.com/uBlockOrigin/uMatrix-issues/issues/128 + +But this needs fixing because glitchiness in some cases. +I may end up having to create a custom merge view rather +than using the existing CodeMirror one. + +CodeMirror.registerHelper('fold', 'ubo-dynamic-filtering', (cm, start) => { + function isHeader(lineNo) { + const tokentype = cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0)); + return tokentype && /\bheader\b/.test(tokentype); + } + + function headerLevel(lineNo, line, nextLine) { + let match = line && line.match(/^#+/); + if (match && isHeader(lineNo)) return match[0].length; + match = nextLine && nextLine.match(/^[=\-]+\s*$/); + if (match && isHeader(lineNo + 1)) return nextLine[0] === '=' ? 1 : 2; + return 100; + } + + const firstLine = cm.getLine(start.line); + let nextLine = cm.getLine(start.line + 1); + const level = headerLevel(start.line, firstLine, nextLine); + if ( level === 100 ) { return; } + + const lastLineNo = cm.lastLine(); + let end = start.line, + nextNextLine = cm.getLine(end + 2); + while ( end < lastLineNo ) { + if ( headerLevel(end + 1, nextLine, nextNextLine) <= level ) { break; } + ++end; + nextLine = nextNextLine; + nextNextLine = cm.getLine(end + 2); + } + + return { + from: CodeMirror.Pos(start.line, firstLine.length), + to: CodeMirror.Pos(end, cm.getLine(end).length), + }; +}); +*/