| Index: devtools-panel.js | 
| =================================================================== | 
| --- a/devtools-panel.js | 
| +++ b/devtools-panel.js | 
| @@ -18,27 +18,22 @@ | 
| "use strict"; | 
|  | 
| let lastFilterQuery = null; | 
|  | 
| browser.runtime.sendMessage({type: "types.get"}, | 
| (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) | 
| { | 
| let filter = request.url.replace(/^[\w-]+:\/+(?:www\.)?/, "||"); | 
| let options = []; | 
|  | 
| if (request.type == "POPUP") | 
| @@ -168,94 +163,131 @@ | 
| { | 
| if (element.innerText.search(query) != -1) | 
| return false; | 
| } | 
| } | 
| return true; | 
| } | 
|  | 
| -function performSearch(table, query) | 
| +function performSearch(rows, query) | 
| { | 
| -  for (let row of table.rows) | 
| +  for (let row of rows) | 
| { | 
| if (shouldFilterRow(row, query)) | 
| row.classList.add("filtered-by-search"); | 
| else | 
| row.classList.remove("filtered-by-search"); | 
| } | 
| } | 
|  | 
| function cancelSearch(table) | 
| { | 
| for (let row of table.rows) | 
| row.classList.remove("filtered-by-search"); | 
| } | 
|  | 
| +function createZebra(rows, state, type) | 
| +{ | 
| +  let i = 0; | 
| +  for (const tr of rows) | 
| +  { | 
| +    tr.hidden = (state ? tr.dataset.state !== state : false) || | 
| +                (type ? tr.dataset.type !== type : false); | 
| + | 
| +    if (!tr.hidden) | 
| +      tr.classList.toggle("odd", !!(i++ % 2)); | 
| +  } | 
| +} | 
| + | 
| document.addEventListener("DOMContentLoaded", () => | 
| { | 
| -  let container = document.getElementById("items"); | 
| -  let table = container.querySelector("tbody"); | 
| -  let template = document.querySelector("template").content.firstElementChild; | 
| +  const container = document.getElementById("items"); | 
| +  const foregroundTable = container.querySelector("table.foreground tbody"); | 
| +  const backgroundTable = container.querySelector("table.background tbody"); | 
| +  const template = document.querySelector("template") | 
| +                            .content.firstElementChild; | 
| + | 
| +  // We update rows by message index. | 
| +  // Since we don't have a linear collection of rows anymore, | 
| +  // all records, as row, will be indexed in here. | 
| +  // Memory wise, this is just a collection that points | 
| +  // at live DOM nodes (rows), so this does not imply | 
| +  // higher memory consumption, leaks, or any other concern. | 
| +  const records = []; | 
| + | 
| +  const filterState = document.getElementById("filter-state"); | 
| +  const filterType = document.getElementById("filter-type"); | 
| + | 
| +  const updateRowsState = () => | 
| +  { | 
| +    const state = filterState.value; | 
| +    const type = filterType.value; | 
| +    createZebra(foregroundTable.querySelectorAll("tr"), state, type); | 
| +    const notFirstTR = "tr:not(:first-child)"; | 
| +    createZebra(backgroundTable.querySelectorAll(notFirstTR), state, type); | 
| +  }; | 
|  | 
| document.getElementById("reload").addEventListener("click", () => | 
| { | 
| ext.devtools.inspectedWindow.reload(); | 
| }, false); | 
|  | 
| -  document.getElementById("filter-state").addEventListener("change", (event) => | 
| -  { | 
| -    container.dataset.filterState = event.target.value; | 
| -  }, false); | 
| - | 
| -  document.getElementById("filter-type").addEventListener("change", (event) => | 
| -  { | 
| -    container.dataset.filterType = event.target.value; | 
| -  }, false); | 
| +  // update rows visibility state per each change | 
| +  filterState.addEventListener("change", updateRowsState, false); | 
| +  filterType.addEventListener("change", updateRowsState, false); | 
|  | 
| ext.onMessage.addListener((message) => | 
| { | 
| switch (message.type) | 
| { | 
| case "add-record": | 
| -        table.appendChild(createRecord(message.request, message.filter, | 
| -                                       template)); | 
| +        const record = createRecord(message.request, message.filter, template); | 
| +        records.push(record); | 
| +        // waiting for 6402 to ship | 
| +        if (message.request.docDomain == null) | 
| +          backgroundTable.appendChild(record); | 
| +        else | 
| +          foregroundTable.appendChild(record); | 
| break; | 
|  | 
| case "update-record": | 
| -        let oldRow = table.getElementsByTagName("tr")[message.index]; | 
| +        let oldRow = records[message.index]; | 
| let newRow = createRecord(message.request, message.filter, template); | 
| +        records[message.index] = newRow; | 
| oldRow.parentNode.replaceChild(newRow, oldRow); | 
| newRow.classList.add("changed"); | 
| container.classList.add("has-changes"); | 
| break; | 
|  | 
| case "remove-record": | 
| -        let row = table.getElementsByTagName("tr")[message.index]; | 
| +        let row = records.splice(message.index, 1)[0]; | 
| row.parentNode.removeChild(row); | 
| container.classList.add("has-changes"); | 
| break; | 
|  | 
| case "reset": | 
| -        table.innerHTML = ""; | 
| +        records.splice(0); | 
| +        foregroundTable.innerHTML = ""; | 
| container.classList.remove("has-changes"); | 
| break; | 
| } | 
| +    updateRowsState(); | 
| }); | 
|  | 
| window.addEventListener("message", (event) => | 
| { | 
| switch (event.data.type) | 
| { | 
| case "performSearch": | 
| -        performSearch(table, event.data.queryString); | 
| +        performSearch(records, event.data.queryString); | 
| lastFilterQuery = event.data.queryString; | 
| break; | 
| case "cancelSearch": | 
| -        cancelSearch(table); | 
| +        cancelSearch(records); | 
| lastFilterQuery = null; | 
| break; | 
| } | 
| }); | 
|  | 
| // Since Chrome 54 the themeName is accessible, for earlier versions we must | 
| // assume the default theme is being used. | 
| // https://bugs.chromium.org/p/chromium/issues/detail?id=608869 | 
|  |