Index: README.md
===================================================================
--- a/README.md
+++ b/README.md
@@ -97,8 +97,6 @@
creating the link to the version-specific release notes
* `addSubscription=true`: this parameter should trigger a dialog for adding
subscriptions as initiated by clicking on an "abp:subscribe" link
-* `filterError=true`: causes filter validation to fail, showing validation
- errors when adding new filters on the options page
* `blockedURLs`: a comma-separated list of URLs that should be considered
blocked (necessary to test the check for blocked scripts in sharing buttons).
* `downloadStatus`: sets downloadStatus parameter for filter lists, can be used
Index: background.js
===================================================================
--- a/background.js
+++ b/background.js
@@ -71,7 +71,6 @@
blockedURLs: "",
filterlistsReinitialized: false,
addSubscription: false,
- filterError: false,
downloadStatus: "synchronize_ok",
showNotificationUI: false,
showPageOptions: false
@@ -313,7 +312,10 @@
{
this.text = text;
this.disabled = false;
+ if (Filter.elemhideRegExp.test(text))
+ this.selector = RegExp.$3;
}
+ Filter.elemhideRegExp = /^([^/*|@"!]*?)#([@?])?#(.+)$/;
Filter.fromText = (text) => new Filter(text);
function BlockingFilter()
@@ -331,24 +333,65 @@
RegExpFilter
};
+ const isValidCSSSelector = selector =>
+ {
+ if (!selector)
+ return true;
+ try
+ {
+ document.documentElement.matches(selector);
+ return true;
+ }
+ catch (error)
+ {
+ return false;
+ }
+ };
+
modules.filterValidation =
{
+ // to test failing filters
+ // use one or more bad CSS selectors
+ // or start the line with a [
parseFilter(text)
{
- if (params.filterError)
- return {error: "Invalid filter"};
- return {filter: modules.filterClasses.Filter.fromText(text)};
+ let filter = null;
+ if (text)
+ {
+ if (text[0] == "[")
+ return {error: {reason: "Unexpected filter list header"}};
+
+ filter = modules.filterClasses.Filter.fromText(text);
+
+ if (!isValidCSSSelector(filter.selector))
+ {
+ return {error: {reason: "Invalid CSS selector"}};
+ }
+ }
+
+ return {filter};
},
parseFilters(text)
{
- if (params.filterError)
- return {errors: ["Invalid filter"]};
- return {
- filters: text.split("\n")
- .filter((filter) => !!filter)
- .map(modules.filterClasses.Filter.fromText),
- errors: []
- };
+ let lines = text.split("\n");
+ let filters = [];
+ let errors = [];
+
+ for (let i = 0; i < lines.length; i++)
+ {
+ let {filter, error} = this.parseFilter(lines[i]);
+
+ if (filter)
+ filters.push(filter);
+
+ if (error)
+ {
+ error.lineno = i + 1;
+ errors.push(error);
+ }
+ }
+
+ return {filters, errors};
}
};
Index: desktop-options.html
===================================================================
--- a/desktop-options.html
+++ b/desktop-options.html
@@ -289,6 +289,9 @@
@@ -297,18 +300,22 @@
-
+
-
Index: desktop-options.js
===================================================================
--- a/desktop-options.js
+++ b/desktop-options.js
@@ -547,14 +547,17 @@
return null;
}
- function sendMessageHandleErrors(message, onSuccess)
+ function sendMessageHandleErrors(message, callback)
{
browser.runtime.sendMessage(message, (errors) =>
{
- if (errors.length > 0)
- alert(errors.join("\n"));
- else if (onSuccess)
- onSuccess();
+ if (callback)
+ {
+ if (errors.length > 0)
+ callback(errors);
+ else
+ callback();
+ }
});
}
@@ -648,14 +651,37 @@
});
break;
case "save-custom-filters":
+ const filters = E("custom-filters-raw").value;
sendMessageHandleErrors({
type: "filters.importRaw",
- text: E("custom-filters-raw").value,
+ text: filters,
removeExisting: true
},
- () =>
+ (errors) =>
{
- setCustomFiltersView("read");
+ if (errors)
+ {
+ E("custom-filters").classList.add("warning");
+ const customFiltersError = clearAndGetCustomFiltersError();
+
+ // The current error does not contain info about the line
+ // that generated such error.
+ // Whenever the error object will pass the bad filter
+ // within its properties, this split should be removed.
+ const lines = filters.split("\n");
+ const messages = errors.map(error => lines[error.lineno - 1]);
+ for (const message of messages)
+ {
+ const li = document.createElement("li");
+ customFiltersError.appendChild(li).textContent = message;
+ }
+ if (errors.length > 5)
+ customFiltersError.classList.add("many");
+ }
+ else
+ {
+ setCustomFiltersView("read");
+ }
});
break;
case "show-more-filters-section":
@@ -749,12 +775,22 @@
}
}
+ function clearAndGetCustomFiltersError()
+ {
+ const customFiltersError = E("custom-filters-error");
+ customFiltersError.textContent = "";
+ customFiltersError.classList.remove("many");
+ return customFiltersError;
+ }
+
function setCustomFiltersView(mode)
{
let customFiltersElement = E("custom-filters-raw");
updateCustomFiltersUi();
if (mode == "read")
{
+ E("custom-filters").classList.remove("warning");
+ clearAndGetCustomFiltersError();
customFiltersElement.disabled = true;
if (!customFiltersElement.value)
{
@@ -918,10 +954,10 @@
{
setLinks("enable-acceptable-ads-description", link);
});
- setElementText(E("tracking-warning-1"), "options_tracking_warning_1",
+ setElementText(E("tracking-warning-1"), "options_tracking_warning_1",
[getMessage("common_feature_privacy_title"),
- getMessage("options_acceptableAds_ads_label")]);
- setElementText(E("tracking-warning-3"), "options_tracking_warning_3",
+ getMessage("options_acceptableAds_ads_label")]);
+ setElementText(E("tracking-warning-3"), "options_tracking_warning_3",
[getMessage("options_acceptableAds_privacy_label")]);
getDocLink("privacy_friendly_ads", (link) =>
@@ -963,7 +999,8 @@
getDocLink("filterdoc", (link) =>
{
- E("link-filters").setAttribute("href", link);
+ E("link-filters-1").setAttribute("href", link);
+ E("link-filters-2").setAttribute("href", link);
});
getDocLink("subscriptions", (link) =>
@@ -1158,11 +1195,7 @@
browser.runtime.sendMessage({
type: "filters.get",
subscriptionUrl: subscription.url
- },
- (filters) =>
- {
- loadCustomFilters(filters);
- });
+ }, loadCustomFilters);
}
});
loadRecommendations();
@@ -1295,7 +1328,7 @@
updateSubscription(subscription);
break;
case "added":
- let {url, recommended} = subscription;
+ let {url} = subscription;
// Handle custom subscription
if (/^~user/.test(url))
{
Index: locale/en_US/desktop-options.json
===================================================================
--- a/locale/en_US/desktop-options.json
+++ b/locale/en_US/desktop-options.json
@@ -287,7 +287,11 @@
},
"options_customFilters_learn": {
"description": "Custom filter widget(empty) link text in Advanced tab",
- "message": "Learn how to write filter lists"
+ "message": "Learn how to write filter lists."
+ },
+ "options_customFilters_edit_error": {
+ "description": "Custom filters editing text in Advanced tab",
+ "message": "Something's not right. Please check the following filters:"
},
"options_customFilters_edit_placeholder": {
"description": "Custom filter widget(empty) placeholder text in Advanced tab",
Index: messageResponder.js
===================================================================
--- a/messageResponder.js
+++ b/messageResponder.js
@@ -269,7 +269,9 @@
for (let error of result.errors)
{
if (error.type != "unexpected-filter-list-header")
- errors.push(error.toString());
+ {
+ errors.push(convertObject(["lineno", "reason"], error));
+ }
}
if (errors.length > 0)
Index: skin/desktop-options.css
===================================================================
--- a/skin/desktop-options.css
+++ b/skin/desktop-options.css
@@ -219,6 +219,7 @@
{
margin: 0.8rem 0rem;
display: flex;
+ align-self: center;
justify-content: flex-end;
}
@@ -544,7 +545,7 @@
#sidebar
{
- flex-shrink: 0;
+ flex-shrink: 0;
}
#sidebar .fixed
@@ -1134,17 +1135,14 @@
#custom-filters[data-mode="empty"] #custom-filters-raw,
#custom-filters:not([data-mode="write"]) #custom-filters-raw-controls,
#custom-filters:not([data-mode="read"]) #custom-filters-edit,
+#custom-filters-error,
.state span,
#acceptable-ads:not(.show-dnt-notification) #dnt
{
display: none;
}
-#all-filter-lists-table button[role="checkbox"][aria-checked="true"] + .state #state-active
-{
- display: inline;
-}
-
+#all-filter-lists-table button[role="checkbox"][aria-checked="true"] + .state #state-active,
#all-filter-lists-table button[role="checkbox"][aria-checked="false"] + .state #state-disabled
{
display: inline;
@@ -1155,11 +1153,71 @@
margin-bottom: 0.8rem;
}
+#custom-filters-edit
+{
+ white-space: nowrap;
+}
+
+#custom-filters-control
+{
+ display: flex;
+ width: 100%;
+}
+
+/*
+ avoid doubled margin glitchy behavior
+ as shown in visual specifications
+*/
+#custom-filters-control .side-controls .side-controls
+{
+ margin: 0;
+}
+
+/*
+ visibility: hidden;
+ preserves the flex-box meaning,
+ keeping buttons on the right.
+*/
+#custom-filters-edit-error
+{
+ visibility: hidden;
+ display: flex;
+ align-self: center;
+ justify-content: flex-start;
+ font-weight: bold;
+}
+
+#custom-filters.warning #custom-filters-edit-error,
+#custom-filters-error
+{
+ color: #C11D26;
+ padding: 0.2rem 0.8rem;
+}
+
+#custom-filters.warning #custom-filters-edit-error
+{
+ visibility: visible;
+}
+
+#custom-filters-error
+{
+ display: block;
+ list-style: none;
+ white-space: pre;
+ overflow: auto;
+}
+
+#custom-filters-error.many
+{
+ border: 1px solid silver;
+ max-height: 6.2rem;
+}
+
#custom-filters-raw
{
width: 100%;
height: 23.6rem;
- padding: 0.8rem;
+ padding: 0.8rem;
}
#custom-filters-raw:focus
@@ -1168,6 +1226,12 @@
border: 2px solid #077CA6;
}
+#custom-filters.warning #custom-filters-raw
+{
+ outline: none;
+ border: 2px solid #C11D26;
+}
+
#empty-custom-filters
{
padding: 1.5rem;