Bug 1029887: Keep track of DOM nodes blocked by NS_ERROR_TRACKING_URI (r=khuey,smaug)
authorGeorgios Kontaxis <gkontaxis@mozilla.com>
Tue, 24 Jun 2014 17:37:03 -0700
changeset 217120 a7ee14e173393822b22ad1a7ed02d29c76f944e4
parent 217119 df134a5be8fb86a29813c7c403e1106b3faa5bba
child 217121 0416b6a1ba2d02ddb100cb45fd736731002e3cdc
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey, smaug
bugs1029887
milestone33.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 1029887: Keep track of DOM nodes blocked by NS_ERROR_TRACKING_URI (r=khuey,smaug)
content/base/public/nsIDocument.h
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
content/base/src/nsImageLoadingContent.cpp
content/base/src/nsScriptLoader.cpp
docshell/base/nsDocShell.cpp
dom/webidl/HTMLDocument.webidl
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -2321,16 +2321,34 @@ public:
 
   // Each import tree has exactly one master document which is
   // the root of the tree, and owns the browser context.
   virtual already_AddRefed<nsIDocument> MasterDocument() = 0;
   virtual void SetMasterDocument(nsIDocument* master) = 0;
   virtual bool IsMasterDocument() = 0;
   virtual already_AddRefed<mozilla::dom::ImportManager> ImportManager() = 0;
 
+  /*
+   * Given a node, get a weak reference to it and append that reference to
+   * mBlockedTrackingNodes. Can be used later on to look up a node in it.
+   * (e.g., by the UI)
+   */
+  void AddBlockedTrackingNode(nsINode *node)
+  {
+    if (!node) {
+      return;
+    }
+
+    nsWeakPtr weakNode = do_GetWeakReference(node);
+
+    if (weakNode) {
+      mBlockedTrackingNodes.AppendElement(weakNode);
+    }
+  }
+
 private:
   uint64_t mWarnedAbout;
   SelectorCache mSelectorCache;
 
 protected:
   ~nsIDocument();
   nsPropertyTable* GetExtraPropertyTable(uint16_t aCategory);
 
@@ -2623,16 +2641,23 @@ protected:
    */
   uint32_t mExternalScriptsBeingEvaluated;
 
   /**
    * The current frame request callback handle
    */
   int32_t mFrameRequestCallbackCounter;
 
+  // Array of nodes that have been blocked to prevent user tracking.
+  // They most likely have had their nsIChannel canceled by the URL
+  // classifier. (Safebrowsing)
+  //
+  // Weak nsINode pointers are used to allow nodes to disappear.
+  nsTArray<nsWeakPtr> mBlockedTrackingNodes;
+
   // Weak reference to mScriptGlobalObject QI:d to nsPIDOMWindow,
   // updated on every set of mSecriptGlobalObject.
   nsPIDOMWindow *mWindow;
 
   nsCOMPtr<nsIDocumentEncoder> mCachedEncoder;
 
   struct FrameRequest;
 
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -5969,16 +5969,44 @@ nsDocument::GetElementsByTagName(const n
   nsRefPtr<nsContentList> list = GetElementsByTagName(aTagname);
   NS_ENSURE_TRUE(list, NS_ERROR_OUT_OF_MEMORY);
 
   // transfer ref to aReturn
   list.forget(aReturn);
   return NS_OK;
 }
 
+long
+nsDocument::BlockedTrackingNodeCount() const
+{
+  return mBlockedTrackingNodes.Length();
+}
+
+already_AddRefed<nsSimpleContentList>
+nsDocument::BlockedTrackingNodes() const
+{
+  nsRefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
+
+  nsTArray<nsWeakPtr> blockedTrackingNodes;
+  blockedTrackingNodes = mBlockedTrackingNodes;
+
+  for (unsigned long i = 0; i < blockedTrackingNodes.Length(); i++) {
+    nsWeakPtr weakNode = blockedTrackingNodes[i];
+    nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode);
+    // Consider only nodes to which we have managed to get strong references.
+    // Coping with nullptrs since it's expected for nodes to disappear when
+    // nobody else is referring to them.
+    if (node) {
+      list->AppendElement(node);
+    }
+  }
+
+  return list.forget();
+}
+
 already_AddRefed<nsContentList>
 nsIDocument::GetElementsByTagNameNS(const nsAString& aNamespaceURI,
                                     const nsAString& aLocalName,
                                     ErrorResult& aResult)
 {
   int32_t nameSpaceId = kNameSpaceID_Wildcard;
 
   if (!aNamespaceURI.EqualsLiteral("*")) {
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -1141,16 +1141,42 @@ public:
   virtual void SetApprovedForFullscreen(bool aIsApproved) MOZ_OVERRIDE;
   virtual nsresult RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement,
                                                 const nsAString& aNewOrigin) MOZ_OVERRIDE;
 
   virtual nsresult RemoteFrameFullscreenReverted() MOZ_OVERRIDE;
   virtual nsIDocument* GetFullscreenRoot() MOZ_OVERRIDE;
   virtual void SetFullscreenRoot(nsIDocument* aRoot) MOZ_OVERRIDE;
 
+  // Returns the size of the mBlockedTrackingNodes array. (nsIDocument.h)
+  //
+  // This array contains nodes that have been blocked to prevent
+  // user tracking. They most likely have had their nsIChannel
+  // canceled by the URL classifier (Safebrowsing).
+  //
+  // A script can subsequently use GetBlockedTrackingNodes()
+  // to get a list of references to these nodes.
+  //
+  // Note:
+  // This expresses how many tracking nodes have been blocked for this
+  // document since its beginning, not how many of them are still around
+  // in the DOM tree. Weak references to blocked nodes are added in the
+  // mBlockedTrackingNodesArray but they are not removed when those nodes
+  // are removed from the tree or even garbage collected.
+  long BlockedTrackingNodeCount() const;
+
+  //
+  // Returns strong references to mBlockedTrackingNodes. (nsIDocument.h)
+  //
+  // This array contains nodes that have been blocked to prevent
+  // user tracking. They most likely have had their nsIChannel
+  // canceled by the URL classifier (Safebrowsing).
+  //
+  already_AddRefed<nsSimpleContentList> BlockedTrackingNodes() const;
+
   static void ExitFullscreen(nsIDocument* aDoc);
 
   // This is called asynchronously by nsIDocument::AsyncRequestFullScreen()
   // to move this document into full-screen mode if allowed. aWasCallerChrome
   // should be true when nsIDocument::AsyncRequestFullScreen() was called
   // by chrome code. aNotifyOnOriginChange denotes whether we should send a
   // fullscreen-origin-change notification if requesting fullscreen in this
   // document causes the origin which is fullscreen to change. We may want to
--- a/content/base/src/nsImageLoadingContent.cpp
+++ b/content/base/src/nsImageLoadingContent.cpp
@@ -158,16 +158,34 @@ nsImageLoadingContent::Notify(imgIReques
     // Have to check for state changes here, since we might have been in
     // the LOADING state before.
     UpdateImageState(true);
   }
 
   if (aType == imgINotificationObserver::LOAD_COMPLETE) {
     uint32_t reqStatus;
     aRequest->GetImageStatus(&reqStatus);
+    /* triage STATUS_ERROR */
+    if (reqStatus & imgIRequest::STATUS_ERROR) {
+      nsresult errorCode = NS_OK;
+      aRequest->GetImageErrorCode(&errorCode);
+
+      /* Handle image not loading error because source was a tracking URL.
+       * (Safebrowinsg) We make a note of this image node by including it
+       * in a dedicated array of blocked tracking nodes under its parent
+       * document.
+       */
+      if (errorCode == NS_ERROR_TRACKING_URI) {
+        nsCOMPtr<nsIContent> thisNode
+          = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+
+        nsIDocument *doc = GetOurOwnerDoc();
+        doc->AddBlockedTrackingNode(thisNode);
+      }
+    }
     nsresult status =
         reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
     return OnStopRequest(aRequest, status);
   }
 
   if (aType == imgINotificationObserver::DECODE_COMPLETE && mFireEventsOnDecode) {
     mFireEventsOnDecode = false;
 
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -1397,16 +1397,27 @@ nsScriptLoader::OnStreamComplete(nsIStre
 {
   nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext);
   NS_ASSERTION(request, "null request in stream complete handler");
   NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
 
   nsresult rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen,
                                      aString);
   if (NS_FAILED(rv)) {
+    /*
+     * Handle script not loading error because source was a tracking URL.
+     * (Safebrowinsg) We make a note of this script node by including it
+     * in a dedicated array of blocked tracking nodes under its parent
+     * document.
+     */
+    if (rv == NS_ERROR_TRACKING_URI) {
+      nsCOMPtr<nsIContent> cont = do_QueryInterface(request->mElement);
+      mDocument->AddBlockedTrackingNode(cont);
+    }
+
     if (mDeferRequests.RemoveElement(request) ||
         mAsyncRequests.RemoveElement(request) ||
         mNonAsyncExternalScriptInsertedRequests.RemoveElement(request) ||
         mXSLTRequests.RemoveElement(request)) {
       FireScriptAvailable(rv, request);
     } else if (mParserBlockingRequest == request) {
       mParserBlockingRequest = nullptr;
       UnblockParser(request);
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -7062,16 +7062,53 @@ nsDocShell::EndPageLoad(nsIWebProgress *
     if (url && NS_FAILED(aStatus)) {
         if (aStatus == NS_ERROR_FILE_NOT_FOUND ||
             aStatus == NS_ERROR_CORRUPTED_CONTENT ||
             aStatus == NS_ERROR_INVALID_CONTENT_ENCODING) {
             DisplayLoadError(aStatus, url, nullptr, aChannel);
             return NS_OK;
         }
 
+        // Handle iframe document not loading error because source was
+        // a tracking URL. (Safebrowsing) We make a note of this iframe
+        // node by including it in a dedicated array of blocked tracking
+        // nodes under its parent document. (document of parent window of
+        // blocked document)
+        if (isTopFrame == false && aStatus == NS_ERROR_TRACKING_URI) {
+            // frameElement is our nsIContent to be annotated
+            nsCOMPtr<nsIDOMElement> frameElement;
+            nsPIDOMWindow* thisWindow = GetWindow();
+            if (!thisWindow) {
+                return NS_OK;
+            }
+
+            thisWindow->GetFrameElement(getter_AddRefs(frameElement));
+            if (!frameElement) {
+                return NS_OK;
+            }
+
+            // Parent window
+            nsCOMPtr<nsIDocShellTreeItem> parentItem;
+            GetSameTypeParent(getter_AddRefs(parentItem));
+            if (!parentItem) {
+                return NS_OK;
+            }
+
+            nsCOMPtr<nsIDocument> parentDoc;
+            parentDoc = parentItem->GetDocument();
+            if (!parentDoc) {
+                return NS_OK;
+            }
+
+            nsCOMPtr<nsIContent> cont = do_QueryInterface(frameElement);
+            parentDoc->AddBlockedTrackingNode(cont);
+
+            return NS_OK;
+        }
+
         if (sURIFixup) {
             //
             // Try and make an alternative URI from the old one
             //
             nsCOMPtr<nsIURI> newURI;
             nsCOMPtr<nsIInputStream> newPostData;
 
             nsAutoCString oldSpec;
--- a/dom/webidl/HTMLDocument.webidl
+++ b/dom/webidl/HTMLDocument.webidl
@@ -79,8 +79,24 @@ interface HTMLDocument : Document {
   Selection? getSelection();
 
   // @deprecated These are old Netscape 4 methods. Do not use,
   //             the implementation is no-op.
   // XXXbz do we actually need these anymore?
   void                      captureEvents();
   void                      releaseEvents();
 };
+
+partial interface HTMLDocument {
+  /*
+   * Number of nodes that have been blocked by
+   * the Safebrowsing API to prevent tracking.
+   */
+  [ChromeOnly, Pure]
+  readonly attribute long blockedTrackingNodeCount;
+
+  /*
+   * List of nodes that have been blocked by
+   * the Safebrowsing API to prevent tracking.
+   */
+  [ChromeOnly, Pure]
+  readonly attribute NodeList blockedTrackingNodes;
+};