| Index: devtools-panel.js |
| =================================================================== |
| --- a/devtools-panel.js |
| +++ b/devtools-panel.js |
| @@ -23,17 +23,12 @@ |
| (filterTypes) => |
| { |
| let filterTypesElem = document.getElementById("filter-type"); |
| - let filterStyleElem = document.createElement("style"); |
| for (let type of filterTypes) |
| { |
| - filterStyleElem.innerHTML += |
| - `#items[data-filter-type=${type}] tr:not([data-type=${type}])` + |
| - "{display: none;}"; |
| let optionNode = document.createElement("option"); |
| optionNode.appendChild(document.createTextNode(type)); |
| filterTypesElem.appendChild(optionNode); |
| } |
| - document.body.appendChild(filterStyleElem); |
| }); |
| function generateFilter(request, domainSpecific) |
| @@ -190,11 +185,85 @@ |
| row.classList.remove("filtered-by-search"); |
| } |
| +/** |
| + * Return a generic <tr> placeholder |
| + * double linked with the <tr> itself to simplify |
| + * tbody swaps / filtering of undesired rows. |
| + * @param {HTMLTableRowElement} tr |
| + * @returns {Comment} |
| + */ |
| +function getPlaceholder(tr) |
| +{ |
| + let placeholder = tr.placeholder; |
| + if (!placeholder) |
| + { |
| + placeholder = document.createComment("placeholder"); |
| + tr.placeholder = placeholder; |
| + placeholder.placeholder = tr; |
| + } |
| + return placeholder; |
| +} |
| + |
| +/** |
| + * Put back all hidden table rows and eventually |
| + * filters those that do not match the state or type. |
| + * @param {HTMLTableSectionElement} tbody |
| + * @param {DOMStringMap} dataset |
| + */ |
| +function updateRows(tbody, dataset) |
| +{ |
| + // use filters to match nodes or retrieve them later on |
| + const selector = getCSSFilter(dataset); |
| + for (const node of tbody.childNodes) |
| + { |
| + // comments linked to table rows that |
| + // won't match selector, will be used |
| + // to place their linked row back in |
| + if ( |
| + node.nodeType === Node.COMMENT_NODE && |
| + (!selector || !node.placeholder.matches(selector)) |
| + ) |
| + { |
| + tbody.replaceChild(node.placeholder, node); |
| + } |
| + } |
| + |
| + // if there is a list of nodes to filter |
| + if (selector) |
| + { |
| + // every matched row should bre replaced with its placeholder |
| + for (const tr of tbody.querySelectorAll(selector)) |
| + { |
| + tbody.replaceChild(getPlaceholder(tr), tr); |
| + } |
| + } |
| +} |
| + |
| +function getCSSFilter(dataset) |
| +{ |
| + const {filterState, filterType} = dataset; |
| + |
| + // create a list of possible CSS filter |
| + // excluding #background-items from the list of rows to consider |
| + const query = []; |
| + if (filterState) |
| + { |
| + query.push(`tr:not(#background-items):not([data-state="${filterState}"])`); |
| + } |
| + if (filterType) |
| + { |
| + query.push(`tr:not(#background-items):not([data-type="${filterType}"])`); |
| + } |
| + return query.join(","); |
| +} |
| + |
| document.addEventListener("DOMContentLoaded", () => |
| { |
| let container = document.getElementById("items"); |
| let table = container.querySelector("tbody"); |
| + let bgItems = container.querySelector("#background-items"); |
| let template = document.querySelector("template").content.firstElementChild; |
| + const records = []; |
| document.getElementById("reload").addEventListener("click", () => |
| { |
| @@ -204,11 +273,13 @@ |
| document.getElementById("filter-state").addEventListener("change", (event) => |
| { |
| container.dataset.filterState = event.target.value; |
| + updateRows(table, container.dataset); |
| }, false); |
| document.getElementById("filter-type").addEventListener("change", (event) => |
| { |
| container.dataset.filterType = event.target.value; |
| + updateRows(table, container.dataset); |
| }, false); |
| ext.onMessage.addListener((message) => |
| @@ -216,25 +287,42 @@ |
| switch (message.type) |
| { |
| case "add-record": |
| - table.appendChild(createRecord(message.request, message.filter, |
| - template)); |
| + let record = createRecord(message.request, message.filter, template); |
| + records.push(record); |
| + // avoid pushing rows that are being filtered |
| + // and use their placeholder when that is the case |
| + let selector = getCSSFilter(container.dataset); |
| + let node = (!selector || !record.matches(selector)) ? |
| + record : getPlaceholder(record); |
| + if (message.request.docDomain == null) |
| + { |
| + table.appendChild(node); |
| + } |
| + else |
| + { |
| + table.insertBefore(node, bgItems); |
| + } |
| break; |
| case "update-record": |
| - let oldRow = table.getElementsByTagName("tr")[message.index]; |
| + let oldRow = records[message.index]; |
| let newRow = createRecord(message.request, message.filter, template); |
| - oldRow.parentNode.replaceChild(newRow, oldRow); |
| + records[message.index] = newRow; |
| + let liveRow = oldRow.parentNode ? oldRow : oldRow.placeholder; |
| + liveRow.parentNode.replaceChild(newRow, liveRow); |
| newRow.classList.add("changed"); |
| container.classList.add("has-changes"); |
| break; |
| case "remove-record": |
| - let row = table.getElementsByTagName("tr")[message.index]; |
| - row.parentNode.removeChild(row); |
| + let row = records.splice(message.index, 1)[0]; |
| + let live = row.parentNode ? row : row.placeholder; |
| + live.parentNode.removeChild(live); |
| container.classList.add("has-changes"); |
| break; |
| case "reset": |
| + records.splice(0); |
| table.innerHTML = ""; |
| container.classList.remove("has-changes"); |
| break; |