Bug 1363059 - Add a test for images loaded at startup vs. images shown at startup. r=florian,jwatt
☠☠ backed out by 6b0f55d8a2d2 ☠ ☠
authorJohann Hofmann <jhofmann@mozilla.com>
Thu, 15 Jun 2017 00:11:48 +0200
changeset 365982 d2f4da0ae6819be3b3ced71947f66585df58e145
parent 365981 24faed7bb3d8b23268eadb59c1f8cdbf8a1bc99a
child 365983 504e5b1de987f7ec75aa303402ada8f61faf92ff
push id45471
push userjhofmann@mozilla.com
push dateMon, 26 Jun 2017 09:11:42 +0000
treeherderautoland@d2f4da0ae681 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersflorian, jwatt
bugs1363059
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1363059 - Add a test for images loaded at startup vs. images shown at startup. r=florian,jwatt This patch enables startupRecorder.js to collect data on loaded and shown raster and SVG images on startup via events from native code. It also adds a test that uses this data to find images that are unnecessarily loaded. I've not fixed any of the affected images yet, there's a fairly comprehensive whitelist that I want to gradually decrease by opening bugs in the respective components. MozReview-Commit-ID: 9KqQvKLtZhu
browser/base/content/test/performance/browser.ini
browser/base/content/test/performance/browser_startup.js
browser/base/content/test/performance/browser_startup_images.js
browser/components/tests/startupRecorder.js
image/ImageFactory.cpp
image/RasterImage.cpp
image/VectorImage.cpp
--- a/browser/base/content/test/performance/browser.ini
+++ b/browser/base/content/test/performance/browser.ini
@@ -1,13 +1,15 @@
 [DEFAULT]
 support-files =
   head.js
 [browser_appmenu_reflows.js]
 [browser_startup.js]
+[browser_startup_images.js]
+skip-if = !debug
 [browser_tabclose_grow_reflows.js]
 [browser_tabclose_reflows.js]
 [browser_tabopen_reflows.js]
 [browser_tabopen_squeeze_reflows.js]
 [browser_tabswitch_reflows.js]
 [browser_toolbariconcolor_restyles.js]
 [browser_windowclose_reflows.js]
 [browser_windowopen_reflows.js]
--- a/browser/base/content/test/performance/browser_startup.js
+++ b/browser/base/content/test/performance/browser_startup.js
@@ -100,17 +100,17 @@ const startupPhases = {
 
 function test() {
   if (!AppConstants.NIGHTLY_BUILD && !AppConstants.DEBUG) {
     ok(!("@mozilla.org/test/startuprecorder;1" in Cc),
        "the startup recorder component shouldn't exist in this non-nightly non-debug build.");
     return;
   }
 
-  let data = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject.data;
+  let data = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject.data.code;
   // Keep only the file name for components, as the path is an absolute file
   // URL rather than a resource:// URL like for modules.
   for (let phase in data) {
     data[phase].components =
       data[phase].components.map(f => f.replace(/.*\//, ""))
                             .filter(c => c != "startupRecorder.js");
   }
 
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/performance/browser_startup_images.js
@@ -0,0 +1,201 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* A whitelist of images that are loaded at startup but not shown.
+ * List items support the following attributes:
+ *  - file: The location of the loaded image file.
+ *  - hidpi: An alternative hidpi file location for retina screens, if one exists.
+ *           May be the magic string <not loaded> in strange cases where
+ *           only the low-resolution image is loaded but not shown.
+ *  - platforms: An array of the platforms where the issue is occurring.
+ *               Possible values are linux, win, macosx.
+ *  - intermittentNotLoaded: an array of platforms where this image is
+ *                           intermittently not loaded, e.g. because it is
+ *                           loaded during the time we stop recording.
+ *  - intermittentShown: An array of platforms where this image is
+ *                       intermittently shown, contrary to what our
+ *                       whitelist says.
+ *
+ * Please don't add items to this list. Please remove items from this list.
+ */
+const whitelist = [
+  {
+    file: "chrome://browser/skin/fxa/sync-illustration.svg",
+    platforms: ["linux", "win", "macosx"],
+  },
+  {
+    file: "chrome://browser/skin/tabbrowser/tab-overflow-indicator.png",
+    platforms: ["linux", "win", "macosx"],
+  },
+  {
+    file: "chrome://browser/skin/stop.svg",
+    platforms: ["linux", "win", "macosx"],
+  },
+  {
+    file: "chrome://browser/skin/sidebars.svg",
+    platforms: ["linux", "win", "macosx"],
+    intermittentNotLoaded: ["macosx"],
+  },
+  {
+    file: "chrome://pocket-shared/skin/pocket.svg",
+    platforms: ["linux", "win", "macosx"],
+    intermittentNotLoaded: ["macosx"],
+  },
+  {
+    file: "chrome://browser/skin/places/toolbarDropMarker.png",
+    platforms: ["linux", "win", "macosx"],
+  },
+  {
+    file: "chrome://browser/skin/tracking-protection-16.svg#enabled",
+    platforms: ["linux", "win", "macosx"],
+  },
+  {
+    file: "chrome://browser/skin/toolbarbutton-dropdown-arrow.png",
+    platforms: ["linux", "win", "macosx"],
+  },
+  {
+    file: "chrome://global/skin/icons/autoscroll.png",
+    platforms: ["linux", "win", "macosx"],
+  },
+
+  {
+    file: "chrome://browser/skin/tabbrowser/tab-background-end.png",
+    hidpi: "chrome://browser/skin/tabbrowser/tab-background-end@2x.png",
+    platforms: ["linux", "win", "macosx"],
+  },
+  {
+    file: "chrome://browser/skin/tabbrowser/tab-background-middle.png",
+    hidpi: "chrome://browser/skin/tabbrowser/tab-background-middle@2x.png",
+    platforms: ["linux", "win", "macosx"],
+  },
+  {
+    file: "chrome://browser/skin/tabbrowser/tab-background-start.png",
+    hidpi: "chrome://browser/skin/tabbrowser/tab-background-start@2x.png",
+    platforms: ["linux", "win", "macosx"],
+  },
+  {
+    file: "chrome://browser/skin/tabbrowser/tabDragIndicator.png",
+    hidpi: "chrome://browser/skin/tabbrowser/tabDragIndicator@2x.png",
+    platforms: ["linux", "win", "macosx"],
+  },
+
+  {
+    file: "resource://gre-resources/loading-image.png",
+    platforms: ["win", "macosx"],
+    intermittentNotLoaded: ["win", "macosx"],
+  },
+  {
+    file: "resource://gre-resources/broken-image.png",
+    platforms: ["win", "macosx"],
+    intermittentNotLoaded: ["win", "macosx"],
+  },
+
+  {
+    file: "chrome://browser/skin/places/unfiledBookmarks.png",
+    hidpi: "<not loaded>",
+    platforms: ["win", "macosx"],
+    intermittentNotLoaded: ["win"],
+  },
+  {
+    file: "chrome://browser/skin/urlbar-history-dropmarker.png",
+    hidpi: "<not loaded>",
+    platforms: ["win", "macosx"],
+    intermittentShown: ["win"],
+  },
+
+  {
+    file: "chrome://global/skin/icons/chevron.png",
+    hidpi: "chrome://global/skin/icons/chevron@2x.png",
+    platforms: ["macosx"],
+  },
+
+  {
+    file: "chrome://browser/skin/tabbrowser/alltabs-box-bkgnd-icon.png",
+    hidpi: "chrome://browser/skin/tabbrowser/alltabs-box-bkgnd-icon@2x.png",
+    platforms: ["macosx"],
+  },
+
+  {
+    file: "chrome://global/skin/toolbar/chevron.gif",
+    platforms: ["win", "linux"],
+  },
+  {
+    file: "chrome://browser/skin/reload-stop-go.png",
+    platforms: ["win", "linux"],
+    intermittentShown: ["win"],
+  },
+
+  {
+    file: "chrome://browser/skin/tabbrowser/alltabs.png",
+    platforms: ["linux"],
+  },
+
+  {
+    file: "chrome://browser/skin/tabbrowser/tab-arrow-left.svg",
+    platforms: ["win"],
+  },
+
+  {
+    file: "chrome://global/skin/icons/resizer.png",
+    platforms: ["win"],
+  },
+
+  {
+    file: "chrome://global/skin/icons/resizer.png",
+    platforms: ["win"],
+  },
+
+  {
+    file: "chrome://browser/skin/tabbrowser/tab-arrow-left.png",
+    hidpi: "chrome://browser/skin/tabbrowser/tab-arrow-left@2x.png",
+    platforms: ["linux", "macosx"],
+  },
+  {
+    file: "chrome://browser/skin/tabbrowser/tab-arrow-right.png",
+    hidpi: "chrome://browser/skin/tabbrowser/tab-arrow-right@2x.png",
+    platforms: ["macosx"],
+  },
+];
+
+function test() {
+  let data = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject.data.images;
+  let platformWhitelist = whitelist.filter(el => el.platforms.includes(AppConstants.platform));
+
+  let loadedImages = data["image-loading"];
+  let shownImages = data["image-drawing"];
+
+  for (let loaded of loadedImages.values()) {
+    let whitelistItem = platformWhitelist.find(el => {
+      if (window.devicePixelRatio >= 2 && el.hidpi && el.hidpi == loaded) {
+        return true;
+      }
+      return el.file == loaded;
+    });
+    if (whitelistItem) {
+      if (!whitelistItem.intermittentShown ||
+          !whitelistItem.intermittentShown.includes(AppConstants.platform)) {
+        todo(shownImages.has(loaded), `Loaded image ${loaded} is not shown but whitelisted.`);
+      }
+      continue;
+    }
+    ok(shownImages.has(loaded), `Loaded image ${loaded} was shown.`);
+  }
+
+  // Check for unneeded whitelist entries.
+  for (let item of platformWhitelist) {
+    if (!item.intermittentNotLoaded ||
+        !item.intermittentNotLoaded.includes(AppConstants.platform)) {
+      if (window.devicePixelRatio >= 2 && item.hidpi) {
+        if (item.hidpi != "<not loaded>") {
+          ok(loadedImages.has(item.hidpi), `Whitelisted image ${item.hidpi} was loaded.`);
+          ok(!shownImages.has(item.hidpi), `Whitelisted image ${item.hidpi} was not shown.`);
+        }
+      } else {
+        ok(loadedImages.has(item.file), `Whitelisted image ${item.file} was loaded.`);
+        ok(!shownImages.has(item.file), `Whitelisted image ${item.file} was not shown.`);
+      }
+    }
+  }
+}
--- a/browser/components/tests/startupRecorder.js
+++ b/browser/components/tests/startupRecorder.js
@@ -21,25 +21,31 @@ if (AppConstants.platform == "linux")
   * The records are meant to be used by startup tests in
   * browser/base/content/test/performance
   * This component only exists in nightly and debug builds, it doesn't ship in
   * our release builds.
   */
 function startupRecorder() {
   this.wrappedJSObject = this;
   this.loader = Cc["@mozilla.org/moz/jsloader;1"].getService(Ci.xpcIJSModuleLoader);
-  this.data = {};
+  this.data = {
+    images: {
+      "image-drawing": new Set(),
+      "image-loading": new Set(),
+    },
+    code: {}
+  };
 }
 startupRecorder.prototype = {
   classID: Components.ID("{11c095b2-e42e-4bdf-9dd0-aed87595f6a4}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
   record(name) {
-    this.data[name] = {
+    this.data.code[name] = {
       components: this.loader.loadedComponents(),
       modules: this.loader.loadedModules(),
       services: Object.keys(Cc).filter(c => {
         try {
           Cm.isServiceInstantiatedByContractID(c, Ci.nsISupports);
           return true;
         } catch (e) {
           return false;
@@ -52,27 +58,36 @@ startupRecorder.prototype = {
 
     if (topic == "app-startup") {
       // We can't ensure our observer will be called first or last, so the list of
       // topics we observe here should avoid the topics used to trigger things
       // during startup (eg. the topics observed by nsBrowserGlue.js).
       let topics = [
         "profile-do-change", // This catches stuff loaded during app-startup
         "toplevel-window-ready", // Catches stuff from final-ui-startup
+        "image-loading",
+        "image-drawing",
         firstPaintNotification,
         "sessionstore-windows-restored",
       ];
       for (let t of topics)
         Services.obs.addObserver(this, t);
       return;
     }
 
+    if (topic == "image-drawing" || topic == "image-loading") {
+      this.data.images[topic].add(data);
+      return;
+    }
+
     Services.obs.removeObserver(this, topic);
 
     if (topic == "sessionstore-windows-restored") {
+      Services.obs.removeObserver(this, "image-drawing");
+      Services.obs.removeObserver(this, "image-loading");
       // We use idleDispatchToMainThread here to record the set of
       // loaded scripts after we are fully done with startup and ready
       // to react to user events.
       Services.tm.idleDispatchToMainThread(
         this.record.bind(this, "before handling user events"));
     } else {
       const topicsToNames = {
         "profile-do-change": "before profile selection",
--- a/image/ImageFactory.cpp
+++ b/image/ImageFactory.cpp
@@ -87,16 +87,28 @@ ImageFactory::CreateImage(nsIRequest* aR
                           uint32_t aInnerWindowId)
 {
   MOZ_ASSERT(gfxPrefs::SingletonExists(),
              "Pref observers should have been initialized already");
 
   // Compute the image's initialization flags.
   uint32_t imageFlags = ComputeImageFlags(aURI, aMimeType, aIsMultiPart);
 
+#ifdef DEBUG
+  // Record the image load for startup performance testing.
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+    if (NS_WARN_IF(obs)) {
+      nsAutoCString spec;
+      aURI->GetSpec(spec);
+      obs->NotifyObservers(nullptr, "image-loading", NS_ConvertUTF8toUTF16(spec).get());
+    }
+  }
+#endif
+
   // Select the type of image to create based on MIME type.
   if (aMimeType.EqualsLiteral(IMAGE_SVG_XML)) {
     return CreateVectorImage(aRequest, aProgressTracker, aMimeType,
                              aURI, imageFlags, aInnerWindowId);
   } else {
     return CreateRasterImage(aRequest, aProgressTracker, aMimeType,
                              aURI, imageFlags, aInnerWindowId);
   }
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -1394,16 +1394,29 @@ RasterImage::DrawInternal(DrawableSurfac
                           SamplingFilter aSamplingFilter,
                           uint32_t aFlags,
                           float aOpacity)
 {
   gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
   ImageRegion region(aRegion);
   bool frameIsFinished = aSurface->IsFinished();
 
+#ifdef DEBUG
+  // Record the image drawing for startup performance testing.
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+    if (NS_WARN_IF(obs)) {
+      nsCOMPtr<nsIURI> imageURI = mURI->ToIURI();
+      nsAutoCString spec;
+      imageURI->GetSpec(spec);
+      obs->NotifyObservers(nullptr, "image-drawing", NS_ConvertUTF8toUTF16(spec).get());
+    }
+  }
+#endif
+
   // By now we may have a frame with the requested size. If not, we need to
   // adjust the drawing parameters accordingly.
   IntSize finalSize = aSurface->GetImageSize();
   bool couldRedecodeForBetterFrame = false;
   if (finalSize != aSize) {
     gfx::Size scale(double(aSize.width) / finalSize.width,
                     double(aSize.height) / finalSize.height);
     aContext->Multiply(gfxMatrix::Scaling(scale.width, scale.height));
--- a/image/VectorImage.cpp
+++ b/image/VectorImage.cpp
@@ -1020,16 +1020,29 @@ VectorImage::Show(gfxDrawable* aDrawable
   MOZ_ASSERT(aDrawable, "Should have a gfxDrawable by now");
   gfxUtils::DrawPixelSnapped(aParams.context, aDrawable,
                              aParams.size,
                              aParams.region,
                              SurfaceFormat::B8G8R8A8,
                              aParams.samplingFilter,
                              aParams.flags, aParams.opacity);
 
+#ifdef DEBUG
+  // Record the image drawing for startup performance testing.
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+    if (NS_WARN_IF(obs)) {
+      nsCOMPtr<nsIURI> imageURI = mURI->ToIURI();
+      nsAutoCString spec;
+      imageURI->GetSpec(spec);
+      obs->NotifyObservers(nullptr, "image-drawing", NS_ConvertUTF8toUTF16(spec).get());
+    }
+  }
+#endif
+
   MOZ_ASSERT(mRenderingObserver, "Should have a rendering observer by now");
   mRenderingObserver->ResumeHonoringInvalidations();
 }
 
 void
 VectorImage::RecoverFromLossOfSurfaces()
 {
   NS_WARNING("An imgFrame became invalid. Attempting to recover...");