Bug 1559657: Pass load time in RemoteWebProgress. r=Ehsan,barret
authorWill Hawkins <whawkins@mozilla.com>
Thu, 11 Jul 2019 04:00:41 +0000
changeset 482330 3f3048189adbaef8933a5da5600e88b8a1aadc9b
parent 482329 012d1b8802007b5d0dab4e741b1eead6186944c3
child 482331 9c274e3d473bdd3bb917ff34fabefd555db2d2cd
push id89700
push usercsabou@mozilla.com
push dateThu, 11 Jul 2019 04:20:53 +0000
treeherderautoland@3f3048189adb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersEhsan, barret
bugs1559657
milestone70.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 1559657: Pass load time in RemoteWebProgress. r=Ehsan,barret Differential Revision: https://phabricator.services.mozilla.com/D35147
dom/ipc/BrowserChild.cpp
dom/ipc/BrowserParent.cpp
dom/ipc/PBrowser.ipdl
dom/ipc/RemoteWebProgressRequest.cpp
dom/ipc/RemoteWebProgressRequest.h
dom/ipc/nsIRemoteWebProgressRequest.idl
dom/ipc/tests/browser.ini
dom/ipc/tests/browser_ElapsedTime.js
dom/ipc/tests/elapsed_time.sjs
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -3489,16 +3489,39 @@ NS_IMETHODIMP BrowserChild::OnStateChang
 
   Maybe<WebProgressData> webProgressData;
   Maybe<WebProgressStateChangeData> stateChangeData;
   RequestData requestData;
 
   MOZ_TRY(PrepareProgressListenerData(aWebProgress, aRequest, webProgressData,
                                       requestData));
 
+  /*
+   * If
+   * 1) this is a document,
+   * 2) the document is top-level,
+   * 3) the document is completely loaded (STATE_STOP), and
+   * 4) this is the end of activity for the document
+   *    (STATE_IS_WINDOW, STATE_IS_NETWORK),
+   * then record the elapsed time that it took to load.
+   */
+  if (document && webProgressData->isTopLevel() &&
+      (aStateFlags & nsIWebProgressListener::STATE_STOP) &&
+      (aStateFlags & nsIWebProgressListener::STATE_IS_WINDOW) &&
+      (aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK)) {
+    RefPtr<nsDOMNavigationTiming> navigationTiming =
+        document->GetNavigationTiming();
+    if (navigationTiming) {
+      TimeDuration elapsedLoadTimeMS =
+          TimeStamp::Now() - navigationTiming->GetNavigationStartTimeStamp();
+      requestData.elapsedLoadTimeMS() =
+          Some(elapsedLoadTimeMS.ToMilliseconds());
+    }
+  }
+
   if (webProgressData->isTopLevel()) {
     stateChangeData.emplace();
 
     stateChangeData->isNavigating() = docShell->GetIsNavigating();
     stateChangeData->mayEnableCharacterEncodingMenu() =
         docShell->GetMayEnableCharacterEncodingMenu();
     stateChangeData->charsetAutodetected() = docShell->GetCharsetAutodetected();
 
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -2618,17 +2618,17 @@ void BrowserParent::ReconstructWebProgre
   } else {
     webProgress = new RemoteWebProgress(aManager, 0, 0, 0, false, false);
   }
   webProgress.forget(aOutWebProgress);
 
   if (aRequestData.requestURI()) {
     nsCOMPtr<nsIRequest> request = MakeAndAddRef<RemoteWebProgressRequest>(
         aRequestData.requestURI(), aRequestData.originalRequestURI(),
-        aRequestData.matchedList());
+        aRequestData.matchedList(), aRequestData.elapsedLoadTimeMS());
     request.forget(aOutRequest);
   } else {
     *aOutRequest = nullptr;
   }
 }
 
 mozilla::ipc::IPCResult BrowserParent::RecvSessionStoreUpdate(
     const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode,
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -113,16 +113,22 @@ struct WebProgressData
   uint64_t innerDOMWindowID;
 };
 
 struct RequestData
 {
   nsIURI requestURI;
   nsIURI originalRequestURI;
   nsCString matchedList;
+  // The elapsedLoadTimeMS is only set when the request has finished loading.
+  // In other words, this field is set only during and |OnStateChange| event
+  // where |aStateFlags| contains |nsIWebProgressListener::STATE_STOP| and
+  // |nsIWebProgressListener::STATE_IS_NETWORK| and
+  // |nsIWebProgressListener::STATE_IS_WINDOW|, and the document is top level.
+  uint64_t? elapsedLoadTimeMS;
 };
 
 struct WebProgressStateChangeData
 {
   bool isNavigating;
   bool mayEnableCharacterEncodingMenu;
   bool charsetAutodetected;
 
--- a/dom/ipc/RemoteWebProgressRequest.cpp
+++ b/dom/ipc/RemoteWebProgressRequest.cpp
@@ -13,16 +13,27 @@ NS_IMPL_ISUPPORTS(RemoteWebProgressReque
 NS_IMETHODIMP RemoteWebProgressRequest::Init(nsIURI* aURI,
                                              nsIURI* aOriginalURI) {
   mURI = aURI;
   mOriginalURI = aOriginalURI;
 
   return NS_OK;
 }
 
+NS_IMETHODIMP RemoteWebProgressRequest::GetElapsedLoadTimeMS(
+    uint64_t* aElapsedLoadTimeMS) {
+  NS_ENSURE_ARG_POINTER(aElapsedLoadTimeMS);
+  if (mMaybeElapsedLoadTimeMS) {
+    *aElapsedLoadTimeMS = *mMaybeElapsedLoadTimeMS;
+    return NS_OK;
+  }
+  *aElapsedLoadTimeMS = 0;
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
 // nsIChannel methods
 
 NS_IMETHODIMP RemoteWebProgressRequest::GetOriginalURI(nsIURI** aOriginalURI) {
   NS_ENSURE_ARG_POINTER(aOriginalURI);
   NS_ADDREF(*aOriginalURI = mOriginalURI);
   return NS_OK;
 }
 
--- a/dom/ipc/RemoteWebProgressRequest.h
+++ b/dom/ipc/RemoteWebProgressRequest.h
@@ -21,24 +21,35 @@ class RemoteWebProgressRequest final : p
   NS_DECL_NSICHANNEL
   NS_DECL_NSICLASSIFIEDCHANNEL
   NS_DECL_NSIREQUEST
 
   RemoteWebProgressRequest()
       : mURI(nullptr), mOriginalURI(nullptr), mMatchedList(VoidCString()) {}
 
   RemoteWebProgressRequest(nsIURI* aURI, nsIURI* aOriginalURI,
-                           const nsACString& aMatchedList)
-      : mURI(aURI), mOriginalURI(aOriginalURI), mMatchedList(aMatchedList) {}
+                           const nsACString& aMatchedList,
+                           const Maybe<uint64_t>& aMaybeElapsedLoadTimeMS)
+      : mURI(aURI),
+        mOriginalURI(aOriginalURI),
+        mMatchedList(aMatchedList),
+        mMaybeElapsedLoadTimeMS(aMaybeElapsedLoadTimeMS) {}
 
  protected:
   ~RemoteWebProgressRequest() = default;
 
  private:
   nsCOMPtr<nsIURI> mURI;
   nsCOMPtr<nsIURI> mOriginalURI;
   nsCString mMatchedList;
+
+  // This field is only Some(...) when the RemoteWebProgressRequest
+  // is created at a time that the document whose progress is being
+  // described by this request is top level and its status changes
+  // from loading to completely loaded.
+  // See BrowserChild::OnStateChange.
+  Maybe<uint64_t> mMaybeElapsedLoadTimeMS;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_RemoteWebProgressRequest_h
--- a/dom/ipc/nsIRemoteWebProgressRequest.idl
+++ b/dom/ipc/nsIRemoteWebProgressRequest.idl
@@ -5,9 +5,16 @@
 #include "nsISupports.idl"
 
 interface nsIURI;
 
 [scriptable, uuid(e14ff4c2-7e26-4748-8755-dfd074b9c746)]
 interface nsIRemoteWebProgressRequest : nsISupports
 {
   void init(in nsIURI aURI, in nsIURI aOriginalURI);
+
+  // This field is available to users in |OnStateChange| methods only
+  // when the document whose progress is being described by this progress
+  // request is top level and its status has just changed from loading to
+  // completely loaded; for invocations of |OnStateChange| before or after
+  // that transition, this field will throw |NS_ERROR_UNAVAILABLE|.
+  readonly attribute uint64_t elapsedLoadTimeMS;
 };
--- a/dom/ipc/tests/browser.ini
+++ b/dom/ipc/tests/browser.ini
@@ -4,8 +4,10 @@ support-files =
   file_domainPolicy_base.html
   file_cancel_content_js.html
 
 [browser_domainPolicy.js]
 [browser_memory_distribution_telemetry.js]
 skip-if = !e10 # This is an e10s only probe.
 [browser_cancel_content_js.js]
 skip-if = !e10s # This is an e10s only probe.
+[browser_ElapsedTime.js]
+support-files = elapsed_time.sjs
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/browser_ElapsedTime.js
@@ -0,0 +1,63 @@
+"use strict";
+
+/*
+ * Synchronize DELAY_MS with DELAY_MS from elapsed_time.sjs
+ */
+const DELAY_MS = 200;
+const SLOW_PAGE =
+  getRootDirectory(gTestPath).replace(
+    "chrome://mochitests/content",
+    "https://example.com"
+  ) + "elapsed_time.sjs";
+
+add_task(async function testLongElapsedTime() {
+  await BrowserTestUtils.withNewTab(
+    { gBrowser, url: "about:blank" },
+    async function(tabBrowser) {
+      const flags = Ci.nsIWebProgress.NOTIFY_STATE_NETWORK;
+      let listener;
+
+      let stateChangeWaiter = new Promise(resolve => {
+        listener = {
+          onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+            if (!aWebProgress.isTopLevel) {
+              return;
+            }
+            const isTopLevel = aWebProgress.isTopLevel;
+            const isStop = aStateFlags & Ci.nsIWebProgressListener.STATE_STOP;
+            const isNetwork =
+              aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+            const isWindow =
+              aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
+            const isLoadingDocument = aWebProgress.isLoadingDocument;
+
+            if (
+              isTopLevel &&
+              isStop &&
+              isWindow &&
+              isNetwork &&
+              !isLoadingDocument
+            ) {
+              aRequest.QueryInterface(Ci.nsIRemoteWebProgressRequest);
+              if (aRequest.elapsedLoadTimeMS >= DELAY_MS) {
+                resolve(true);
+              }
+            }
+          },
+        };
+      });
+      tabBrowser.addProgressListener(listener, flags);
+
+      BrowserTestUtils.loadURI(tabBrowser, SLOW_PAGE);
+      await BrowserTestUtils.browserLoaded(tabBrowser);
+      let pass = await stateChangeWaiter;
+
+      tabBrowser.removeProgressListener(listener);
+
+      ok(
+        pass,
+        "Bug 1559657: Check that the elapsedLoadTimeMS in RemoteWebProgress meets expectations."
+      );
+    }
+  );
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/elapsed_time.sjs
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const DELAY_MS = 200;
+
+const HTML = `<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+  <head>
+    <meta charset="utf8">
+  </head>
+  <body>
+  </body>
+</html>`;
+
+/*
+ * Keep timer as a global so that it is not GC'd
+ * between the time that handleRequest() completes
+ * and it expires.
+ */
+var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+function handleRequest(req, resp) {
+  resp.processAsync();
+  resp.setHeader("Cache-Control", "no-cache", false);
+  resp.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+  resp.write(HTML);
+  timer.init(() => {
+    resp.write("");
+    resp.finish();
+  }, DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
+}