Left: | ||
Right: |
OLD | NEW |
---|---|
1 /* | 1 /* |
2 * This file is part of Adblock Plus <https://adblockplus.org/>, | 2 * This file is part of Adblock Plus <https://adblockplus.org/>, |
3 * Copyright (C) 2006-present eyeo GmbH | 3 * Copyright (C) 2006-present eyeo GmbH |
4 * | 4 * |
5 * Adblock Plus is free software: you can redistribute it and/or modify | 5 * Adblock Plus is free software: you can redistribute it and/or modify |
6 * it under the terms of the GNU General Public License version 3 as | 6 * it under the terms of the GNU General Public License version 3 as |
7 * published by the Free Software Foundation. | 7 * published by the Free Software Foundation. |
8 * | 8 * |
9 * Adblock Plus is distributed in the hope that it will be useful, | 9 * Adblock Plus is distributed in the hope that it will be useful, |
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 * GNU General Public License for more details. | 12 * GNU General Public License for more details. |
13 * | 13 * |
14 * You should have received a copy of the GNU General Public License | 14 * You should have received a copy of the GNU General Public License |
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
16 */ | 16 */ |
17 | 17 |
18 "use strict"; | 18 "use strict"; |
19 | 19 |
20 let lastFilterQuery = null; | 20 let lastFilterQuery = null; |
21 | 21 |
22 browser.runtime.sendMessage({type: "types.get"}, | 22 browser.runtime.sendMessage({type: "types.get"}, |
23 (filterTypes) => | 23 (filterTypes) => |
24 { | 24 { |
25 let filterTypesElem = document.getElementById("filter-type"); | 25 let filterTypesElem = document.getElementById("filter-type"); |
26 let filterStyleElem = document.createElement("style"); | |
27 for (let type of filterTypes) | 26 for (let type of filterTypes) |
28 { | 27 { |
29 filterStyleElem.innerHTML += | |
30 `#items[data-filter-type=${type}] tr:not([data-type=${type}])` + | |
31 "{display: none;}"; | |
32 let optionNode = document.createElement("option"); | 28 let optionNode = document.createElement("option"); |
33 optionNode.appendChild(document.createTextNode(type)); | 29 optionNode.appendChild(document.createTextNode(type)); |
34 filterTypesElem.appendChild(optionNode); | 30 filterTypesElem.appendChild(optionNode); |
35 } | 31 } |
36 document.body.appendChild(filterStyleElem); | |
37 }); | 32 }); |
38 | 33 |
39 function generateFilter(request, domainSpecific) | 34 function generateFilter(request, domainSpecific) |
40 { | 35 { |
41 let filter = request.url.replace(/^[\w-]+:\/+(?:www\.)?/, "||"); | 36 let filter = request.url.replace(/^[\w-]+:\/+(?:www\.)?/, "||"); |
42 let options = []; | 37 let options = []; |
43 | 38 |
44 if (request.type == "POPUP") | 39 if (request.type == "POPUP") |
45 { | 40 { |
46 options.push("popup"); | 41 options.push("popup"); |
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
183 row.classList.remove("filtered-by-search"); | 178 row.classList.remove("filtered-by-search"); |
184 } | 179 } |
185 } | 180 } |
186 | 181 |
187 function cancelSearch(table) | 182 function cancelSearch(table) |
188 { | 183 { |
189 for (let row of table.rows) | 184 for (let row of table.rows) |
190 row.classList.remove("filtered-by-search"); | 185 row.classList.remove("filtered-by-search"); |
191 } | 186 } |
192 | 187 |
188 /** | |
Thomas Greiner
2018/02/28 18:18:08
In comparison to how we did the record filtering b
a.giammarchi
2018/02/28 19:07:13
before we were not doing anything, it was a hack w
| |
189 * Return a generic <tr> placeholder | |
190 * double linked with the <tr> itself to simplify | |
191 * tbody swaps / filtering of undesired rows. | |
192 * @param {HTMLTableRowElement} tr | |
193 * @returns {Comment} | |
194 */ | |
195 function getPlaceholder(tr) | |
196 { | |
197 let placeholder = tr.placeholder; | |
198 if (!placeholder) | |
199 { | |
200 placeholder = document.createComment("placeholder"); | |
201 tr.placeholder = placeholder; | |
202 placeholder.placeholder = tr; | |
203 } | |
204 return placeholder; | |
205 } | |
206 | |
207 /** | |
208 * Put back all hidden table rows and eventually | |
209 * filters those that do not match the state or type. | |
210 * @param {HTMLTableSectionElement} tbody | |
211 * @param {DOMStringMap} dataset | |
212 */ | |
213 function updateRows(tbody, dataset) | |
214 { | |
215 // use filters to match nodes or retrieve them later on | |
216 const selector = getCSSFilter(dataset); | |
217 for (const node of tbody.childNodes) | |
218 { | |
219 // comments linked to table rows that | |
220 // won't match selector, will be used | |
221 // to place their linked row back in | |
222 if ( | |
223 node.nodeType === Node.COMMENT_NODE && | |
224 (!selector || !node.placeholder.matches(selector)) | |
225 ) | |
226 { | |
227 tbody.replaceChild(node.placeholder, node); | |
228 } | |
229 } | |
230 | |
231 // if there is a list of nodes to filter | |
232 if (selector) | |
233 { | |
234 // every matched row should bre replaced with its placeholder | |
235 for (const tr of tbody.querySelectorAll(selector)) | |
236 { | |
237 tbody.replaceChild(getPlaceholder(tr), tr); | |
238 } | |
239 } | |
240 } | |
241 | |
242 function getCSSFilter(dataset) | |
Thomas Greiner
2018/02/28 18:18:08
Detail: This name confused me a bit because (a) "f
a.giammarchi
2018/02/28 19:07:13
it actually returns a CSS selector that filters ro
| |
243 { | |
244 const {filterState, filterType} = dataset; | |
245 | |
246 // create a list of possible CSS filter | |
247 // excluding #background-items from the list of rows to consider | |
248 const query = []; | |
249 if (filterState) | |
250 { | |
251 query.push(`tr:not(#background-items):not([data-state="${filterState}"])`); | |
252 } | |
253 if (filterType) | |
254 { | |
255 query.push(`tr:not(#background-items):not([data-type="${filterType}"])`); | |
256 } | |
257 return query.join(","); | |
258 } | |
259 | |
193 document.addEventListener("DOMContentLoaded", () => | 260 document.addEventListener("DOMContentLoaded", () => |
194 { | 261 { |
195 let container = document.getElementById("items"); | 262 let container = document.getElementById("items"); |
196 let table = container.querySelector("tbody"); | 263 let table = container.querySelector("tbody"); |
264 let bgItems = container.querySelector("#background-items"); | |
197 let template = document.querySelector("template").content.firstElementChild; | 265 let template = document.querySelector("template").content.firstElementChild; |
266 const records = []; | |
198 | 267 |
199 document.getElementById("reload").addEventListener("click", () => | 268 document.getElementById("reload").addEventListener("click", () => |
200 { | 269 { |
201 ext.devtools.inspectedWindow.reload(); | 270 ext.devtools.inspectedWindow.reload(); |
202 }, false); | 271 }, false); |
203 | 272 |
204 document.getElementById("filter-state").addEventListener("change", (event) => | 273 document.getElementById("filter-state").addEventListener("change", (event) => |
205 { | 274 { |
206 container.dataset.filterState = event.target.value; | 275 container.dataset.filterState = event.target.value; |
276 updateRows(table, container.dataset); | |
207 }, false); | 277 }, false); |
208 | 278 |
209 document.getElementById("filter-type").addEventListener("change", (event) => | 279 document.getElementById("filter-type").addEventListener("change", (event) => |
210 { | 280 { |
211 container.dataset.filterType = event.target.value; | 281 container.dataset.filterType = event.target.value; |
282 updateRows(table, container.dataset); | |
212 }, false); | 283 }, false); |
213 | 284 |
214 ext.onMessage.addListener((message) => | 285 ext.onMessage.addListener((message) => |
215 { | 286 { |
216 switch (message.type) | 287 switch (message.type) |
217 { | 288 { |
218 case "add-record": | 289 case "add-record": |
219 table.appendChild(createRecord(message.request, message.filter, | 290 let record = createRecord(message.request, message.filter, template); |
220 template)); | 291 records.push(record); |
292 // avoid pushing rows that are being filtered | |
293 // and use their placeholder when that is the case | |
294 let selector = getCSSFilter(container.dataset); | |
295 let node = (!selector || !record.matches(selector)) ? | |
296 record : getPlaceholder(record); | |
297 if (message.request.docDomain == null) | |
Thomas Greiner
2018/02/28 18:18:08
Note: We'll be able to check `message.request.page
a.giammarchi
2018/02/28 19:07:13
then I prefer making that one a blocking of this o
| |
298 { | |
299 table.appendChild(node); | |
300 } | |
301 else | |
302 { | |
303 table.insertBefore(node, bgItems); | |
304 } | |
221 break; | 305 break; |
222 | 306 |
223 case "update-record": | 307 case "update-record": |
224 let oldRow = table.getElementsByTagName("tr")[message.index]; | 308 let oldRow = records[message.index]; |
225 let newRow = createRecord(message.request, message.filter, template); | 309 let newRow = createRecord(message.request, message.filter, template); |
226 oldRow.parentNode.replaceChild(newRow, oldRow); | 310 records[message.index] = newRow; |
311 let liveRow = oldRow.parentNode ? oldRow : oldRow.placeholder; | |
312 liveRow.parentNode.replaceChild(newRow, liveRow); | |
227 newRow.classList.add("changed"); | 313 newRow.classList.add("changed"); |
228 container.classList.add("has-changes"); | 314 container.classList.add("has-changes"); |
229 break; | 315 break; |
230 | 316 |
231 case "remove-record": | 317 case "remove-record": |
232 let row = table.getElementsByTagName("tr")[message.index]; | 318 let row = records.splice(message.index, 1)[0]; |
233 row.parentNode.removeChild(row); | 319 let live = row.parentNode ? row : row.placeholder; |
320 live.parentNode.removeChild(live); | |
234 container.classList.add("has-changes"); | 321 container.classList.add("has-changes"); |
235 break; | 322 break; |
236 | 323 |
237 case "reset": | 324 case "reset": |
325 records.splice(0); | |
238 table.innerHTML = ""; | 326 table.innerHTML = ""; |
327 table.appendChild(bgItems); | |
239 container.classList.remove("has-changes"); | 328 container.classList.remove("has-changes"); |
240 break; | 329 break; |
241 } | 330 } |
242 }); | 331 }); |
243 | 332 |
244 window.addEventListener("message", (event) => | 333 window.addEventListener("message", (event) => |
245 { | 334 { |
246 switch (event.data.type) | 335 switch (event.data.type) |
247 { | 336 { |
248 case "performSearch": | 337 case "performSearch": |
249 performSearch(table, event.data.queryString); | 338 performSearch(table, event.data.queryString); |
250 lastFilterQuery = event.data.queryString; | 339 lastFilterQuery = event.data.queryString; |
251 break; | 340 break; |
252 case "cancelSearch": | 341 case "cancelSearch": |
253 cancelSearch(table); | 342 cancelSearch(table); |
254 lastFilterQuery = null; | 343 lastFilterQuery = null; |
255 break; | 344 break; |
256 } | 345 } |
257 }); | 346 }); |
258 | 347 |
259 // Since Chrome 54 the themeName is accessible, for earlier versions we must | 348 // Since Chrome 54 the themeName is accessible, for earlier versions we must |
260 // assume the default theme is being used. | 349 // assume the default theme is being used. |
261 // https://bugs.chromium.org/p/chromium/issues/detail?id=608869 | 350 // https://bugs.chromium.org/p/chromium/issues/detail?id=608869 |
262 let theme = browser.devtools.panels.themeName || "default"; | 351 let theme = browser.devtools.panels.themeName || "default"; |
263 document.body.classList.add(theme); | 352 document.body.classList.add(theme); |
264 }, false); | 353 }, false); |
OLD | NEW |