Bug 1363059 - Add a test for images loaded at startup vs. images shown at startup. r=florian,jwatt
authorJohann Hofmann <jhofmann@mozilla.com>
Thu, 15 Jun 2017 00:11:48 +0200
changeset 366823 31f204fd055003ec1487597a79be89147452ab74
parent 366822 23c4b7136fd65e8dc167493eb4806350372e3fd9
child 366824 e0882faffdd74c73d746c302a28ff6d296f73ba5
push id32108
push usercbook@mozilla.com
push dateFri, 30 Jun 2017 10:57:12 +0000
treeherdermozilla-central@a578ce873d80 [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,229 @@
+/* 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/bookmark-hollow.svg",
+    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: ["win"],
+  },
+  {
+    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", "macosx"],
+  },
+  {
+    file: "chrome://browser/skin/urlbar-history-dropmarker.png",
+    hidpi: "<not loaded>",
+    platforms: ["win", "macosx"],
+    intermittentShown: ["win", "macosx"],
+  },
+
+  {
+    file: "chrome://browser/skin/yosemite/tab-selected-start-inactive.svg",
+    platforms: ["macosx"],
+  },
+  {
+    file: "chrome://browser/skin/yosemite/tab-active-middle-inactive.png",
+    platforms: ["macosx"],
+  },
+  {
+    file: "chrome://browser/skin/yosemite/tab-selected-end-inactive.svg",
+    platforms: ["macosx"],
+  },
+  {
+    file: "chrome://browser/skin/yosemite/tab-stroke-start-inactive.png",
+    platforms: ["macosx"],
+  },
+  {
+    file: "chrome://browser/skin/yosemite/tab-stroke-end-inactive.png",
+    platforms: ["macosx"],
+  },
+
+  {
+    file: "chrome://browser/skin/tabbrowser/newtab.png",
+    platforms: ["macosx"],
+  },
+
+  {
+    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", "linux"],
+  },
+
+  {
+    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), `Whitelisted image ${loaded} should not have been shown.`);
+      }
+      continue;
+    }
+    ok(shownImages.has(loaded), `Loaded image ${loaded} should have been 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} should have been loaded.`);
+        }
+      } else {
+        ok(loadedImages.has(item.file), `Whitelisted image ${item.file} should have been loaded.`);
+      }
+    }
+  }
+}
--- 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,32 +58,42 @@ 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") {
       // 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"));
+
+      Services.obs.removeObserver(this, "image-drawing");
+      Services.obs.removeObserver(this, "image-loading");
     } else {
       const topicsToNames = {
         "profile-do-change": "before profile selection",
         "toplevel-window-ready": "before opening first browser window",
       };
       topicsToNames[firstPaintNotification] = "before first paint";
       this.record(topicsToNames[topic]);
     }
--- 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
@@ -1390,16 +1390,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,
                              SizeDouble(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...");