Bug 1149486 - Extract a window title and window ID for PerformanceStats. r=mossop
☠☠ backed out by 6973f1341ace ☠ ☠
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Tue, 05 May 2015 18:14:05 +0200
changeset 276574 99b209f7d0852b79207d307d56088e3e18d3b58a
parent 276573 9b60d38c552e5dbf39f5b66a9ca348c78127e1a7
child 276575 cc90f71c22dc5423d517bfd3ba06225252c91c0e
push id897
push userjlund@mozilla.com
push dateMon, 14 Sep 2015 18:56:12 +0000
treeherdermozilla-release@9411e2d2b214 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmossop
bugs1149486
milestone41.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 1149486 - Extract a window title and window ID for PerformanceStats. r=mossop
toolkit/components/aboutperformance/content/aboutPerformance.js
toolkit/components/aboutperformance/tests/browser/browser.ini
toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js
toolkit/components/aboutperformance/tests/browser/browser_compartments.html
toolkit/components/aboutperformance/tests/browser/browser_compartments_frame.html
toolkit/components/aboutperformance/tests/browser/browser_compartments_script.js
toolkit/components/perfmonitoring/PerformanceStats.jsm
toolkit/components/perfmonitoring/moz.build
toolkit/components/perfmonitoring/nsIPerformanceStats.idl
toolkit/components/perfmonitoring/nsPerformanceStats.cpp
toolkit/components/perfmonitoring/tests/browser/browser.ini
toolkit/components/perfmonitoring/tests/browser/browser_compartments.html
toolkit/components/perfmonitoring/tests/browser/browser_compartments.js
toolkit/components/perfmonitoring/tests/browser/browser_compartments_frame.html
toolkit/components/perfmonitoring/tests/browser/browser_compartments_script.js
--- a/toolkit/components/aboutperformance/content/aboutPerformance.js
+++ b/toolkit/components/aboutperformance/content/aboutPerformance.js
@@ -221,17 +221,17 @@ function updateLiveData() {
       row.appendChild(el);
       if (item.addonId) {
         let _el = el;
         let _item = item;
         AddonManager.getAddonByID(item.addonId, a => {
           _el.textContent = a ? a.name : _item.name
         });
       } else {
-        el.textContent = item.name;
+        el.textContent = item.title || item.name;
       }
     }
   } catch (ex) {
     console.error(ex);
   }
 }
 
 function go() {
--- a/toolkit/components/aboutperformance/tests/browser/browser.ini
+++ b/toolkit/components/aboutperformance/tests/browser/browser.ini
@@ -1,11 +1,9 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
 [DEFAULT]
 head = head.js
-support-files = 
+support-files =
   browser_compartments.html
+  browser_compartments_frame.html
+  browser_compartments_script.js
 
 [browser_aboutperformance.js]
 skip-if = e10s # Feature not implemented yet – bug 1140310
--- a/toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js
+++ b/toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js
@@ -1,65 +1,67 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 Cu.import("resource://testing-common/ContentTask.jsm", this);
 
-const URL = "http://example.com/browser/toolkit/components/aboutperformance/tests/browser/browser_compartments.html?test=" + Math.random();
+const SALT = Math.random();
+const URL = "http://example.com/browser/toolkit/components/aboutperformance/tests/browser/browser_compartments.html?test=" + SALT;
 
 // This function is injected as source as a frameScript
 function frameScript() {
   "use strict";
 
-  addMessageListener("aboutperformance-test:hasItems", ({data: url}) => {
+  addMessageListener("aboutperformance-test:hasItems", ({data: salt}) => {
     let hasPlatform = false;
-    let hasURL = false;
+    let hasSalt = false;
 
     try {
       let eltData = content.document.getElementById("liveData");
       if (!eltData) {
         return;
       }
 
       // Find if we have a row for "platform"
       hasPlatform = eltData.querySelector("tr.platform") != null;
 
       // Find if we have a row for our URL
-      hasURL = false;
+      hasSalt = false;
       for (let eltContent of eltData.querySelectorAll("tr.content td.name")) {
-        if (eltContent.textContent == url) {
-          hasURL = true;
+        let name = eltContent.textContent;
+        if (name.contains(salt) && !name.contains("http")) {
+          hasSalt = true;
           break;
         }
       }
 
     } catch (ex) {
       Cu.reportError("Error in content: " + ex);
       Cu.reportError(ex.stack);
     } finally {
-      sendAsyncMessage("aboutperformance-test:hasItems", {hasPlatform, hasURL});
+      sendAsyncMessage("aboutperformance-test:hasItems", {hasPlatform, hasSalt});
     }
   });
 }
 
 
 add_task(function* test() {
   let tabAboutPerformance = gBrowser.addTab("about:performance");
   let tabContent = gBrowser.addTab(URL);
 
   yield ContentTask.spawn(tabAboutPerformance.linkedBrowser, null, frameScript);
 
   while (true) {
     yield new Promise(resolve => setTimeout(resolve, 100));
-    let {hasPlatform, hasURL} = (yield promiseContentResponse(tabAboutPerformance.linkedBrowser, "aboutperformance-test:hasItems", URL));
-    info(`Platform: ${hasPlatform}, url: ${hasURL}`);
-    if (hasPlatform && hasURL) {
-      Assert.ok(true, "Found a row for <platform> and a row for our URL");
+    let {hasPlatform, hasSalt} = (yield promiseContentResponse(tabAboutPerformance.linkedBrowser, "aboutperformance-test:hasItems", SALT));
+    info(`Platform: ${hasPlatform}, salt: ${hasSalt}`);
+    if (hasPlatform && hasSalt) {
+      Assert.ok(true, "Found a row for <platform> and a row for our page");
       break;
     }
   }
 
   // Cleanup
   gBrowser.removeTab(tabContent);
   gBrowser.removeTab(tabAboutPerformance);
 });
--- a/toolkit/components/aboutperformance/tests/browser/browser_compartments.html
+++ b/toolkit/components/aboutperformance/tests/browser/browser_compartments.html
@@ -1,25 +1,25 @@
 <!DOCTYPE html>
 <html>
 <head>
   <title>
-    browser_compartments.html
+    Main frame for test browser_compartments.js
   </title>
-  <script type="text/javascript">
-  // Use some CPU.
-  window.setInterval(() => {
-    // Compute an arbitrary value, print it out to make sure that the JS
-    // engine doesn't discard all our computation.
-    var date = Date.now();
-    var array = [];
-    var i = 0;
-    while (Date.now() - date <= 100) {
-      array[i%2] = i++;
-    }
-    console.log("Arbitrary value", array);
-  }, 300);
+  <script>
+  function onload() {
+    var salt = new URL(window.location).searchParams.get('test');
+    document.title = `${document.title}: ${salt}`;    
+  }
   </script>
 </head>
-<body>
-browser_compartments.html
+<body onload="onload()">
+Main frame.
+
+<iframe src="browser_compartments_frame.html?frame=1">
+  Subframe 1
+</iframe>
+
+<iframe src="browser_compartments_frame.html?frame=2">
+  Subframe 2.
+</iframe>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/aboutperformance/tests/browser/browser_compartments_frame.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>
+    Subframe for test browser_compartments.html (do not change this title)
+  </title>
+  <script src="browser_compartments_script.js"></script>
+</head>
+<body>
+Subframe loaded.
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/aboutperformance/tests/browser/browser_compartments_script.js
@@ -0,0 +1,20 @@
+
+window.addEventListener("message", e => {
+  console.log("frame content", "message", e);
+  if ("title" in e.data) {
+    document.title = e.data.title;
+  }
+});
+
+// Use some CPU.
+window.setInterval(() => {
+  // Compute an arbitrary value, print it out to make sure that the JS
+  // engine doesn't discard all our computation.
+  var date = Date.now();
+  var array = [];
+  var i = 0;
+  while (Date.now() - date <= 100) {
+    array[i%2] = i++;
+  }
+  console.log("Arbitrary value", window.location, array);
+}, 300);
--- a/toolkit/components/perfmonitoring/PerformanceStats.jsm
+++ b/toolkit/components/perfmonitoring/PerformanceStats.jsm
@@ -20,32 +20,42 @@ const { classes: Cc, interfaces: Ci, uti
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 
 let performanceStatsService =
   Cc["@mozilla.org/toolkit/performance-stats-service;1"].
   getService(Ci.nsIPerformanceStatsService);
 
 
 const PROPERTIES_NUMBERED = ["totalUserTime", "totalSystemTime", "totalCPOWTime", "ticks"];
-const PROPERTIES_META = ["name", "addonId", "isSystem"];
+const PROPERTIES_META_IMMUTABLE = ["name", "addonId", "isSystem"];
+const PROPERTIES_META = [...PROPERTIES_META_IMMUTABLE, "windowId", "title"];
 const PROPERTIES_FLAT = [...PROPERTIES_NUMBERED, ...PROPERTIES_META];
 
 /**
  * Information on a single component.
  *
  * This offers the following fields:
  *
  * @field {string} name The name of the component:
  * - for the process itself, "<process>";
  * - for platform code, "<platform>";
  * - for an add-on, the identifier of the addon (e.g. "myaddon@foo.bar");
  * - for a webpage, the url of the page.
  *
  * @field {string} addonId The identifier of the addon (e.g. "myaddon@foo.bar").
  *
+ * @field {string|null} title The title of the webpage to which this code
+ *  belongs. Note that this is the title of the entire webpage (i.e. the tab),
+ *  even if the code is executed in an iframe. Also note that this title may
+ *  change over time.
+ *
+ * @field {number} windowId The outer window ID of the top-level nsIDOMWindow
+ *  to which this code belongs. May be 0 if the code doesn't belong to any
+ *  nsIDOMWindow.
+ *
  * @field {boolean} isSystem `true` if the component is a system component (i.e.
  *  an add-on or platform-code), `false` otherwise (i.e. a webpage).
  *
  * @field {number} totalUserTime The total amount of time spent executing code.
  *
  * @field {number} totalSystemTime The total amount of time spent executing
  *  system calls.
  *
--- a/toolkit/components/perfmonitoring/moz.build
+++ b/toolkit/components/perfmonitoring/moz.build
@@ -23,9 +23,14 @@ XPIDL_SOURCES += [
 UNIFIED_SOURCES += [
     'nsPerformanceStats.cpp'
 ]
 
 EXPORTS += [
     'nsPerformanceStats.h'
 ]
 
+# We need nsGlobalWindow
+LOCAL_INCLUDES += [
+    '/dom/base',
+]
+
 FINAL_LIBRARY = 'xul'
--- a/toolkit/components/perfmonitoring/nsIPerformanceStats.idl
+++ b/toolkit/components/perfmonitoring/nsIPerformanceStats.idl
@@ -1,32 +1,33 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-*/
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 #include "nsIArray.idl"
+#include "nsIDOMWindow.idl"
 
 /**
  * Mechanisms for querying the current process about performance
  * information.
  *
  * JavaScript clients should rather use PerformanceStats.jsm.
  */
 
 /**
  * Snapshot of the performance of a component, e.g. an add-on, a web
  * page, system built-ins, or the entire process itself.
  *
  * All values are monotonic and are updated only when
  * `nsIPerformanceStatsService.isStopwatchActive` is `true`.
  */
-[scriptable, uuid(f015cbad-e16f-4982-a080-67e4e69a5b2e)]
+[scriptable, uuid(c2e2d494-06b9-40c5-91f7-505086dc4ecd)]
 interface nsIPerformanceStats: nsISupports {
   /**
    * The name of the component:
    * - for the process itself, "<process>";
    * - for platform code, "<platform>";
    * - for an add-on, the identifier of the addon (e.g. "myaddon@foo.bar");
    * - for a webpage, the url of the page.
    */
@@ -34,16 +35,28 @@ interface nsIPerformanceStats: nsISuppor
 
   /**
    * If the component is an add-on, the ID of the addon,
    * otherwise an empty string.
    */
   readonly attribute AString addonId;
 
   /**
+   * If the component is code executed in a window, the ID of the topmost
+   * outer window (i.e. the tab), otherwise 0.
+   */
+  readonly attribute uint64_t windowId;
+
+  /**
+   * If the component is code executed in a window, the title of the topmost
+   * window (i.e. the tab), otherwise an empty string.
+   */
+  readonly attribute AString title;
+
+  /**
    * Total amount of time spent executing code in this group, in
    * microseconds.
    */
   readonly attribute unsigned long long totalUserTime;
   readonly attribute unsigned long long totalSystemTime;
   readonly attribute unsigned long long totalCPOWTime;
 
   /**
@@ -86,26 +99,26 @@ interface nsIPerformanceSnapshot: nsISup
    * This contains the total amount of time spent executing JS code,
    * the total amount of time spent waiting for system calls while
    * executing JS code, the total amount of time performing blocking
    * inter-process calls, etc.
    */
   nsIPerformanceStats getProcessData();
 };
 
-[scriptable, builtinclass, uuid(5795113a-39a1-4087-ba09-98b7d07d025a)]
+[scriptable, builtinclass, uuid(3e4bff64-555f-41ec-9e7f-9338c77fd696)]
 interface nsIPerformanceStatsService : nsISupports {
   /**
    * `true` if we should monitor performance, `false` otherwise.
    */
   [implicit_jscontext] attribute bool isStopwatchActive;
 
   /**
    * Capture a snapshot of the performance data.
    */
-  nsIPerformanceSnapshot getSnapshot();
+  [implicit_jscontext] nsIPerformanceSnapshot getSnapshot();
 };
 
 %{C++
 #define NS_TOOLKIT_PERFORMANCESTATSSERVICE_CID {0xfd7435d4, 0x9ec4, 0x4699, \
       {0xad, 0xd4, 0x1b, 0xe8, 0x3d, 0xd6, 0x8e, 0xf3} }
 #define NS_TOOLKIT_PERFORMANCESTATSSERVICE_CONTRACTID "@mozilla.org/toolkit/performance-stats-service;1"
 %}
--- a/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
+++ b/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
@@ -1,31 +1,44 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "jsapi.h"
 #include "nsPerformanceStats.h"
+
 #include "nsMemory.h"
 #include "nsLiteralString.h"
 #include "nsCRTGlue.h"
-#include "nsIJSRuntimeService.h"
 #include "nsServiceManagerUtils.h"
+
 #include "nsCOMArray.h"
 #include "nsIMutableArray.h"
+
+#include "jsapi.h"
 #include "nsJSUtils.h"
 #include "xpcpublic.h"
 #include "jspubtd.h"
+#include "nsIJSRuntimeService.h"
+
+#include "nsIDOMWindow.h"
+#include "nsGlobalWindow.h"
 
 class nsPerformanceStats: public nsIPerformanceStats {
 public:
-  nsPerformanceStats(nsAString& aName, nsAString& aAddonId, bool aIsSystem, js::PerformanceData& aPerformanceData)
+  nsPerformanceStats(const nsAString& aName,
+                     const nsAString& aAddonId,
+                     const nsAString& aTitle,
+                     const uint64_t aWindowId,
+                     const bool aIsSystem,
+                     const js::PerformanceData& aPerformanceData)
     : mName(aName)
     , mAddonId(aAddonId)
+    , mTitle(aTitle)
+    , mWindowId(aWindowId)
     , mIsSystem(aIsSystem)
     , mPerformanceData(aPerformanceData)
   {
   }
   explicit nsPerformanceStats() {}
 
   NS_DECL_ISUPPORTS
 
@@ -36,16 +49,34 @@ public:
   };
 
   /* readonly attribute AString addon id; */
   NS_IMETHOD GetAddonId(nsAString& aAddonId) override {
     aAddonId.Assign(mAddonId);
     return NS_OK;
   };
 
+  /* readonly attribute uint64_t windowId; */
+  NS_IMETHOD GetWindowId(uint64_t *aWindowId) override {
+    *aWindowId = mWindowId;
+    return NS_OK;
+  }
+
+  /* readonly attribute AString title; */
+  NS_IMETHOD GetTitle(nsAString & aTitle) override {
+    aTitle.Assign(mTitle);
+    return NS_OK;
+  }
+
+  /* readonly attribute bool isSystem; */
+  NS_IMETHOD GetIsSystem(bool *_retval) override {
+    *_retval = mIsSystem;
+    return NS_OK;
+  }
+
   /* readonly attribute unsigned long long totalUserTime; */
   NS_IMETHOD GetTotalUserTime(uint64_t *aTotalUserTime) override {
     *aTotalUserTime = mPerformanceData.totalUserTime;
     return NS_OK;
   };
 
   /* readonly attribute unsigned long long totalSystemTime; */
   NS_IMETHOD GetTotalSystemTime(uint64_t *aTotalSystemTime) override {
@@ -73,94 +104,203 @@ public:
     }
     *aNumberOfOccurrences = new uint64_t[length];
     for (size_t i = 0; i < length; ++i) {
       (*aNumberOfOccurrences)[i] = mPerformanceData.durations[i];
     }
     return NS_OK;
   };
 
-  /* readonly attribute bool isSystem; */
-  NS_IMETHOD GetIsSystem(bool *_retval) override {
-    *_retval = mIsSystem;
-    return NS_OK;
-  }
-
 private:
   nsString mName;
   nsString mAddonId;
+  nsString mTitle;
+  uint64_t mWindowId;
   bool mIsSystem;
+
   js::PerformanceData mPerformanceData;
 
   virtual ~nsPerformanceStats() {}
 };
 
 NS_IMPL_ISUPPORTS(nsPerformanceStats, nsIPerformanceStats)
 
 
 class nsPerformanceSnapshot : public nsIPerformanceSnapshot
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPERFORMANCESNAPSHOT
 
   nsPerformanceSnapshot();
-  nsresult Init();
+  nsresult Init(JSContext*);
 private:
   virtual ~nsPerformanceSnapshot();
 
   /**
-   * Import a `PerformanceStats` as a `nsIPerformanceStats`.
+   * Import a `js::PerformanceStats` as a `nsIPerformanceStats`.
+   *
+   * Precondition: this method assumes that we have entered the JSCompartment for which data `c`
+   * has been collected.
+   *
+   * `cx` may be `nullptr` if we are importing the statistics for the
+   * entire process, rather than the statistics for a specific set of
+   * compartments.
+   */
+  already_AddRefed<nsIPerformanceStats> ImportStats(JSContext* cx, const js::PerformanceData& data);
+
+  /**
+   * Callbacks for iterating through the `PerformanceStats` of a runtime.
    */
-  already_AddRefed<nsIPerformanceStats> ImportStats(js::PerformanceStats* c);
+  bool IterPerformanceStatsCallbackInternal(JSContext* cx, const js::PerformanceData& stats);
+  static bool IterPerformanceStatsCallback(JSContext* cx, const js::PerformanceData& stats, void* self);
+
+  // If the context represents a window, extract the title and window ID.
+  // Otherwise, extract "" and 0.
+  static void GetWindowData(JSContext*,
+                            nsString& title,
+                            uint64_t* windowId);
 
+  // If the context presents an add-on, extract the addon ID.
+  // Otherwise, extract "".
+  static void GetAddonId(JSContext*,
+                         JS::Handle<JSObject*> global,
+                         nsAString& addonId);
+
+  // Determine whether a context is part of the system principals.
+  static bool GetIsSystem(JSContext*,
+                          JS::Handle<JSObject*> global);
+
+private:
   nsCOMArray<nsIPerformanceStats> mComponentsData;
   nsCOMPtr<nsIPerformanceStats> mProcessData;
 };
 
 NS_IMPL_ISUPPORTS(nsPerformanceSnapshot, nsIPerformanceSnapshot)
 
 nsPerformanceSnapshot::nsPerformanceSnapshot()
 {
 }
 
 nsPerformanceSnapshot::~nsPerformanceSnapshot()
 {
 }
 
+/* static */ void
+nsPerformanceSnapshot::GetWindowData(JSContext* cx,
+                                     nsString& title,
+                                     uint64_t* windowId)
+{
+  MOZ_ASSERT(windowId);
+
+  title.SetIsVoid(true);
+  *windowId = 0;
+
+  nsCOMPtr<nsPIDOMWindow> win = xpc::CurrentWindowOrNull(cx);
+  if (!win) {
+    return;
+  }
+
+  nsCOMPtr<nsIDOMWindow> top;
+  nsresult rv = win->GetTop(getter_AddRefs(top));
+  if (!top || NS_FAILED(rv)) {
+    return;
+  }
+
+  nsCOMPtr<nsPIDOMWindow> privateTop = do_QueryInterface(top);
+  if (!privateTop) {
+    return;
+  }
+
+  nsCOMPtr<nsIDocument> doc = privateTop->GetExtantDoc();
+  if (!doc) {
+    return;
+  }
+  doc->GetTitle(title);
+  *windowId = privateTop->WindowID();
+}
+
+/* static */ void
+nsPerformanceSnapshot::GetAddonId(JSContext*,
+                                  JS::Handle<JSObject*> global,
+                                  nsAString& addonId)
+{
+  addonId.AssignLiteral("");
+
+  JSAddonId* jsid = AddonIdOfObject(global);
+  if (!jsid) {
+    return;
+  }
+  AssignJSFlatString(addonId, (JSFlatString*)jsid);
+}
+
+/* static */ bool
+nsPerformanceSnapshot::GetIsSystem(JSContext*,
+                                   JS::Handle<JSObject*> global)
+{
+  return nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(global));
+}
+
 already_AddRefed<nsIPerformanceStats>
-nsPerformanceSnapshot::ImportStats(js::PerformanceStats* c) {
+nsPerformanceSnapshot::ImportStats(JSContext* cx, const js::PerformanceData& performance) {
+  JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+
+  if (!global) {
+    // While it is possible for a compartment to have no global
+    // (e.g. atoms), this compartment is not very interesting for us.
+    return nullptr;
+  }
+
   nsString addonId;
-  if (c->addonId) {
-    AssignJSFlatString(addonId, (JSFlatString*)c->addonId);
+  GetAddonId(cx, global, addonId);
+
+  nsString title;
+  uint64_t windowId;
+  GetWindowData(cx, title, &windowId);
+
+  nsAutoString name;
+  nsAutoCString cname;
+  xpc::GetCurrentCompartmentName(cx, cname);
+  name.Assign(NS_ConvertUTF8toUTF16(cname));
+
+  bool isSystem = GetIsSystem(cx, global);
+
+  nsCOMPtr<nsIPerformanceStats> result =
+    new nsPerformanceStats(name, addonId, title, windowId, isSystem, performance);
+  return result.forget();
+}
+
+/*static*/ bool
+nsPerformanceSnapshot::IterPerformanceStatsCallback(JSContext* cx, const js::PerformanceData& stats, void* self) {
+  return reinterpret_cast<nsPerformanceSnapshot*>(self)->IterPerformanceStatsCallbackInternal(cx, stats);
+}
+
+bool
+nsPerformanceSnapshot::IterPerformanceStatsCallbackInternal(JSContext* cx, const js::PerformanceData& stats) {
+  nsCOMPtr<nsIPerformanceStats> result = ImportStats(cx, stats);
+  if (result) {
+    mComponentsData.AppendElement(result);
   }
-  nsCString cname(c->name);
-  NS_ConvertUTF8toUTF16 name(cname);
-  nsCOMPtr<nsIPerformanceStats> result = new nsPerformanceStats(name, addonId, c->isSystem, c->performance);
-  return result.forget();
+
+  return true;
 }
 
 nsresult
-nsPerformanceSnapshot::Init() {
-  JSRuntime* rt;
-  nsCOMPtr<nsIJSRuntimeService> svc(do_GetService("@mozilla.org/js/xpc/RuntimeService;1"));
-  NS_ENSURE_TRUE(svc, NS_ERROR_FAILURE);
-  svc->GetRuntime(&rt);
-  js::PerformanceStats processStats;
-  js::PerformanceStatsVector componentsStats;
-  if (!js::GetPerformanceStats(rt, componentsStats, processStats)) {
-    return NS_ERROR_OUT_OF_MEMORY;
+nsPerformanceSnapshot::Init(JSContext* cx) {
+  js::PerformanceData processStats;
+  if (!js::IterPerformanceStats(cx, nsPerformanceSnapshot::IterPerformanceStatsCallback, &processStats, this)) {
+    return NS_ERROR_UNEXPECTED;
   }
 
-  size_t num = componentsStats.length();
-  for (size_t pos = 0; pos < num; pos++) {
-    nsCOMPtr<nsIPerformanceStats> stats = ImportStats(&componentsStats[pos]);
-    mComponentsData.AppendObject(stats);
-  }
-  mProcessData = ImportStats(&processStats);
+  mProcessData = new nsPerformanceStats(NS_LITERAL_STRING("<process>"), // name
+                                        NS_LITERAL_STRING(""),          // add-on id
+                                        NS_LITERAL_STRING(""),          // title
+                                        0,                              // window id
+                                        true,                           // isSystem
+                                        processStats);
   return NS_OK;
 }
 
 
 /* void getComponentsData (out nsIArray aComponents); */
 NS_IMETHODIMP nsPerformanceSnapshot::GetComponentsData(nsIArray * *aComponents)
 {
   const size_t length = mComponentsData.Length();
@@ -204,20 +344,20 @@ NS_IMETHODIMP nsPerformanceStatsService:
   JSRuntime *runtime = JS_GetRuntime(cx);
   if (!js::SetStopwatchActive(runtime, aIsStopwatchActive)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   return NS_OK;
 }
 
 /* readonly attribute nsIPerformanceSnapshot snapshot; */
-NS_IMETHODIMP nsPerformanceStatsService::GetSnapshot(nsIPerformanceSnapshot * *aSnapshot)
+NS_IMETHODIMP nsPerformanceStatsService::GetSnapshot(JSContext* cx, nsIPerformanceSnapshot * *aSnapshot)
 {
   nsRefPtr<nsPerformanceSnapshot> snapshot = new nsPerformanceSnapshot();
-  nsresult rv = snapshot->Init();
+  nsresult rv = snapshot->Init(cx);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   snapshot.forget(aSnapshot);
   return NS_OK;
 }
 
--- a/toolkit/components/perfmonitoring/tests/browser/browser.ini
+++ b/toolkit/components/perfmonitoring/tests/browser/browser.ini
@@ -1,8 +1,10 @@
 [DEFAULT]
 head = head.js
 support-files =
   browser_Addons_sample.xpi
   browser_compartments.html
+  browser_compartments_frame.html
+  browser_compartments_script.js
 
 [browser_AddonWatcher.js]
 [browser_compartments.js]
--- a/toolkit/components/perfmonitoring/tests/browser/browser_compartments.html
+++ b/toolkit/components/perfmonitoring/tests/browser/browser_compartments.html
@@ -1,25 +1,20 @@
 <!DOCTYPE html>
 <html>
 <head>
   <title>
-    browser_compartments.html
+    Main frame for test browser_compartments.js
   </title>
-  <script type="text/javascript">
-  // Use some CPU.
-  window.setInterval(() => {
-    // Compute an arbitrary value, print it out to make sure that the JS
-    // engine doesn't discard all our computation.
-    var date = Date.now();
-    var array = [];
-    var i = 0;
-    while (Date.now() - date <= 100) {
-      array[i%2] = i++;
-    }
-    console.log("Arbitrary value", array);
-  }, 300);
-  </script>
 </head>
 <body>
-browser_compartments.html
+Main frame.
+
+<iframe src="browser_compartments_frame.html?frame=1">
+  Subframe 1
+</iframe>
+
+<iframe src="browser_compartments_frame.html?frame=2">
+  Subframe 2.
+</iframe>
+
 </body>
 </html>
--- a/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js
+++ b/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js
@@ -1,17 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+/**
+ * Test that we see jank that takes place in a webpage,
+ * and that jank from several iframes are actually charged
+ * to the top window.
+ */
 Cu.import("resource://gre/modules/PerformanceStats.jsm", this);
 Cu.import("resource://testing-common/ContentTask.jsm", this);
 
 const URL = "http://example.com/browser/toolkit/components/perfmonitoring/tests/browser/browser_compartments.html?test=" + Math.random();
+const PARENT_TITLE = `Main frame for test browser_compartments.js ${Math.random()}`;
+const FRAME_TITLE = `Subframe for test browser_compartments.js ${Math.random()}`;
 
 // This function is injected as source as a frameScript
 function frameScript() {
   "use strict";
 
   const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
   Cu.import("resource://gre/modules/PerformanceStats.jsm");
 
@@ -25,16 +32,31 @@ function frameScript() {
   addMessageListener("compartments-test:getStatistics", () => {
     try {
       sendAsyncMessage("compartments-test:getStatistics", PerformanceStats.getSnapshot());
     } catch (ex) {
       Cu.reportError("Error in content: " + ex);
       Cu.reportError(ex.stack);
     }
   });
+
+  addMessageListener("compartments-test:setTitles", titles => {
+    try {
+      console.log("content", "Setting titles", Object.keys(titles));
+      content.document.title = titles.data.parent;
+      for (let i = 0; i < content.frames.length; ++i) {
+        content.frames[i].postMessage({title: titles.data.frames}, "*");
+      }
+      console.log("content", "Done setting titles", content.document.title);
+      sendAsyncMessage("compartments-test:setTitles");
+    } catch (ex) {
+      Cu.reportError("Error in content: " + ex);
+      Cu.reportError(ex.stack);
+    }
+  });
 }
 
 // A variant of `Assert` that doesn't spam the logs
 // in case of success.
 let SilentAssert = {
   equal: function(a, b, msg) {
     if (a == b) {
       return;
@@ -67,38 +89,39 @@ function monotinicity_tester(source, tes
   // - there is at most one component with a combination of `name` and `addonId`;
   // - types, etc.
   let previous = {
     processData: null,
     componentsMap: new Map(),
   };
 
   let sanityCheck = function(prev, next) {
+    let name = `${testName}: ${next.name}`;
     info(`Sanity check: ${JSON.stringify(next, null, "\t")}`);
     if (prev == null) {
       return;
     }
     for (let k of ["name", "addonId", "isSystem"]) {
-      SilentAssert.equal(prev[k], next[k], `Sanity check (${testName}): ${k} hasn't changed.`);
+      SilentAssert.equal(prev[k], next[k], `Sanity check (${name}): ${k} hasn't changed.`);
     }
     for (let k of ["totalUserTime", "totalSystemTime", "totalCPOWTime", "ticks"]) {
-      SilentAssert.equal(typeof next[k], "number", `Sanity check (${testName}): ${k} is a number.`);
-      SilentAssert.leq(prev[k], next[k], `Sanity check (${testName}): ${k} is monotonic.`);
-      SilentAssert.leq(0, next[k], `Sanity check (${testName}): ${k} is >= 0.`)
+      SilentAssert.equal(typeof next[k], "number", `Sanity check (${name}): ${k} is a number.`);
+      SilentAssert.leq(prev[k], next[k], `Sanity check (${name}): ${k} is monotonic.`);
+      SilentAssert.leq(0, next[k], `Sanity check (${name}): ${k} is >= 0.`)
     }
     SilentAssert.equal(prev.durations.length, next.durations.length);
     for (let i = 0; i < next.durations.length; ++i) {
       SilentAssert.ok(typeof next.durations[i] == "number" && next.durations[i] >= 0,
-        `Sanity check (${testName}): durations[${i}] is a non-negative number.`);
+        `Sanity check (${name}): durations[${i}] is a non-negative number.`);
       SilentAssert.leq(prev.durations[i], next.durations[i],
-        `Sanity check (${testName}): durations[${i}] is monotonic.`)
+        `Sanity check (${name}): durations[${i}] is monotonic.`)
     }
     for (let i = 0; i < next.durations.length - 1; ++i) {
       SilentAssert.leq(next.durations[i + 1], next.durations[i],
-        `Sanity check (${testName}): durations[${i}] >= durations[${i + 1}].`)
+        `Sanity check (${name}): durations[${i}] >= durations[${i + 1}].`)
     }
   };
   let iteration = 0;
   let frameCheck = Task.async(function*() {
     let name = `${testName}: ${iteration++}`;
     let snapshot = yield source();
     if (!snapshot) {
       // This can happen at the end of the test when we attempt
@@ -113,17 +136,17 @@ function monotinicity_tester(source, tes
     SilentAssert.equal(snapshot.processData.name, "<process>");
     SilentAssert.equal(snapshot.processData.addonId, "");
     previous.procesData = snapshot.processData;
 
     // Sanity check on components data.
     let set = new Set();
     let keys = [];
     for (let item of snapshot.componentsData) {
-      let key = `{name: ${item.name}, addonId: ${item.addonId}, isSystem: ${item.isSystem}}`;
+      let key = `{name: ${item.name}, addonId: ${item.addonId}, isSystem: ${item.isSystem}, windowId: ${item.windowId}}`;
       keys.push(key);
       set.add(key);
       sanityCheck(previous.componentsMap.get(key), item);
       previous.componentsMap.set(key, item);
 
       for (let k of ["totalUserTime", "totalSystemTime", "totalCPOWTime"]) {
         SilentAssert.leq(item[k], snapshot.processData[k],
           `Sanity check (${testName}): component has a lower ${k} than process`);
@@ -161,24 +184,47 @@ add_task(function* test() {
   } else {
     info("Setting up sanity checks");
     monotinicity_tester(() => PerformanceStats.getSnapshot(), "parent process");
     monotinicity_tester(() => promiseContentResponseOrNull(browser, "compartments-test:getStatistics", null), "content process" );
   }
 
   let skipTotalUserTime = hasLowPrecision();
 
+
   while (true) {
+    yield new Promise(resolve => setTimeout(resolve, 100));
+
+    // We may have race conditions with DOM loading.
+    // Don't waste too much brainpower here, let's just ask
+    // repeatedly for the title to be changed, until this works.
+    info("Setting titles");
+    yield promiseContentResponse(browser, "compartments-test:setTitles", {
+      parent: PARENT_TITLE,
+      frames: FRAME_TITLE
+    });
+    info("Titles set");
+
     let stats = (yield promiseContentResponse(browser, "compartments-test:getStatistics", null));
-    let found = stats.componentsData.find(stat => {
-      return (stat.name.indexOf(URL) != -1)
-      && (skipTotalUserTime || stat.totalUserTime > 1000)
-    });
-    if (found) {
-      info(`Expected totalUserTime > 1000, got ${found.totalUserTime}`);
+
+    // While the webpage consists in three compartments, we should see only
+    // one `PerformanceData` in `componentsData`. Its `name` is undefined
+    // (could be either the main frame or one of its subframes), but its
+    // `title` should be the title of the main frame.
+    let frame = stats.componentsData.find(stat => stat.title == FRAME_TITLE);
+    Assert.equal(frame, null, "Searching by title, the frames don't show up in the list of components");
+
+    let parent = stats.componentsData.find(stat => stat.title == PARENT_TITLE);
+    if (!parent) {
+      info("Searching by title, we didn't find the main frame");
+      continue;
+    }
+    info("Searching by title, we found the main frame");
+
+    info(`Total user time: ${parent.totalUserTime}`);
+    if (skipTotalUserTime || parent.totalUserTime > 1000) {
       break;
     }
-    yield new Promise(resolve => setTimeout(resolve, 100));
   }
 
   // Cleanup
   gBrowser.removeTab(newTab);
 });
new file mode 100644
--- /dev/null
+++ b/toolkit/components/perfmonitoring/tests/browser/browser_compartments_frame.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>
+    Subframe for test browser_compartments.html (do not change this title)
+  </title>
+  <script src="browser_compartments_script.js"></script>
+</head>
+<body>
+Subframe loaded.
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/perfmonitoring/tests/browser/browser_compartments_script.js
@@ -0,0 +1,20 @@
+
+window.addEventListener("message", e => {
+  console.log("frame content", "message", e);
+  if ("title" in e.data) {
+    document.title = e.data.title;
+  }
+});
+
+// Use some CPU.
+window.setInterval(() => {
+  // Compute an arbitrary value, print it out to make sure that the JS
+  // engine doesn't discard all our computation.
+  var date = Date.now();
+  var array = [];
+  var i = 0;
+  while (Date.now() - date <= 100) {
+    array[i%2] = i++;
+  }
+  console.log("Arbitrary value", window.location, array);
+}, 300);