From 88b4396a695874f214fde7d05dc1399ad11fe59f Mon Sep 17 00:00:00 2001 From: Will Hawkins Date: Thu, 11 Jul 2019 04:00:41 +0000 Subject: [PATCH] Bug 1559657: Pass load time in RemoteWebProgress. r=Ehsan,barret Differential Revision: https://phabricator.services.mozilla.com/D35147 --HG-- extra : moz-landing-system : lando --- dom/ipc/BrowserChild.cpp | 23 +++++++++ dom/ipc/BrowserParent.cpp | 2 +- dom/ipc/PBrowser.ipdl | 6 +++ dom/ipc/RemoteWebProgressRequest.cpp | 11 +++++ dom/ipc/RemoteWebProgressRequest.h | 15 +++++- dom/ipc/nsIRemoteWebProgressRequest.idl | 7 +++ dom/ipc/tests/browser.ini | 2 + dom/ipc/tests/browser_ElapsedTime.js | 63 +++++++++++++++++++++++++ dom/ipc/tests/elapsed_time.sjs | 31 ++++++++++++ 9 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 dom/ipc/tests/browser_ElapsedTime.js create mode 100644 dom/ipc/tests/elapsed_time.sjs diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp index 6c0729de9ef4..13b963955e8f 100644 --- a/dom/ipc/BrowserChild.cpp +++ b/dom/ipc/BrowserChild.cpp @@ -3494,6 +3494,29 @@ NS_IMETHODIMP BrowserChild::OnStateChange(nsIWebProgress* aWebProgress, 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 navigationTiming = + document->GetNavigationTiming(); + if (navigationTiming) { + TimeDuration elapsedLoadTimeMS = + TimeStamp::Now() - navigationTiming->GetNavigationStartTimeStamp(); + requestData.elapsedLoadTimeMS() = + Some(elapsedLoadTimeMS.ToMilliseconds()); + } + } + if (webProgressData->isTopLevel()) { stateChangeData.emplace(); diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp index 4100605caea7..917d495f0f45 100644 --- a/dom/ipc/BrowserParent.cpp +++ b/dom/ipc/BrowserParent.cpp @@ -2623,7 +2623,7 @@ void BrowserParent::ReconstructWebProgressAndRequest( if (aRequestData.requestURI()) { nsCOMPtr request = MakeAndAddRef( aRequestData.requestURI(), aRequestData.originalRequestURI(), - aRequestData.matchedList()); + aRequestData.matchedList(), aRequestData.elapsedLoadTimeMS()); request.forget(aOutRequest); } else { *aOutRequest = nullptr; diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index 06a03337eafb..89900f67f0b6 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -118,6 +118,12 @@ 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 diff --git a/dom/ipc/RemoteWebProgressRequest.cpp b/dom/ipc/RemoteWebProgressRequest.cpp index 705672b52952..49dc0498dbb2 100644 --- a/dom/ipc/RemoteWebProgressRequest.cpp +++ b/dom/ipc/RemoteWebProgressRequest.cpp @@ -18,6 +18,17 @@ NS_IMETHODIMP RemoteWebProgressRequest::Init(nsIURI* aURI, 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) { diff --git a/dom/ipc/RemoteWebProgressRequest.h b/dom/ipc/RemoteWebProgressRequest.h index 7d92e65a75a0..d0e192d60025 100644 --- a/dom/ipc/RemoteWebProgressRequest.h +++ b/dom/ipc/RemoteWebProgressRequest.h @@ -26,8 +26,12 @@ class RemoteWebProgressRequest final : public nsIRemoteWebProgressRequest, : 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& aMaybeElapsedLoadTimeMS) + : mURI(aURI), + mOriginalURI(aOriginalURI), + mMatchedList(aMatchedList), + mMaybeElapsedLoadTimeMS(aMaybeElapsedLoadTimeMS) {} protected: ~RemoteWebProgressRequest() = default; @@ -36,6 +40,13 @@ class RemoteWebProgressRequest final : public nsIRemoteWebProgressRequest, nsCOMPtr mURI; nsCOMPtr 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 mMaybeElapsedLoadTimeMS; }; } // namespace dom diff --git a/dom/ipc/nsIRemoteWebProgressRequest.idl b/dom/ipc/nsIRemoteWebProgressRequest.idl index 8a3fc2c343d4..a5e5c98f3db8 100644 --- a/dom/ipc/nsIRemoteWebProgressRequest.idl +++ b/dom/ipc/nsIRemoteWebProgressRequest.idl @@ -10,4 +10,11 @@ interface nsIURI; 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; }; diff --git a/dom/ipc/tests/browser.ini b/dom/ipc/tests/browser.ini index 4be5dc4a2be2..ed87d5114d83 100644 --- a/dom/ipc/tests/browser.ini +++ b/dom/ipc/tests/browser.ini @@ -9,3 +9,5 @@ support-files = 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 diff --git a/dom/ipc/tests/browser_ElapsedTime.js b/dom/ipc/tests/browser_ElapsedTime.js new file mode 100644 index 000000000000..ab01dc9dd100 --- /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." + ); + } + ); +}); diff --git a/dom/ipc/tests/elapsed_time.sjs b/dom/ipc/tests/elapsed_time.sjs new file mode 100644 index 000000000000..e5262af7b69f --- /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 = ` + + + + + + +`; + +/* + * 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); +}