Bug 1603542 - Log a warning in the web console when a preloaded resource is not used, r=Harald
☠☠ backed out by d1d679603288 ☠ ☠
authorHonza Bambas <honzab.moz@firemni.cz>
Wed, 17 Jun 2020 14:06:50 +0000
changeset 536094 c3dcacdd97c4ae8873392e9e0947a1f57bf073c2
parent 536093 6de6943d856fe6e8adb1ce0352b2612e2c117a38
child 536095 cc70c96b63e099507005de8cc9ab23d79b4beb1d
push id37516
push usercbrindusan@mozilla.com
push dateWed, 17 Jun 2020 21:52:06 +0000
treeherdermozilla-central@fa0afb432810 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersHarald
bugs1603542
milestone79.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 1603542 - Log a warning in the web console when a preloaded resource is not used, r=Harald Differential Revision: https://phabricator.services.mozilla.com/D76527
dom/locales/en-US/chrome/dom/dom.properties
uriloader/preload/PreloaderBase.cpp
uriloader/preload/PreloaderBase.h
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -384,8 +384,10 @@ MathML_DeprecatedScriptShiftAttributes=MathML attributes “subscriptshift” and “superscriptshift” are deprecated and may be removed at a future date.
 MathML_DeprecatedStyleAttributeWarning=MathML attributes “background”, “color”, “fontfamily”, “fontsize”, “fontstyle” and “fontweight” are deprecated and will be removed at a future date.
 # LOCALIZATION NOTE: Do not translate MathML and XLink.
 MathML_DeprecatedXLinkAttributeWarning=XLink attributes “href”, “type”, “show” and “actuate” are deprecated on MathML elements and will be removed at a future date.
 WebShareAPI_Failed=The share operation has failed.
 WebShareAPI_Aborted=The share operation was aborted.
 # LOCALIZATION NOTE (UnknownProtocolNavigationPrevented): %1$S is the destination URL.
 UnknownProtocolNavigationPrevented=Prevented navigation to “%1$S” due to an unknown protocol.
 PostMessageSharedMemoryObjectToCrossOriginWarning=Cannot post message containing a shared memory object to a cross-origin window.
+# LOCALIZATION NOTE: %S is the URL of the resource in question
+UnusedLinkPreloadPending=The resource at “%S” preloaded with link preload was not used within a few seconds. Make sure all attributes of the preload tag are set correctly.
--- a/uriloader/preload/PreloaderBase.cpp
+++ b/uriloader/preload/PreloaderBase.cpp
@@ -1,15 +1,16 @@
 /* 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 "PreloaderBase.h"
 
 #include "mozilla/dom/Document.h"
+#include "nsContentUtils.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "nsIChannel.h"
 #include "nsILoadGroup.h"
 #include "nsIInterfaceRequestorUtils.h"
 
 // Change this if we want to cancel and remove the associated preload on removal
 // of all <link rel=preload> tags from the tree.
 constexpr static bool kCancelAndRemovePreloadOnZeroReferences = false;
@@ -106,17 +107,19 @@ void PreloaderBase::NotifyOpen(PreloadHa
   mIsUsed = !aIsPreload;
 }
 
 void PreloaderBase::NotifyOpen(PreloadHashKey* aKey, nsIChannel* aChannel,
                                dom::Document* aDocument, bool aIsPreload) {
   NotifyOpen(aKey, aDocument, aIsPreload);
   mChannel = aChannel;
 
-  // * Start the usage timer if aIsPreload.
+  auto callback = MakeRefPtr<UsageTimer>(this, aDocument);
+  NS_NewTimerWithCallback(getter_AddRefs(mUsageTimer), callback, 10000,
+                          nsITimer::TYPE_ONE_SHOT);
 
   nsCOMPtr<nsIInterfaceRequestor> callbacks;
   mChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
   RefPtr<RedirectSink> sink(new RedirectSink(this, callbacks));
   mChannel->SetNotificationCallbacks(sink);
 }
 
 void PreloaderBase::NotifyUsage(LoadBackground aLoadBackground) {
@@ -141,31 +144,32 @@ void PreloaderBase::NotifyUsage(LoadBack
         if (NS_SUCCEEDED(rv)) {
           loadGroup->AddRequest(mChannel, nullptr);
         }
       }
     }
   }
 
   mIsUsed = true;
-
-  // * Cancel the usage timer.
+  CancelUsageTimer();
 }
 
 void PreloaderBase::RemoveSelf(dom::Document* aDocument) {
   if (aDocument) {
     aDocument->Preloads().DeregisterPreload(&mKey);
   }
 }
 
 void PreloaderBase::NotifyRestart(dom::Document* aDocument,
                                   PreloaderBase* aNewPreloader) {
   RemoveSelf(aDocument);
   mKey = PreloadHashKey();
 
+  CancelUsageTimer();
+
   if (aNewPreloader) {
     aNewPreloader->mNodes = std::move(mNodes);
   }
 }
 
 void PreloaderBase::NotifyStart(nsIRequest* aRequest) {
   // If there is no channel assigned on this preloader, we are not between
   // channel switching, so we can freely update the mShouldFireLoadEvent using
@@ -249,16 +253,23 @@ void PreloaderBase::RemoveLinkPreloadNod
   }
 }
 
 void PreloaderBase::NotifyNodeEvent(nsINode* aNode) {
   PreloadService::NotifyNodeEvent(
       aNode, mShouldFireLoadEvent || NS_SUCCEEDED(*mOnStopStatus));
 }
 
+void PreloaderBase::CancelUsageTimer() {
+  if (mUsageTimer) {
+    mUsageTimer->Cancel();
+    mUsageTimer = nullptr;
+  }
+}
+
 nsresult PreloaderBase::AsyncConsume(nsIStreamListener* aListener) {
   // We want to return an error so that consumers can't ever use a preload to
   // consume data unless it's properly implemented.
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 // PreloaderBase::RedirectRecord
 
@@ -270,9 +281,43 @@ nsCString PreloaderBase::RedirectRecord:
 }
 
 nsCString PreloaderBase::RedirectRecord::Fragment() const {
   nsCString fragment;
   mURI->GetRef(fragment);
   return fragment;
 }
 
+// PreloaderBase::UsageTimer
+
+NS_IMPL_ISUPPORTS(PreloaderBase::UsageTimer, nsITimerCallback)
+
+NS_IMETHODIMP PreloaderBase::UsageTimer::Notify(nsITimer* aTimer) {
+  if (!mPreload || !mDocument) {
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(aTimer == mPreload->mUsageTimer);
+  mPreload->mUsageTimer = nullptr;
+
+  if (mPreload->IsUsed()) {
+    // Left in the hashtable, but marked as used.  This is a valid case, and we
+    // don't want to emit a warning for this preload then.
+    return NS_OK;
+  }
+
+  // PreloadHashKey overrides GetKey, we need to use the nsURIHashKey one to get
+  // the URI.
+  nsIURI* uri = static_cast<nsURIHashKey*>(&mPreload->mKey)->GetKey();
+  if (!uri) {
+    return NS_OK;
+  }
+  nsString spec = NS_ConvertUTF8toUTF16(uri->GetSpecOrDefault());
+
+  nsContentUtils::ReportToConsole(
+      nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DOM"), mDocument,
+      nsContentUtils::eDOM_PROPERTIES, "UnusedLinkPreloadPending",
+      nsTArray<nsString>({spec}));
+
+  return NS_OK;
+}
+
 }  // namespace mozilla
--- a/uriloader/preload/PreloaderBase.h
+++ b/uriloader/preload/PreloaderBase.h
@@ -6,16 +6,17 @@
 #define PreloaderBase_h__
 
 #include "mozilla/Maybe.h"
 #include "mozilla/PreloadHashKey.h"
 #include "mozilla/WeakPtr.h"
 #include "nsIChannelEventSink.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIRedirectResultListener.h"
+#include "nsITimer.h"
 #include "nsIURI.h"
 #include "nsTArray.h"
 #include "nsProxyRelease.h"
 #include "nsWeakReference.h"
 
 class nsIChannel;
 class nsINode;
 class nsIRequest;
@@ -136,16 +137,17 @@ class PreloaderBase : public SupportsWea
  protected:
   virtual ~PreloaderBase();
 
   // The loading channel.  This will update when a redirect occurs.
   nsCOMPtr<nsIChannel> mChannel;
 
  private:
   void NotifyNodeEvent(nsINode* aNode);
+  void CancelUsageTimer();
 
   // A helper class that will update the PreloaderBase.mChannel member when a
   // redirect happens, so that we can reprioritize or cancel when needed.
   // Having a separate class instead of implementing this on PreloaderBase
   // directly is to keep PreloaderBase as simple as possible so that derived
   // classes don't have to deal with calling super when implementing these
   // interfaces from some reason as well.
   class RedirectSink final : public nsIInterfaceRequestor,
@@ -163,24 +165,43 @@ class PreloaderBase : public SupportsWea
     RedirectSink(PreloaderBase* aPreloader, nsIInterfaceRequestor* aCallbacks);
 
    private:
     nsMainThreadPtrHandle<PreloaderBase> mPreloader;
     nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
     nsCOMPtr<nsIChannel> mRedirectChannel;
   };
 
+  // A timer callback to trigger the unuse warning for this preload
+  class UsageTimer final : public nsITimerCallback {
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSITIMERCALLBACK
+
+    UsageTimer(PreloaderBase* aPreload, dom::Document* aDocument)
+        : mDocument(aDocument), mPreload(aPreload) {}
+
+   private:
+    ~UsageTimer() = default;
+
+    WeakPtr<dom::Document> mDocument;
+    WeakPtr<PreloaderBase> mPreload;
+  };
+
  private:
   // Reference to HTMLLinkElement DOM nodes to deliver onload and onerror
   // notifications to.
   nsTArray<nsWeakPtr> mNodes;
 
   // History of redirects.
   nsTArray<RedirectRecord> mRedirectRecords;
 
+  // Usage timer, emits warning when the preload is not used in time.  Started
+  // in NotifyOpen and stopped in NotifyUsage.
+  nsCOMPtr<nsITimer> mUsageTimer;
+
   // The key this preload has been registered under.  We want to remember it to
   // be able to deregister itself from the document's preloads.
   PreloadHashKey mKey;
 
   // This overrides the final event we send to DOM nodes to be always 'load'.
   // Modified in NotifyStart based on LoadInfo data of the loading channel.
   bool mShouldFireLoadEvent = false;