Bug 703855. Don't update performance timing or navigation timing state during document.open. r=jst
authorBoris Zbarsky <bzbarsky@mit.edu>
Wed, 08 Aug 2012 23:47:04 -0400
changeset 105043 cc595773e189ce150bfb3e836227b3dca934be5b
parent 105042 d2465f38b85b1ed349d2cce40f01895d72081015
child 105044 beb7a4c0d06f6dbec6cff778cd64df780b3f1f4b
push idunknown
push userunknown
push dateunknown
reviewersjst
bugs703855
milestone17.0a1
Bug 703855. Don't update performance timing or navigation timing state during document.open. r=jst
docshell/base/nsDocShell.cpp
docshell/test/Makefile.in
docshell/test/file_bug703855.html
docshell/test/test_bug703855.html
dom/base/nsDOMNavigationTiming.cpp
dom/base/nsDOMNavigationTiming.h
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/base/nsPerformance.h
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -6050,27 +6050,29 @@ nsDocShell::OnStateChange(nsIWebProgress
 
     if ((~aStateFlags & (STATE_START | STATE_IS_NETWORK)) == 0) {
         // Save timing statistics.
         nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
         nsCOMPtr<nsIURI> uri;
         channel->GetURI(getter_AddRefs(uri));
         nsCAutoString aURI;
         uri->GetAsciiSpec(aURI);
-        if (this == aProgress){
+
+        nsCOMPtr<nsIWyciwygChannel>  wcwgChannel(do_QueryInterface(aRequest));
+        nsCOMPtr<nsIWebProgress> webProgress =
+            do_QueryInterface(GetAsSupports(this));
+
+        // We don't update navigation timing for wyciwyg channels
+        if (this == aProgress && !wcwgChannel){
             rv = MaybeInitTiming();
             if (mTiming) {
                 mTiming->NotifyFetchStart(uri, ConvertLoadTypeToNavigationType(mLoadType));
             } 
         }
 
-        nsCOMPtr<nsIWyciwygChannel>  wcwgChannel(do_QueryInterface(aRequest));
-        nsCOMPtr<nsIWebProgress> webProgress =
-            do_QueryInterface(GetAsSupports(this));
-
         // Was the wyciwyg document loaded on this docshell?
         if (wcwgChannel && !mLSHE && (mItemType == typeContent) && aProgress == webProgress.get()) {
             bool equalUri = true;
             // Store the wyciwyg url in session history, only if it is
             // being loaded fresh for the first time. We don't want 
             // multiple entries for successive loads
             if (mCurrentURI &&
                 NS_SUCCEEDED(uri->Equals(mCurrentURI, &equalUri)) &&
--- a/docshell/test/Makefile.in
+++ b/docshell/test/Makefile.in
@@ -90,16 +90,18 @@ MOCHITEST_FILES = \
 		file_bug669671.sjs \
 		test_bug675587.html \
 		test_bfcache_plus_hash.html \
 		test_bug680257.html \
 		file_bug680257.html \
 		test_bug691547.html \
 		bug691547_frame.html \
 		test_bug694612.html \
+		test_bug703855.html \
+		file_bug703855.html \
 		test_bug713825.html \
 		test_bug728939.html \
 		file_bug728939.html \
 		$(NULL)
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
 MOCHITEST_FILES += \
 		test_bug511449.html \
new file mode 100644
--- /dev/null
+++ b/docshell/test/file_bug703855.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<!-- Just need an empty file here, as long as it's served over HTTP -->
new file mode 100644
--- /dev/null
+++ b/docshell/test/test_bug703855.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=703855
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 703855</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=703855">Mozilla Bug 703855</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  <iframe id="f" src="file_bug703855.html"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 703855 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var timingAttributes = [
+  'connectEnd',
+  'connectStart',
+  'domComplete',
+  'domContentLoadedEventEnd',
+  'domContentLoadedEventStart',
+  'domInteractive',
+  'domLoading',
+  'domainLookupEnd',
+  'domainLookupStart',
+  'fetchStart',
+  'loadEventEnd',
+  'loadEventStart',
+  'navigationStart',
+  'redirectEnd',
+  'redirectStart',
+  'requestStart',
+  'responseEnd',
+  'responseStart',
+  'unloadEventEnd',
+  'unloadEventStart'
+];
+var originalTiming = {};
+
+function runTest() {
+  var timing = $("f").contentWindow.performance.timing;
+  for (i in timingAttributes) {
+    originalTiming[timingAttributes[i]] = timing[timingAttributes[i]];
+  }
+
+  var doc = $("f").contentDocument;
+  doc.open();
+  doc.write("<!DOCTYPE html>");
+  doc.close();
+
+  SimpleTest.executeSoon(function() {
+    var newTiming = $("f").contentWindow.performance.timing;
+    for (var i in timingAttributes) {
+      is(timing[timingAttributes[i]], originalTiming[timingAttributes[i]],
+         "document.open should not affect value of " + timingAttributes[i]);
+    }
+    SimpleTest.finish();
+  });
+}
+
+addLoadEvent(function() {
+  SimpleTest.executeSoon(runTest);
+});
+
+
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/base/nsDOMNavigationTiming.cpp
+++ b/dom/base/nsDOMNavigationTiming.cpp
@@ -36,16 +36,24 @@ nsDOMNavigationTiming::Clear()
   mLoadEventStart = 0;
   mLoadEventEnd = 0;
   mDOMLoading = 0;
   mDOMInteractive = 0;
   mDOMContentLoadedEventStart = 0;
   mDOMContentLoadedEventEnd = 0;
   mDOMComplete = 0;
   mRedirectCheck = NOT_CHECKED;
+
+  mLoadEventStartSet = false;
+  mLoadEventEndSet = false;
+  mDOMLoadingSet = false;
+  mDOMInteractiveSet = false;
+  mDOMContentLoadedEventStartSet = false;
+  mDOMContentLoadedEventEndSet = false;
+  mDOMCompleteSet = false;
 }
 
 DOMTimeMilliSec
 nsDOMNavigationTiming::TimeStampToDOM(mozilla::TimeStamp aStamp) const
 {
   if (aStamp.IsNull()) {
     return 0;
   }
@@ -123,23 +131,29 @@ void
 nsDOMNavigationTiming::NotifyUnloadEventEnd()
 {
   mUnloadEnd = DurationFromStart();
 }
 
 void
 nsDOMNavigationTiming::NotifyLoadEventStart()
 {
-  mLoadEventStart = DurationFromStart();
+  if (!mLoadEventStartSet) {
+    mLoadEventStart = DurationFromStart();
+    mLoadEventStartSet = true;
+  }
 }
 
 void
 nsDOMNavigationTiming::NotifyLoadEventEnd()
 {
-  mLoadEventEnd = DurationFromStart();
+  if (!mLoadEventEndSet) {
+    mLoadEventEnd = DurationFromStart();
+    mLoadEventEndSet = true;
+  }
 }
 
 bool
 nsDOMNavigationTiming::ReportRedirects()
 {
   if (mRedirectCheck == NOT_CHECKED) {
     mRedirectCount = mRedirects.Count();
     if (mRedirects.Count() == 0) {
@@ -161,53 +175,71 @@ nsDOMNavigationTiming::ReportRedirects()
     }
   }
   return mRedirectCheck == CHECK_PASSED;
 }
 
 void
 nsDOMNavigationTiming::SetDOMLoadingTimeStamp(nsIURI* aURI, mozilla::TimeStamp aValue)
 {
-  mLoadedURI = aURI;
-  mDOMLoading = TimeStampToDOM(aValue);
+  if (!mDOMLoadingSet) {
+    mLoadedURI = aURI;
+    mDOMLoading = TimeStampToDOM(aValue);
+    mDOMLoadingSet = true;
+  }
 }
 
 void
 nsDOMNavigationTiming::NotifyDOMLoading(nsIURI* aURI)
 {
-  mLoadedURI = aURI;
-  mDOMLoading = DurationFromStart();
+  if (!mDOMLoadingSet) {
+    mLoadedURI = aURI;
+    mDOMLoading = DurationFromStart();
+    mDOMLoadingSet = true;
+  }
 }
 
 void
 nsDOMNavigationTiming::NotifyDOMInteractive(nsIURI* aURI)
 {
-  mLoadedURI = aURI;
-  mDOMInteractive = DurationFromStart();
+  if (!mDOMInteractiveSet) {
+    mLoadedURI = aURI;
+    mDOMInteractive = DurationFromStart();
+    mDOMInteractiveSet = true;
+  }
 }
 
 void
 nsDOMNavigationTiming::NotifyDOMComplete(nsIURI* aURI)
 {
-  mLoadedURI = aURI;
-  mDOMComplete = DurationFromStart();
+  if (!mDOMCompleteSet) {
+    mLoadedURI = aURI;
+    mDOMComplete = DurationFromStart();
+    mDOMCompleteSet = true;
+  }
 }
 
 void
 nsDOMNavigationTiming::NotifyDOMContentLoadedStart(nsIURI* aURI)
 {
-  mLoadedURI = aURI;
-  mDOMContentLoadedEventStart = DurationFromStart();
+  if (!mDOMContentLoadedEventStartSet) {
+    mLoadedURI = aURI;
+    mDOMContentLoadedEventStart = DurationFromStart();
+    mDOMContentLoadedEventStartSet = true;
+  }
 }
 
 void
 nsDOMNavigationTiming::NotifyDOMContentLoadedEnd(nsIURI* aURI)
 {
-  mLoadedURI = aURI;
-  mDOMContentLoadedEventEnd = DurationFromStart();
+  if (!mDOMContentLoadedEventEndSet) {
+    mLoadedURI = aURI;
+    mDOMContentLoadedEventEnd = DurationFromStart();
+    mDOMContentLoadedEventEndSet = true;
+  }
 }
 
 PRUint16
 nsDOMNavigationTiming::GetRedirectCount()
 {
   if (ReportRedirects()) {
     return mRedirectCount;
   }
--- a/dom/base/nsDOMNavigationTiming.h
+++ b/dom/base/nsDOMNavigationTiming.h
@@ -131,11 +131,22 @@ private:
   DOMTimeMilliSec mLoadEventStart;
   DOMTimeMilliSec mLoadEventEnd;
 
   DOMTimeMilliSec mDOMLoading;
   DOMTimeMilliSec mDOMInteractive;
   DOMTimeMilliSec mDOMContentLoadedEventStart;
   DOMTimeMilliSec mDOMContentLoadedEventEnd;
   DOMTimeMilliSec mDOMComplete;
+
+  // Booleans to keep track of what things we've already been notified
+  // about.  We don't update those once we've been notified about them
+  // once.
+  bool mLoadEventStartSet : 1;
+  bool mLoadEventEndSet : 1;
+  bool mDOMLoadingSet : 1;
+  bool mDOMInteractiveSet : 1;
+  bool mDOMContentLoadedEventStartSet : 1;
+  bool mDOMContentLoadedEventEndSet : 1;
+  bool mDOMCompleteSet : 1;
 };
 
 #endif /* nsDOMNavigationTiming_h___ */
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -1951,16 +1951,27 @@ nsGlobalWindow::SetNewDocument(nsIDocume
         // Move the navigator from the old inner window to the new one since
         // this is a document.write. This is safe from a same-origin point of
         // view because document.write can only be used by the same origin.
         newInnerWindow->mNavigator = currentInner->mNavigator;
         currentInner->mNavigator = nullptr;
         if (newInnerWindow->mNavigator) {
           newInnerWindow->mNavigator->SetWindow(newInnerWindow);
         }
+
+        // Make a copy of the old window's performance object on document.open.
+        // Note that we have to force eager creation of it here, because we need
+        // to grab the current document channel and whatnot before that changes.
+        currentInner->CreatePerformanceObjectIfNeeded();
+        if (currentInner->mPerformance) {
+          newInnerWindow->mPerformance =
+            new nsPerformance(newInnerWindow,
+                              currentInner->mPerformance->GetDOMTiming(),
+                              currentInner->mPerformance->GetChannel());
+        }
       }
 
       // Don't free objects on our current inner window if it's going to be
       // held in the bfcache.
       if (!currentInner->IsFrozen()) {
         currentInner->FreeInnerObjects();
       }
     }
@@ -2926,37 +2937,41 @@ nsGlobalWindow::GetHistory(nsIDOMHistory
 NS_IMETHODIMP
 nsGlobalWindow::GetPerformance(nsISupports** aPerformance)
 {
   FORWARD_TO_INNER(GetPerformance, (aPerformance), NS_ERROR_NOT_INITIALIZED);
 
   *aPerformance = nullptr;
 
   if (nsGlobalWindow::HasPerformanceSupport()) {
-    if (!mPerformance) {
-      if (!mDoc) {
-        return NS_OK;
-      }
-      nsRefPtr<nsDOMNavigationTiming> timing = mDoc->GetNavigationTiming();
-      nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(mDoc->GetChannel()));
-      bool timingEnabled = false;
-      if (!timedChannel ||
-          !NS_SUCCEEDED(timedChannel->GetTimingEnabled(&timingEnabled)) ||
-          !timingEnabled) {
-        timedChannel = nullptr;
-      }
-      if (timing) {
-        mPerformance = new nsPerformance(this, timing, timedChannel);
-      }
-    }
+    CreatePerformanceObjectIfNeeded();
     NS_IF_ADDREF(*aPerformance = mPerformance);
   }
   return NS_OK;
 }
 
+void
+nsGlobalWindow::CreatePerformanceObjectIfNeeded()
+{
+  if (mPerformance || !mDoc) {
+    return;
+  }
+  nsRefPtr<nsDOMNavigationTiming> timing = mDoc->GetNavigationTiming();
+  nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(mDoc->GetChannel()));
+  bool timingEnabled = false;
+  if (!timedChannel ||
+      !NS_SUCCEEDED(timedChannel->GetTimingEnabled(&timingEnabled)) ||
+      !timingEnabled) {
+    timedChannel = nullptr;
+  }
+  if (timing) {
+    mPerformance = new nsPerformance(this, timing, timedChannel);
+  }
+}
+
 /**
  * GetScriptableParent is called when script reads window.parent.
  *
  * In contrast to GetRealParent, GetScriptableParent respects <iframe
  * mozbrowser> boundaries, so if |this| is contained by an <iframe
  * mozbrowser>, we will return |this| as its own parent.
  */
 NS_IMETHODIMP
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -900,16 +900,19 @@ protected:
 
   void SetIsApp(bool aValue);
   nsresult SetApp(const nsAString& aManifestURL);
   nsresult GetApp(mozIDOMApplication** aApplication);
 
   // Implements Get{Real,Scriptable}Top.
   nsresult GetTopImpl(nsIDOMWindow **aWindow, bool aScriptable);
 
+  // Helper for creating performance objects.
+  void CreatePerformanceObjectIfNeeded();
+
   // When adding new member variables, be careful not to create cycles
   // through JavaScript.  If there is any chance that a member variable
   // could own objects that are implemented in JavaScript, then those
   // objects will keep the global object (this object) alive.  To prevent
   // these cycles, ownership of such members must be released in
   // |CleanUp| and |DetachFromDocShell|.
 
   // This member is also used on both inner and outer windows, but
@@ -985,16 +988,17 @@ protected:
   nsCOMPtr<nsIScriptContext>    mContext;
   nsWeakPtr                     mOpener;
   nsCOMPtr<nsIControllers>      mControllers;
   nsCOMPtr<nsIArray>            mArguments;
   nsCOMPtr<nsIArray>            mArgumentsLast;
   nsCOMPtr<nsIPrincipal>        mArgumentsOrigin;
   nsRefPtr<Navigator>           mNavigator;
   nsRefPtr<nsScreen>            mScreen;
+  // mPerformance is only used on inner windows.
   nsRefPtr<nsPerformance>       mPerformance;
   nsRefPtr<nsDOMWindowList>     mFrames;
   nsRefPtr<nsBarProp>           mMenubar;
   nsRefPtr<nsBarProp>           mToolbar;
   nsRefPtr<nsBarProp>           mLocationbar;
   nsRefPtr<nsBarProp>           mPersonalbar;
   nsRefPtr<nsBarProp>           mStatusbar;
   nsRefPtr<nsBarProp>           mScrollbars;
--- a/dom/base/nsPerformance.h
+++ b/dom/base/nsPerformance.h
@@ -135,16 +135,21 @@ public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsPerformance)
 
   nsDOMNavigationTiming* GetDOMTiming() const
   {
     return mDOMTiming;
   }
 
+  nsITimedChannel* GetChannel() const
+  {
+    return mChannel;
+  }
+
   nsIDOMWindow* GetParentObject() const
   {
     return mWindow.get();
   }
 
   JSObject* WrapObject(JSContext *cx, JSObject *scope, bool *triedToWrap);
 
   // Performance WebIDL methods