Bug 1576147 - Use async/await for frame processing in pageInfo. r=frg a=frg
authorIan Neal <iann_cvs@blueyonder.co.uk>
Sun, 15 Sep 2019 13:04:18 +0200
changeset 32310 da2c74f5383834a99773d54b0246e1b8c3972583
parent 32309 6078db5cb2520750c3e369e74719597aa837c7c9
child 32311 1aa55d01c39693487899ccc544ea94c401b892e0
push id218
push userfrgrahl@gmx.net
push dateSun, 15 Sep 2019 11:06:46 +0000
treeherdercomm-esr60@da2c74f53838 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfrg, frg
bugs1576147
Bug 1576147 - Use async/await for frame processing in pageInfo. r=frg a=frg
suite/browser/content.js
suite/browser/pageinfo/pageInfo.js
--- a/suite/browser/content.js
+++ b/suite/browser/content.js
@@ -11,16 +11,18 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.defineModuleGetter(this, "LoginManagerContent",
   "resource://gre/modules/LoginManagerContent.jsm");
 ChromeUtils.defineModuleGetter(this, "InsecurePasswordUtils",
   "resource://gre/modules/InsecurePasswordUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "LoginFormFactory",
   "resource://gre/modules/LoginManagerContent.jsm");
+ChromeUtils.defineModuleGetter(this, "setTimeout",
+  "resource://gre/modules/Timer.jsm");
 ChromeUtils.defineModuleGetter(this, "Feeds",
   "resource:///modules/Feeds.jsm");
 ChromeUtils.defineModuleGetter(this, "PlacesUIUtils",
   "resource:///modules/PlacesUIUtils.jsm");
 
 addMessageListener("RemoteLogins:fillForm", message => {
   LoginManagerContent.receiveMessage(message, content);
 });
@@ -62,19 +64,16 @@ const XLinkNS  = "http://www.w3.org/1999
 
 let PageInfoListener = {
 
   init: function() {
     addMessageListener("PageInfo:getData", this);
   },
 
   receiveMessage: function(message) {
-    this.imageViewRows = [];
-    this.linkViewRows = [];
-    this.formViewRows = [];
     let strings = message.data.strings;
     let window;
     let document;
 
     let frameOuterWindowID = message.data.frameOuterWindowID;
 
     // If inside frame then get the frame's window and document.
     if (frameOuterWindowID) {
@@ -94,27 +93,16 @@ let PageInfoListener = {
                         windowInfo: this.getWindowInfo(window),
                         imageInfo: this.getImageInfo(imageElement)};
 
     sendAsyncMessage("PageInfo:data", pageInfoData);
 
     // Separate step so page info dialog isn't blank while waiting for this
     // to finish.
     this.getMediaInfo(document, window, strings);
-
-    // Send the message after all the media elements have been walked through.
-    let pageInfoMediaData = {imageViewRows: this.imageViewRows,
-                             linkViewRows: this.linkViewRows,
-                             formViewRows: this.formViewRows};
-
-    this.imageViewRows = null;
-    this.linkViewRows = null;
-    this.formViewRows = null;
-
-    sendAsyncMessage("PageInfo:mediaData", pageInfoMediaData);
   },
 
   getImageInfo: function(imageElement) {
     let imageInfo = null;
     if (imageElement) {
       imageInfo = {
         currentSrc: imageElement.currentSrc,
         width: imageElement.width,
@@ -223,36 +211,50 @@ let PageInfoListener = {
         frameList =
           frameList.concat(this.goThroughFrames(window.frames[i].document,
                                                 window.frames[i]));
       }
     }
     return frameList;
   },
 
-  processFrames: function(document, frameList, strings)
+  async processFrames(document, frameList, strings)
   {
+    let nodeCount = 0;
     for (let doc of frameList) {
       let iterator = doc.createTreeWalker(doc, content.NodeFilter.SHOW_ELEMENT);
 
+      // Goes through all the elements on the doc.
       while (iterator.nextNode()) {
-        this.getMediaNode(document, strings, iterator.currentNode);
+        this.getMediaItems(document, strings, iterator.currentNode);
+
+        if (++nodeCount % 500 == 0) {
+          // setTimeout every 500 elements so we don't keep blocking the
+          // content process.
+          await new Promise(resolve => setTimeout(resolve, 10));
+        }
       }
     }
+    // Send that page info media fetching has finished.
+    sendAsyncMessage("PageInfo:mediaData", {isComplete: true});
   },
 
-  getMediaNode: function(document, strings, elem)
+  getMediaItems: function(document, strings, elem)
   {
-    // Check for images defined in CSS (e.g. background, borders),
-    // any node may have multiple.
+    // Check for images defined in CSS (e.g. background, borders).
     let computedStyle = elem.ownerDocument.defaultView.getComputedStyle(elem, "");
+    // A node can have multiple media items associated with it - for example,
+    // multiple background images.
+    let imageItems = [];
+    let formItems = [];
+    let linkItems = [];
 
     let addImage = (url, type, alt, elem, isBg) => {
       let element = this.serializeElementInfo(document, url, type, alt, elem, isBg);
-      this.imageViewRows.push([url, type, alt, element, isBg]);
+      imageItems.push([url, type, alt, element, isBg]);
     };
 
     if (computedStyle) {
       let addImgFunc = (label, val) => {
         if (val.primitiveType == content.CSSPrimitiveValue.CSS_URI) {
           addImage(val.getStringValue(), label, strings.notSet, elem, true);
         }
         else if (val.primitiveType == content.CSSPrimitiveValue.CSS_STRING) {
@@ -275,78 +277,75 @@ let PageInfoListener = {
       addImgFunc(strings.mediaBGImg, computedStyle.getPropertyCSSValue("background-image"));
       addImgFunc(strings.mediaBorderImg, computedStyle.getPropertyCSSValue("border-image-source"));
       addImgFunc(strings.mediaListImg, computedStyle.getPropertyCSSValue("list-style-image"));
       addImgFunc(strings.mediaCursor, computedStyle.getPropertyCSSValue("cursor"));
     }
 
     let addForm = (elem) => {
       let element = this.serializeFormInfo(document, elem, strings);
-      this.formViewRows.push([elem.name, elem.method, elem.action, element]);
+      formItems.push([elem.name, elem.method, elem.action, element]);
     };
 
     // One swi^H^H^Hif-else to rule them all.
     if (elem instanceof content.HTMLAnchorElement) {
-      this.linkViewRows.push([this.getValueText(elem), elem.href,
-                              strings.linkAnchor, elem.target, elem.accessKey]);
+      linkItems.push([this.getValueText(elem), elem.href, strings.linkAnchor,
+                      elem.target, elem.accessKey]);
     }
     else if (elem instanceof content.HTMLImageElement) {
       addImage(elem.src, strings.mediaImg,
                (elem.hasAttribute("alt")) ? elem.alt : strings.notSet,
                elem, false);
     }
     else if (elem instanceof content.HTMLAreaElement) {
-      this.linkViewRows.push([elem.alt, elem.href,
-                              strings.linkArea, elem.target, ""]);
+      linkItems.push([elem.alt, elem.href, strings.linkArea, elem.target, ""]);
     }
     else if (elem instanceof content.HTMLVideoElement) {
       addImage(elem.currentSrc, strings.mediaVideo, "", elem, false);
     }
     else if (elem instanceof content.HTMLAudioElement) {
       addImage(elem.currentSrc, strings.mediaAudio, "", elem, false);
     }
     else if (elem instanceof content.HTMLLinkElement) {
       if (elem.rel) {
         let rel = elem.rel;
         if (/\bicon\b/i.test(rel)) {
           addImage(elem.href, strings.mediaLink, "", elem, false);
         }
         else if (/(?:^|\s)stylesheet(?:\s|$)/i.test(rel)) {
-          this.linkViewRows.push([elem.rel, elem.href,
-                                  strings.linkStylesheet, elem.target, ""]);
+          linkItems.push([elem.rel, elem.href, strings.linkStylesheet,
+                          elem.target, ""]);
         }
         else {
-          this.linkViewRows.push([elem.rel, elem.href,
-                                  strings.linkRel, elem.target, ""]);
+          linkItems.push([elem.rel, elem.href, strings.linkRel,
+                          elem.target, ""]);
         }
       }
       else {
-        this.linkViewRows.push([elem.rev, elem.href,
-                                strings.linkRev, elem.target, ""]);
+        linkItems.push([elem.rev, elem.href, strings.linkRev, elem.target, ""]);
       }
     }
     else if (elem instanceof content.HTMLInputElement ||
              elem instanceof content.HTMLButtonElement) {
       switch (elem.type.toLowerCase()) {
         case "image":
           addImage(elem.src, strings.mediaInput,
                    (elem.hasAttribute("alt")) ? elem.alt : strings.notSet,
                    elem, false);
           // Fall through, <input type="image"> submits, too
         case "submit":
           if ("form" in elem && elem.form) {
-            this.linkViewRows.push([elem.value || this.getValueText(elem) ||
-                                    strings.linkSubmit, elem.form.action,
-                                    strings.linkSubmission,
-                                    elem.form.target, ""]);
+            linkItems.push([elem.value || this.getValueText(elem) ||
+                            strings.linkSubmit, elem.form.action,
+                            strings.linkSubmission, elem.form.target, ""]);
           }
           else {
-            this.linkViewRows.push([elem.value || this.getValueText(elem) ||
-                                    strings.linkSubmit, "",
-                                    strings.linkSubmission, "", ""]);
+            linkItems.push([elem.value || this.getValueText(elem) ||
+                            strings.linkSubmit, "",
+                            strings.linkSubmission, "", ""]);
           }
       }
     }
     else if (elem instanceof content.HTMLFormElement) {
       addForm(elem);
     }
     else if (elem instanceof content.HTMLObjectElement) {
       addImage(elem.data, strings.mediaObject, this.getValueText(elem),
@@ -356,39 +355,40 @@ let PageInfoListener = {
       addImage(elem.src, strings.mediaEmbed, "", elem, false);
     }
     else if (elem.namespaceURI == MathMLNS && elem.hasAttribute("href")) {
       let href = elem.getAttribute("href");
       try {
         href = makeURLAbsolute(elem.baseURI, href,
                                elem.ownerDocument.characterSet);
       } catch (e) {}
-      this.linkViewRows.push([this.getValueText(elem), href, strings.linkX,
-                              "", ""]);
+      linkItems.push([this.getValueText(elem), href, strings.linkX, "", ""]);
     }
     else if (elem.hasAttributeNS(XLinkNS, "href")) {
       let href = elem.getAttributeNS(XLinkNS, "href");
       try {
         href = makeURLAbsolute(elem.baseURI, href,
                                elem.ownerDocument.characterSet);
       } catch (e) {}
       // SVG images without an xlink:href attribute are ignored
       if (elem instanceof content.SVGImageElement) {
         addImage(href, strings.mediaImg, "", elem, false);
       }
       else {
-        this.linkViewRows.push([this.getValueText(elem), href, strings.linkX,
-                                "", ""]);
+        linkItems.push([this.getValueText(elem), href, strings.linkX, "", ""]);
       }
     }
     else if (elem instanceof content.HTMLScriptElement) {
-      this.linkViewRows.push([elem.type || elem.getAttribute("language") ||
-                              strings.notSet,
-                              elem.src || strings.linkScriptInline,
-                              strings.linkScript, "", "", ""]);
+      linkItems.push([elem.type || elem.getAttribute("language") ||
+                      strings.notSet, elem.src || strings.linkScriptInline,
+                      strings.linkScript, "", "", ""]);
+    }
+    if (imageItems.length || formItems.length || linkItems.length) {
+      sendAsyncMessage("PageInfo:mediaData",
+                       {imageItems, formItems, linkItems, isComplete: false});
     }
   },
 
   /**
    * Set up a JSON element object with all the instanceOf and other infomation
    * that makePreview in pageInfo.js uses to figure out how to display the
    * preview.
    */
--- a/suite/browser/pageinfo/pageInfo.js
+++ b/suite/browser/pageinfo/pageInfo.js
@@ -313,17 +313,17 @@ function loadPageInfo(frameOuterWindowID
   gStrings["application/rdf+xml"]  = gBundle.getString("feedXML");
 
   // Look for pageInfoListener in content.js.
   // Sends message to listener with arguments.
   mm.sendAsyncMessage("PageInfo:getData", {strings: gStrings,
                       frameOuterWindowID: frameOuterWindowID},
                       { imageElement });
 
-  let pageInfoData = null;
+  let pageInfoData;
 
   // Get initial pageInfoData needed to display the general, feeds, permission
   // and security tabs.
   mm.addMessageListener("PageInfo:data", function onmessage(message) {
     mm.removeMessageListener("PageInfo:data", onmessage);
     pageInfoData = message.data;
     let docInfo = pageInfoData.docInfo;
     let windowInfo = pageInfoData.windowInfo;
@@ -344,24 +344,44 @@ function loadPageInfo(frameOuterWindowID
 
     makeGeneralTab(pageInfoData.metaViewRows, docInfo);
     initFeedTab(pageInfoData.feeds);
     onLoadPermission(uri, principal);
     securityOnLoad(uri, windowInfo);
   });
 
   // Get the media elements from content script to setup the media tab.
-  mm.addMessageListener("PageInfo:mediaData", function onmessage(message){
-    mm.removeMessageListener("PageInfo:mediaData", onmessage);
-    makeMediaTab(message.data.imageViewRows);
-    gLinkView.addRows(message.data.linkViewRows);
-    gFormView.addRows(message.data.formViewRows);
+  mm.addMessageListener("PageInfo:mediaData", function onmessage(message) {
+    // Page info window was closed.
+    if (window.closed) {
+      mm.removeMessageListener("PageInfo:mediaData", onmessage);
+      return;
+    }
+
+    // The page info media fetching has been completed.
+    if (message.data.isComplete) {
+      mm.removeMessageListener("PageInfo:mediaData", onmessage);
+      onFinished.forEach(function(func) { func(pageInfoData); });
+      return;
+    }
 
-    // Loop through onFinished and execute the functions on it.
-    onFinished.forEach(function(func) { func(pageInfoData); });
+    if (message.data.imageItems) {
+      for (let item of message.data.imageItems) {
+        addImage(item);
+      }
+      selectImage();
+    }
+
+    if (message.data.linkItems) {
+      gLinkView.addRows(message.data.linkItems);
+    }
+
+    if (message.data.formItems) {
+      gFormView.addRows(message.data.formItems);
+    }
   });
 
   /* Call registered overlay init functions */
   onLoadRegistry.forEach(function(func) { func(); });
 }
 
 function resetPageInfo(args)
 {
@@ -522,37 +542,28 @@ function makeGeneralTab(metaViewRows, do
       var pageSize = cacheEntry.dataSize;
       var kbSize = formatNumber(Math.round(pageSize / 1024 * 100) / 100);
       sizeText = gBundle.getFormattedString("generalSize", [kbSize, formatNumber(pageSize)]);
     }
     setItemValue("sizetext", sizeText);
   });
 }
 
-function makeMediaTab(imageViewRows)
-{
-  // Call addImage passing in the image rows to add to the view on the
-  // Media Tab.
-  for (let image of imageViewRows) {
-    let [url, type, alt, elem, isBg] = image;
-    addImage(url, type, alt, elem, isBg);
-  }
-  selectImage();
-}
-
 function ensureSelection(view)
 {
   // only select something if nothing is currently selected
   // and if there's anything to select
   if (view.selection && view.selection.count == 0 && view.rowCount)
     view.selection.select(0);
 }
 
-function addImage(url, type, alt, elem, isBg)
+function addImage(imageViewRow)
 {
+  let [url, type, alt, elem, isBg] = imageViewRow;
+
   if (!url)
     return;
 
   if (!gImageHash.hasOwnProperty(url))
     gImageHash[url] = { };
   if (!gImageHash[url].hasOwnProperty(type))
     gImageHash[url][type] = { };
   if (!gImageHash[url][type].hasOwnProperty(alt)) {