LEFT | RIGHT |
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 |
(...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
161 { | 161 { |
162 for (let element of elements) | 162 for (let element of elements) |
163 { | 163 { |
164 if (element.innerText.search(query) != -1) | 164 if (element.innerText.search(query) != -1) |
165 return false; | 165 return false; |
166 } | 166 } |
167 } | 167 } |
168 return true; | 168 return true; |
169 } | 169 } |
170 | 170 |
171 function performSearch(table, query) | 171 function performSearch(rows, query) |
172 { | 172 { |
173 for (let row of table.rows) | 173 for (let row of rows) |
174 { | 174 { |
175 if (shouldFilterRow(row, query)) | 175 if (shouldFilterRow(row, query)) |
176 row.classList.add("filtered-by-search"); | 176 row.classList.add("filtered-by-search"); |
177 else | 177 else |
178 row.classList.remove("filtered-by-search"); | 178 row.classList.remove("filtered-by-search"); |
179 } | 179 } |
180 } | 180 } |
181 | 181 |
182 function cancelSearch(table) | 182 function cancelSearch(table) |
183 { | 183 { |
184 for (let row of table.rows) | 184 for (let row of table.rows) |
185 row.classList.remove("filtered-by-search"); | 185 row.classList.remove("filtered-by-search"); |
186 } | 186 } |
187 | 187 |
188 /** | 188 function createZebra(rows, state, type) |
189 * Return a generic <tr> placeholder | 189 { |
190 * double linked with the <tr> itself to simplify | 190 let i = 0; |
191 * tbody swaps / filtering of undesired rows. | 191 for (const tr of rows) |
192 * @param {HTMLTableRowElement} tr | 192 { |
193 * @returns {Comment} | 193 tr.hidden = (state ? tr.dataset.state !== state : false) || |
194 */ | 194 (type ? tr.dataset.type !== type : false); |
195 function getPlaceholder(tr) | 195 |
196 { | 196 if (!tr.hidden) |
197 let placeholder = tr.placeholder; | 197 tr.classList.toggle("odd", !!(i++ % 2)); |
198 if (!placeholder) | 198 } |
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) | |
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 } | 199 } |
259 | 200 |
260 document.addEventListener("DOMContentLoaded", () => | 201 document.addEventListener("DOMContentLoaded", () => |
261 { | 202 { |
262 let container = document.getElementById("items"); | 203 const container = document.getElementById("items"); |
263 let table = container.querySelector("tbody"); | 204 const foregroundTable = container.querySelector("table.foreground tbody"); |
264 let bgItems = container.querySelector("#background-items"); | 205 const backgroundTable = container.querySelector("table.background tbody"); |
265 let template = document.querySelector("template").content.firstElementChild; | 206 const template = document.querySelector("template") |
| 207 .content.firstElementChild; |
| 208 |
| 209 // We update rows by message index. |
| 210 // Since we don't have a linear collection of rows anymore, |
| 211 // all records, as row, will be indexed in here. |
| 212 // Memory wise, this is just a collection that points |
| 213 // at live DOM nodes (rows), so this does not imply |
| 214 // higher memory consumption, leaks, or any other concern. |
266 const records = []; | 215 const records = []; |
| 216 |
| 217 const filterState = document.getElementById("filter-state"); |
| 218 const filterType = document.getElementById("filter-type"); |
| 219 |
| 220 const updateRowsState = () => |
| 221 { |
| 222 const state = filterState.value; |
| 223 const type = filterType.value; |
| 224 createZebra(foregroundTable.querySelectorAll("tr"), state, type); |
| 225 const notFirstTR = "tr:not(:first-child)"; |
| 226 createZebra(backgroundTable.querySelectorAll(notFirstTR), state, type); |
| 227 }; |
267 | 228 |
268 document.getElementById("reload").addEventListener("click", () => | 229 document.getElementById("reload").addEventListener("click", () => |
269 { | 230 { |
270 ext.devtools.inspectedWindow.reload(); | 231 ext.devtools.inspectedWindow.reload(); |
271 }, false); | 232 }, false); |
272 | 233 |
273 document.getElementById("filter-state").addEventListener("change", (event) => | 234 // update rows visibility state per each change |
274 { | 235 filterState.addEventListener("change", updateRowsState, false); |
275 container.dataset.filterState = event.target.value; | 236 filterType.addEventListener("change", updateRowsState, false); |
276 updateRows(table, container.dataset); | |
277 }, false); | |
278 | |
279 document.getElementById("filter-type").addEventListener("change", (event) => | |
280 { | |
281 container.dataset.filterType = event.target.value; | |
282 updateRows(table, container.dataset); | |
283 }, false); | |
284 | 237 |
285 ext.onMessage.addListener((message) => | 238 ext.onMessage.addListener((message) => |
286 { | 239 { |
287 switch (message.type) | 240 switch (message.type) |
288 { | 241 { |
289 case "add-record": | 242 case "add-record": |
290 let record = createRecord(message.request, message.filter, template); | 243 const record = createRecord(message.request, message.filter, template); |
291 records.push(record); | 244 records.push(record); |
292 // avoid pushing rows that are being filtered | 245 // waiting for 6402 to ship |
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) | 246 if (message.request.docDomain == null) |
298 { | 247 backgroundTable.appendChild(record); |
299 table.appendChild(node); | |
300 } | |
301 else | 248 else |
302 { | 249 foregroundTable.appendChild(record); |
303 table.insertBefore(node, bgItems); | |
304 } | |
305 break; | 250 break; |
306 | 251 |
307 case "update-record": | 252 case "update-record": |
308 let oldRow = records[message.index]; | 253 let oldRow = records[message.index]; |
309 let newRow = createRecord(message.request, message.filter, template); | 254 let newRow = createRecord(message.request, message.filter, template); |
310 records[message.index] = newRow; | 255 records[message.index] = newRow; |
311 let liveRow = oldRow.parentNode ? oldRow : oldRow.placeholder; | 256 oldRow.parentNode.replaceChild(newRow, oldRow); |
312 liveRow.parentNode.replaceChild(newRow, liveRow); | |
313 newRow.classList.add("changed"); | 257 newRow.classList.add("changed"); |
314 container.classList.add("has-changes"); | 258 container.classList.add("has-changes"); |
315 break; | 259 break; |
316 | 260 |
317 case "remove-record": | 261 case "remove-record": |
318 let row = records.splice(message.index, 1)[0]; | 262 let row = records.splice(message.index, 1)[0]; |
319 let live = row.parentNode ? row : row.placeholder; | 263 row.parentNode.removeChild(row); |
320 live.parentNode.removeChild(live); | |
321 container.classList.add("has-changes"); | 264 container.classList.add("has-changes"); |
322 break; | 265 break; |
323 | 266 |
324 case "reset": | 267 case "reset": |
325 records.splice(0); | 268 records.splice(0); |
326 table.innerHTML = ""; | 269 foregroundTable.innerHTML = ""; |
327 container.classList.remove("has-changes"); | 270 container.classList.remove("has-changes"); |
328 break; | 271 break; |
329 } | 272 } |
| 273 updateRowsState(); |
330 }); | 274 }); |
331 | 275 |
332 window.addEventListener("message", (event) => | 276 window.addEventListener("message", (event) => |
333 { | 277 { |
334 switch (event.data.type) | 278 switch (event.data.type) |
335 { | 279 { |
336 case "performSearch": | 280 case "performSearch": |
337 performSearch(table, event.data.queryString); | 281 performSearch(records, event.data.queryString); |
338 lastFilterQuery = event.data.queryString; | 282 lastFilterQuery = event.data.queryString; |
339 break; | 283 break; |
340 case "cancelSearch": | 284 case "cancelSearch": |
341 cancelSearch(table); | 285 cancelSearch(records); |
342 lastFilterQuery = null; | 286 lastFilterQuery = null; |
343 break; | 287 break; |
344 } | 288 } |
345 }); | 289 }); |
346 | 290 |
347 // Since Chrome 54 the themeName is accessible, for earlier versions we must | 291 // Since Chrome 54 the themeName is accessible, for earlier versions we must |
348 // assume the default theme is being used. | 292 // assume the default theme is being used. |
349 // https://bugs.chromium.org/p/chromium/issues/detail?id=608869 | 293 // https://bugs.chromium.org/p/chromium/issues/detail?id=608869 |
350 let theme = browser.devtools.panels.themeName || "default"; | 294 let theme = browser.devtools.panels.themeName || "default"; |
351 document.body.classList.add(theme); | 295 document.body.classList.add(theme); |
352 }, false); | 296 }, false); |
LEFT | RIGHT |