| Index: chrome/content/ui/sidebar.js | 
| =================================================================== | 
| --- a/chrome/content/ui/sidebar.js | 
| +++ b/chrome/content/ui/sidebar.js | 
| @@ -253,23 +253,21 @@ function fillInTooltip(e) { | 
|  | 
| if (!item) | 
| { | 
| e.preventDefault(); | 
| return; | 
| } | 
|  | 
| let filter = getFilter(item); | 
| -  let size = ("tooltip" in item ? null : getItemSize(item)); | 
| let subscriptions = (filter ? filter.subscriptions.filter(function(subscription) { return !subscription.disabled; }) : []); | 
|  | 
| E("tooltipDummy").hidden = !("tooltip" in item); | 
| E("tooltipAddressRow").hidden = ("tooltip" in item); | 
| E("tooltipTypeRow").hidden = ("tooltip" in item); | 
| -  E("tooltipSizeRow").hidden = !size; | 
| E("tooltipDocDomainRow").hidden = ("tooltip" in item || !item.docDomain); | 
| E("tooltipFilterRow").hidden = !filter; | 
| E("tooltipFilterSourceRow").hidden = !subscriptions.length; | 
|  | 
| if ("tooltip" in item) | 
| E("tooltipDummy").setAttribute("value", item.tooltip); | 
| else | 
| { | 
| @@ -278,19 +276,16 @@ function fillInTooltip(e) { | 
|  | 
| var type = localizedTypes.get(item.type); | 
| if (filter && filter instanceof WhitelistFilter) | 
| type += " " + E("tooltipType").getAttribute("whitelisted"); | 
| else if (filter && item.type != "ELEMHIDE") | 
| type += " " + E("tooltipType").getAttribute("filtered"); | 
| E("tooltipType").setAttribute("value", type); | 
|  | 
| -    if (size) | 
| -      E("tooltipSize").setAttribute("value", size.join(" x ")); | 
| - | 
| E("tooltipDocDomain").setAttribute("value", item.docDomain + " " + (item.thirdParty ? docDomainThirdParty : docDomainFirstParty)); | 
| } | 
|  | 
| if (filter) | 
| { | 
| let filterField = E("tooltipFilter"); | 
| setMultilineContent(filterField, filter.text); | 
| if (filter.disabled) | 
| @@ -306,16 +301,29 @@ function fillInTooltip(e) { | 
| let sourceElement = E("tooltipFilterSource"); | 
| while (sourceElement.firstChild) | 
| sourceElement.removeChild(sourceElement.firstChild); | 
| for (let i = 0; i < subscriptions.length; i++) | 
| setMultilineContent(sourceElement, subscriptions[i].title, true); | 
| } | 
| } | 
|  | 
| +  E("tooltipSizeRow").hidden = true; | 
| +  if (!("tooltip" in item)) | 
| +  { | 
| +    getItemSize(item, (size) => | 
| +    { | 
| +      if (size) | 
| +      { | 
| +        E("tooltipSizeRow").hidden = false; | 
| +        E("tooltipSize").setAttribute("value", size.join(" x ")); | 
| +      } | 
| +    }); | 
| +  } | 
| + | 
| var showPreview = Prefs.previewimages && !("tooltip" in item); | 
| showPreview = showPreview && item.type == "IMAGE"; | 
| showPreview = showPreview && (!filter || filter.disabled || filter instanceof WhitelistFilter); | 
| E("tooltipPreviewBox").hidden = true; | 
| if (showPreview) | 
| { | 
| if (!cacheStorage) | 
| { | 
| @@ -659,31 +667,45 @@ function detach(doDetach) | 
| let myMainWin = mainWin; | 
|  | 
| // Close sidebar and open detached window | 
| myMainWin.document.getElementById("abp-command-sidebar").doCommand(); | 
| myPrefs.detachsidebar = doDetach; | 
| myMainWin.document.getElementById("abp-command-sidebar").doCommand(); | 
| } | 
|  | 
| -// Returns items size in the document if available | 
| -function getItemSize(item) | 
| +// Returns item's size if already known, otherwise undefined | 
| +function getCachedItemSize(item) | 
| { | 
| +  if ("size" in item) | 
| +    return item.size; | 
| + | 
| let filter = getFilter(item); | 
| if (filter && !filter.disabled && filter instanceof BlockingFilter) | 
| return null; | 
|  | 
| -  for (let node of item.nodes) | 
| +  return undefined; | 
| +} | 
| + | 
| +// Retrieves item's size in the document if available | 
| +function getItemSize(item, callback) | 
| +{ | 
| +  let size = getCachedItemSize(item); | 
| +  if (typeof size != "undefined" || !requestNotifier) | 
| { | 
| -    if (node instanceof HTMLImageElement && (node.naturalWidth || node.naturalHeight)) | 
| -      return [node.naturalWidth, node.naturalHeight]; | 
| -    else if (node instanceof HTMLElement && (node.offsetWidth || node.offsetHeight)) | 
| -      return [node.offsetWidth, node.offsetHeight]; | 
| +    callback(size); | 
| +    return; | 
| } | 
| -  return null; | 
| + | 
| +  requestNotifier.retrieveNodeSize(item.ids, function(size) | 
| +  { | 
| +    if (size) | 
| +      item.size = size; | 
| +    callback(size); | 
| +  }); | 
| } | 
|  | 
| // Sort functions for the item list | 
| function sortByAddress(item1, item2) { | 
| if (item1.location < item2.location) | 
| return -1; | 
| else if (item1.location > item2.location) | 
| return 1; | 
| @@ -723,21 +745,22 @@ function compareState(item1, item2) | 
| { | 
| let filter1 = getFilter(item1); | 
| let filter2 = getFilter(item2); | 
| let state1 = (!filter1 ? 0 : (filter1.disabled ? 1 : (filter1 instanceof WhitelistFilter ? 2 : 3))); | 
| let state2 = (!filter2 ? 0 : (filter2.disabled ? 1 : (filter2 instanceof WhitelistFilter ? 2 : 3))); | 
| return state1 - state2; | 
| } | 
|  | 
| -function compareSize(item1, item2) { | 
| -  var size1 = getItemSize(item1); | 
| +function compareSize(item1, item2) | 
| +{ | 
| +  let size1 = getCachedItemSize(item1); | 
| +  let size2 = getCachedItemSize(item2); | 
| + | 
| size1 = size1 ? size1[0] * size1[1] : 0; | 
| - | 
| -  var size2 = getItemSize(item2); | 
| size2 = size2 ? size2[0] * size2[1] : 0; | 
| return size1 - size2; | 
| } | 
|  | 
| function compareDocDomain(item1, item2) | 
| { | 
| if (item1.docDomain < item2.docDomain) | 
| return -1; | 
| @@ -871,17 +894,25 @@ var treeView = { | 
| if (row >= this.data.length) | 
| return ""; | 
| if (col == "type") | 
| return localizedTypes.get(this.data[row].type); | 
| else if (col == "filter") | 
| return (this.data[row].filter || ""); | 
| else if (col == "size") | 
| { | 
| -        let size = getItemSize(this.data[row]); | 
| +        let size = getCachedItemSize(this.data[row]); | 
| +        if (typeof size == "undefined") | 
| +        { | 
| +          getItemSize(this.data[row], (size) => | 
| +          { | 
| +            if (size) | 
| +              this.boxObject.invalidateRow(row) | 
| +          }); | 
| +        } | 
| return (size ? size.join(" x ") : ""); | 
| } | 
| else if (col == "docDomain") | 
| return this.data[row].docDomain + " " + (this.data[row].thirdParty ? docDomainThirdParty : docDomainFirstParty); | 
| else if (col == "filterSource") | 
| { | 
| let filter = getFilter(this.data[row]) | 
| if (!filter) | 
|  |