Bug 1531917 - Add Telemetry for how pages use bfcache r=jesup,bdekoz,smaug
authorSean Feng <sefeng@mozilla.com>
Tue, 14 May 2019 16:21:09 +0000
changeset 473798 e80a258b4aed3eb6599967b22a12e2b6314482d2
parent 473797 dc20a50efcf1d6343019ab8cabe882698f2823fd
child 473799 fd66f241a8bda8a748dfe741802a4fecf7040c10
push id36015
push usercbrindusan@mozilla.com
push dateTue, 14 May 2019 21:40:04 +0000
treeherdermozilla-central@f17162f343b6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjesup, bdekoz, smaug
bugs1531917
milestone68.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 1531917 - Add Telemetry for how pages use bfcache r=jesup,bdekoz,smaug Differential Revision: https://phabricator.services.mozilla.com/D30211
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
dom/base/Document.cpp
dom/base/Document.h
dom/html/PluginDocument.cpp
toolkit/components/telemetry/Histograms.json
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -7207,18 +7207,62 @@ bool nsDocShell::CanSavePresentation(uin
   nsCOMPtr<nsIDocShellTreeItem> root;
   GetSameTypeParent(getter_AddRefs(root));
   if (root && root != this) {
     return false;  // this is a subframe load
   }
 
   // If the document does not want its presentation cached, then don't.
   RefPtr<Document> doc = mScriptGlobal->GetExtantDoc();
-  return doc && doc->CanSavePresentation(aNewRequest);
-}
+
+  uint16_t bfCacheCombo = 0;
+  bool canSavePresentation =
+      doc->CanSavePresentation(aNewRequest, bfCacheCombo);
+  ReportBFCacheComboTelemetry(bfCacheCombo);
+
+  return doc && canSavePresentation;
+}
+
+void nsDocShell::ReportBFCacheComboTelemetry(uint16_t aCombo) {
+  switch (aCombo) {
+    case BFCACHE_SUCCESS:
+      Telemetry::AccumulateCategorical(
+          Telemetry::LABELS_BFCACHE_COMBO::BFCache_Success);
+      break;
+    case UNLOAD:
+      Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Unload);
+      break;
+    case UNLOAD_REQUEST:
+      Telemetry::AccumulateCategorical(
+          Telemetry::LABELS_BFCACHE_COMBO::Unload_Req);
+      break;
+    case REQUEST:
+      Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Req);
+      break;
+    case UNLOAD_REQUEST_PEER:
+      Telemetry::AccumulateCategorical(
+          Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_Peer);
+      break;
+    case UNLOAD_REQUEST_PEER_MSE:
+      Telemetry::AccumulateCategorical(
+          Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_Peer_MSE);
+      break;
+    case UNLOAD_REQUEST_MSE:
+      Telemetry::AccumulateCategorical(
+          Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_MSE);
+      break;
+    case SUSPENDED_UNLOAD_REQUEST_PEER:
+      Telemetry::AccumulateCategorical(
+          Telemetry::LABELS_BFCACHE_COMBO::SPD_Unload_Req_Peer);
+      break;
+    default:
+      Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Other);
+      break;
+  }
+};
 
 void nsDocShell::ReattachEditorToWindow(nsISHEntry* aSHEntry) {
   MOZ_ASSERT(!mIsBeingDestroyed);
 
   NS_ASSERTION(!mEditorData,
                "Why reattach an editor when we already have one?");
   NS_ASSERTION(aSHEntry && aSHEntry->HasDetachedEditor(),
                "Reattaching when there's not a detached editor.");
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -746,16 +746,47 @@ class nsDocShell final : public nsDocLoa
   // |aLoadType| should be the load type that will replace the current
   // presentation. |aNewRequest| should be the request for the document to
   // be loaded in place of the current document, or null if such a request
   // has not been created yet. |aNewDocument| should be the document that will
   // replace the current document.
   bool CanSavePresentation(uint32_t aLoadType, nsIRequest* aNewRequest,
                            mozilla::dom::Document* aNewDocument);
 
+  // There are 11 possible reasons to make a request fails to use BFCache
+  // (see BFCacheStatus in dom/base/Document.h), and we'd like to record
+  // the common combinations for reasons which make requests fail to use
+  // BFCache. These combinations are generated based on some local browsings,
+  // we need to adjust them when necessary.
+  enum BFCacheStatusCombo : uint16_t {
+    BFCACHE_SUCCESS,
+    UNLOAD = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER,
+    UNLOAD_REQUEST = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER |
+                              mozilla::dom::BFCacheStatus::REQUEST,
+    REQUEST = mozilla::dom::BFCacheStatus::REQUEST,
+    UNLOAD_REQUEST_PEER = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER |
+                          mozilla::dom::BFCacheStatus::REQUEST |
+                          mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION,
+    UNLOAD_REQUEST_PEER_MSE =
+      mozilla::dom::BFCacheStatus::UNLOAD_LISTENER |
+      mozilla::dom::BFCacheStatus::REQUEST |
+      mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION |
+      mozilla::dom::BFCacheStatus::CONTAINS_MSE_CONTENT,
+    UNLOAD_REQUEST_MSE = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER |
+                         mozilla::dom::BFCacheStatus::REQUEST |
+                         mozilla::dom::BFCacheStatus::CONTAINS_MSE_CONTENT,
+    SUSPENDED_UNLOAD_REQUEST_PEER =
+      mozilla::dom::BFCacheStatus::SUSPENDED |
+      mozilla::dom::BFCacheStatus::UNLOAD_LISTENER |
+      mozilla::dom::BFCacheStatus::REQUEST |
+      mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION,
+  };
+
+  void ReportBFCacheComboTelemetry(uint16_t aCombo);
+
   // Captures the state of the supporting elements of the presentation
   // (the "window" object, docshell tree, meta-refresh loads, and security
   // state) and stores them on |mOSHE|.
   nsresult CaptureState();
 
   // Begin the toplevel restore process for |aSHEntry|.
   // This simulates a channel open, and defers the real work until
   // RestoreFromHistory is called from a PLEvent.
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -7629,54 +7629,61 @@ void Document::CollectDescendantDocument
       if (aCallback(subdoc)) {
         aDescendants.AppendElement(entry->mSubDocument);
       }
       subdoc->CollectDescendantDocuments(aDescendants, aCallback);
     }
   }
 }
 
-bool Document::CanSavePresentation(nsIRequest* aNewRequest) {
+bool Document::CanSavePresentation(nsIRequest* aNewRequest,
+                                   uint16_t& aBFCacheCombo) {
+  bool ret = true;
+
   if (!IsBFCachingAllowed()) {
-    return false;
+    aBFCacheCombo |= BFCacheStatus::NOT_ALLOWED;
+    ret = false;
   }
 
   nsAutoCString uri;
   if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
     if (mDocumentURI) {
       mDocumentURI->GetSpec(uri);
     }
   }
 
   if (EventHandlingSuppressed()) {
     MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
             ("Save of %s blocked on event handling suppression", uri.get()));
-    return false;
+    aBFCacheCombo |= BFCacheStatus::EVENT_HANDLING_SUPPRESSED;
+    ret = false;
   }
 
   // Do not allow suspended windows to be placed in the
   // bfcache.  This method is also used to verify a document
   // coming out of the bfcache is ok to restore, though.  So
   // we only want to block suspend windows that aren't also
   // frozen.
   nsPIDOMWindowInner* win = GetInnerWindow();
   if (win && win->IsSuspended() && !win->IsFrozen()) {
     MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
             ("Save of %s blocked on suspended Window", uri.get()));
-    return false;
+    aBFCacheCombo |= BFCacheStatus::SUSPENDED;
+    ret = false;
   }
 
   // Check our event listener manager for unload/beforeunload listeners.
   nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
   if (piTarget) {
     EventListenerManager* manager = piTarget->GetExistingListenerManager();
     if (manager && manager->HasUnloadListeners()) {
       MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
               ("Save of %s blocked due to unload handlers", uri.get()));
-      return false;
+      aBFCacheCombo |= BFCacheStatus::UNLOAD_LISTENER;
+      ret = false;
     }
   }
 
   // Check if we have pending network requests
   nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
   if (loadGroup) {
     nsCOMPtr<nsISimpleEnumerator> requests;
     loadGroup->GetRequests(getter_AddRefs(requests));
@@ -7710,85 +7717,95 @@ bool Document::CanSavePresentation(nsIRe
 
         if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
           nsAutoCString requestName;
           request->GetName(requestName);
           MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
                   ("Save of %s blocked because document has request %s",
                    uri.get(), requestName.get()));
         }
-
-        return false;
+        aBFCacheCombo |= BFCacheStatus::REQUEST;
+        ret = false;
       }
     }
   }
 
   // Check if we have active GetUserMedia use
   if (MediaManager::Exists() && win &&
       MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
     MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
             ("Save of %s blocked due to GetUserMedia", uri.get()));
-    return false;
+    aBFCacheCombo |= BFCacheStatus::ACTIVE_GET_USER_MEDIA;
+    ret = false;
   }
 
 #ifdef MOZ_WEBRTC
   // Check if we have active PeerConnections
   if (win && win->HasActivePeerConnections()) {
     MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
             ("Save of %s blocked due to PeerConnection", uri.get()));
-    return false;
+    aBFCacheCombo |= BFCacheStatus::ACTIVE_PEER_CONNECTION;
+    ret = false;
   }
 #endif  // MOZ_WEBRTC
 
   // Don't save presentations for documents containing EME content, so that
   // CDMs reliably shutdown upon user navigation.
   if (ContainsEMEContent()) {
-    return false;
+    aBFCacheCombo |= BFCacheStatus::CONTAINS_EME_CONTENT;
+    ret = false;
   }
 
   // Don't save presentations for documents containing MSE content, to
   // reduce memory usage.
   if (ContainsMSEContent()) {
     MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
             ("Save of %s blocked due to MSE use", uri.get()));
-    return false;
+    aBFCacheCombo |= BFCacheStatus::CONTAINS_MSE_CONTENT;
+    ret = false;
   }
 
   if (mSubDocuments) {
     for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
       auto entry = static_cast<SubDocMapEntry*>(iter.Get());
       Document* subdoc = entry->mSubDocument;
 
+      uint16_t subDocBFCacheCombo = 0;
       // The aIgnoreRequest we were passed is only for us, so don't pass it on.
-      bool canCache = subdoc ? subdoc->CanSavePresentation(nullptr) : false;
+      bool canCache =
+          subdoc ? subdoc->CanSavePresentation(nullptr, subDocBFCacheCombo)
+                 : false;
       if (!canCache) {
         MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
                 ("Save of %s blocked due to subdocument blocked", uri.get()));
-        return false;
+        aBFCacheCombo |= subDocBFCacheCombo;
+        ret = false;
       }
     }
   }
 
   if (win) {
     auto* globalWindow = nsGlobalWindowInner::Cast(win);
 #ifdef MOZ_WEBSPEECH
     if (globalWindow->HasActiveSpeechSynthesis()) {
       MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
               ("Save of %s blocked due to Speech use", uri.get()));
-      return false;
+      aBFCacheCombo |= BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS;
+      ret = false;
     }
 #endif
     if (globalWindow->HasUsedVR()) {
       MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
               ("Save of %s blocked due to having used VR", uri.get()));
-      return false;
-    }
-  }
-
-  return true;
+      aBFCacheCombo |= BFCacheStatus::HAS_USED_VR;
+      ret = false;
+    }
+  }
+
+  return ret;
 }
 
 void Document::Destroy() {
   // The ContentViewer wants to release the document now.  So, tell our content
   // to drop any references to the document so that it can be destroyed.
   if (mIsGoingAway) return;
 
   // Make sure to report before IPC closed.
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -216,16 +216,30 @@ class Sequence;
 class nsDocumentOnStack;
 class nsUnblockOnloadEvent;
 
 template <typename, typename>
 class CallbackObjectHolder;
 
 enum class CallerType : uint32_t;
 
+enum BFCacheStatus {
+  NOT_ALLOWED = 1 << 0,                  // Status 0
+  EVENT_HANDLING_SUPPRESSED = 1 << 1,    // Status 1
+  SUSPENDED = 1 << 2,                    // Status 2
+  UNLOAD_LISTENER = 1 << 3,              // Status 3
+  REQUEST = 1 << 4,                      // Status 4
+  ACTIVE_GET_USER_MEDIA = 1 << 5,        // Status 5
+  ACTIVE_PEER_CONNECTION = 1 << 6,       // Status 6
+  CONTAINS_EME_CONTENT = 1 << 7,         // Status 7
+  CONTAINS_MSE_CONTENT = 1 << 8,         // Status 8
+  HAS_ACTIVE_SPEECH_SYNTHESIS = 1 << 9,  // Status 9
+  HAS_USED_VR = 1 << 10,                 // Status 10
+};
+
 }  // namespace dom
 }  // namespace mozilla
 
 namespace mozilla {
 namespace net {
 class ChannelEventQueue;
 }  // namespace net
 }  // namespace mozilla
@@ -2247,18 +2261,22 @@ class Document : public nsINode,
    *  - If there are any beforeunload or unload listeners, we must fire them
    *    for correctness, but this likely puts the document into a state where
    *    it would not function correctly if restored.
    *
    * |aNewRequest| should be the request for a new document which will
    * replace this document in the docshell.  The new document's request
    * will be ignored when checking for active requests.  If there is no
    * request associated with the new document, this parameter may be null.
-   */
-  virtual bool CanSavePresentation(nsIRequest* aNewRequest);
+   *
+   * |aBFCacheCombo| is used as a bitmask to indicate what the status
+   * combination is when we try to BFCache aNewRequest
+   */
+  virtual bool CanSavePresentation(nsIRequest* aNewRequest,
+                                   uint16_t& aBFCacheCombo);
 
   virtual nsresult Init();
 
   /**
    * Notify the document that its associated ContentViewer is being destroyed.
    * This releases circular references so that the document can go away.
    * Destroy() is only called on documents that have a content viewer.
    */
--- a/dom/html/PluginDocument.cpp
+++ b/dom/html/PluginDocument.cpp
@@ -37,17 +37,18 @@ class PluginDocument final : public Medi
   nsresult StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
                              nsILoadGroup* aLoadGroup, nsISupports* aContainer,
                              nsIStreamListener** aDocListener,
                              bool aReset = true,
                              nsIContentSink* aSink = nullptr) override;
 
   void SetScriptGlobalObject(
       nsIScriptGlobalObject* aScriptGlobalObject) override;
-  bool CanSavePresentation(nsIRequest* aNewRequest) override;
+  bool CanSavePresentation(nsIRequest* aNewRequest,
+                           uint16_t& aBFCacheStatus) override;
 
   const nsCString& GetType() const { return mMimeType; }
   Element* GetPluginContent() { return mPluginContent; }
 
   virtual void Destroy() override {
     if (mStreamListener) {
       mStreamListener->DropDocumentRef();
     }
@@ -131,17 +132,18 @@ void PluginDocument::SetScriptGlobalObje
       NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create synthetic document");
       InitialSetupDone();
     }
   } else {
     mStreamListener = nullptr;
   }
 }
 
-bool PluginDocument::CanSavePresentation(nsIRequest* aNewRequest) {
+bool PluginDocument::CanSavePresentation(nsIRequest* aNewRequest,
+                                         uint16_t& aBFCacheStatus) {
   // Full-page plugins cannot be cached, currently, because we don't have
   // the stream listener data to feed to the plugin instance.
   return false;
 }
 
 nsresult PluginDocument::StartDocumentLoad(const char* aCommand,
                                            nsIChannel* aChannel,
                                            nsILoadGroup* aLoadGroup,
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -14467,16 +14467,36 @@
     "alert_emails": ["emilio@mozilla.com"],
     "bug_numbers": [1505117],
     "expires_in_version": "70",
     "kind": "categorical",
     "releaseChannelCollection": "opt-out",
     "labels": ["FullStandards", "AlmostStandards", "NavQuirks"],
     "description": "HTML document compat mode (quirks mode)"
   },
+  "BFCACHE_COMBO": {
+    "record_in_processes": ["main", "content"],
+    "alert_emails": ["sefeng@mozilla.com"],
+    "bug_numbers": [1531917],
+    "expires_in_version": "never",
+    "kind": "categorical",
+    "releaseChannelCollection": "opt-out",
+    "labels": [
+      "BFCache_Success",
+      "Unload",
+      "Unload_Req",
+      "Req",
+      "Unload_Req_Peer",
+      "Unload_Req_Peer_MSE",
+      "Unload_Req_MSE",
+      "SPD_Unload_Req_Peer",
+      "Other"
+    ],
+    "description": "The common combinations of BFCacheStatus when we determine whether the page can be BFCached or not; If it uses BFCache, we record BFCache_Success; If it's not and it falls under common failure reasons combinations, we record the corresponding combination; Otherwise, we record Other to indicate this is not a common failure"
+  },
   "HIDDEN_VIEWPORT_OVERFLOW_TYPE": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["mozilla-telemetry@upsuper.org", "botond@mozilla.com"],
     "bug_numbers": [1423013, 1423017, 1513089],
     "expires_in_version": "70",
     "kind": "categorical",
     "releaseChannelCollection": "opt-out",
     "labels": ["NoOverflow", "Desktop", "ButNotMinScaleSize", "MinScaleSize"],