Bug 401174 - Allow how exthandler adds downloads to history to be overridden. r=biesi, sr=bz, a=mconnor
authorsdwilsh@shawnwilsher.com
Fri, 23 Nov 2007 12:20:41 -0800
changeset 8309 037e6f993c9eda30264760aa8e16c60c24ff3b8c
parent 8308 75ef9461fe3c91e75135c3c576b224c0965520ff
child 8310 6953b5aaf3cff84d2a262f752bcf59f8214cf521
push id1
push userbsmedberg@mozilla.com
push dateThu, 20 Mar 2008 16:49:24 +0000
treeherdermozilla-central@61007906a1f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbiesi, bz, mconnor
bugs401174
milestone1.9b2pre
Bug 401174 - Allow how exthandler adds downloads to history to be overridden. r=biesi, sr=bz, a=mconnor
docshell/base/Makefile.in
docshell/base/nsDocShell.cpp
docshell/base/nsDownloadHistory.cpp
docshell/base/nsDownloadHistory.h
docshell/base/nsIDownloadHistory.idl
docshell/build/nsDocShellCID.h
docshell/build/nsDocShellModule.cpp
docshell/test/Makefile.in
docshell/test/unit/head_docshell.js
docshell/test/unit/test_nsIDownloadHistory.js
netwerk/base/public/nsNetUtil.h
uriloader/exthandler/nsExternalHelperAppService.cpp
--- a/docshell/base/Makefile.in
+++ b/docshell/base/Makefile.in
@@ -109,16 +109,17 @@ XPIDLSRCS	= \
 		nsIWebNavigationInfo.idl        \
 		nsIContentViewer.idl		\
 		nsIContentViewerEdit.idl	\
 		nsIContentViewerFile.idl	\
 		nsIURIFixup.idl			\
 		nsIEditorDocShell.idl		\
 		nsIWebPageDescriptor.idl	\
 		nsIURIClassifier.idl		\
+		nsIDownloadHistory.idl \
 		$(NULL)
 
 EXPORTS		= nsDocShellLoadTypes.h
 
 CPPSRCS		= \
 		nsDocShell.cpp		\
 		nsWebShell.cpp		\
 		nsDocShellLoadInfo.cpp		\
@@ -126,16 +127,17 @@ CPPSRCS		= \
 		nsDocShellTransferableHooks.cpp \
 		nsDocShellEnumerator.cpp  \
 		nsDSURIContentListener.cpp		\
 		nsDefaultURIFixup.cpp		\
 		nsGlobalHistoryAdapter.cpp \
 		nsGlobalHistory2Adapter.cpp \
 		nsWebNavigationInfo.cpp \
 		nsAboutRedirector.cpp \
+		nsDownloadHistory.cpp \
 		$(NULL)
 
 # we don't want the shared lib, but we want to force the creation of a
 # static lib.
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -8504,26 +8504,19 @@ nsDocShell::AddToGlobalHistory(nsIURI * 
     if (mItemType != typeContent || !mGlobalHistory)
         return NS_OK;
 
     PRBool visited;
     nsresult rv = mGlobalHistory->IsVisited(aURI, &visited);
     if (NS_FAILED(rv))
         return rv;
 
-    // Get referrer from the channel. We have to check for a property on a
-    // property bag because the referrer may be empty for security reasons (for
-    // example, when loading a http page with a https referrer).
     nsCOMPtr<nsIURI> referrer;
-    nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel));
-    if (props) {
-        props->GetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"),
-                                      NS_GET_IID(nsIURI),
-                                      getter_AddRefs(referrer));
-    }
+    if (aChannel)
+        NS_GetReferrerFromChannel(aChannel, getter_AddRefs(referrer));
 
     rv = mGlobalHistory->AddURI(aURI, aRedirect, !IsFrame(), referrer);
     if (NS_FAILED(rv))
         return rv;
 
     if (!visited) {
         nsCOMPtr<nsIObserverService> obsService =
             do_GetService("@mozilla.org/observer-service;1");
new file mode 100644
--- /dev/null
+++ b/docshell/base/nsDownloadHistory.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsDownloadHistory.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIGlobalHistory.h"
+#include "nsIGlobalHistory2.h"
+#include "nsIObserverService.h"
+#include "nsIURI.h"
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsDownloadHistory
+
+NS_IMPL_ISUPPORTS1(nsDownloadHistory, nsIDownloadHistory)
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIDownloadHistory
+
+NS_IMETHODIMP
+nsDownloadHistory::AddDownload(nsIURI *aSource,
+                               nsIURI *aReferrer,
+                               PRTime aStartTime)
+{
+  NS_ENSURE_ARG_POINTER(aSource);
+
+  nsCOMPtr<nsIGlobalHistory2> history =
+    do_GetService("@mozilla.org/browser/global-history;2");
+  if (!history)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  PRBool visited;
+  nsresult rv = history->IsVisited(aSource, &visited);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = history->AddURI(aSource, PR_FALSE, PR_TRUE, aReferrer);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  if (!visited) {
+    nsCOMPtr<nsIObserverService> os =
+      do_GetService("@mozilla.org/observer-service;1");
+    if (os)
+      os->NotifyObservers(aSource, NS_LINK_VISITED_EVENT_TOPIC, nsnull);
+  }
+
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/docshell/base/nsDownloadHistory.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef __nsDownloadHistory_h__
+#define __nsDownloadHistory_h__
+
+#include "nsIDownloadHistory.h"
+
+#define NS_DOWNLOADHISTORY_CID \
+  {0x2ee83680, 0x2af0, 0x4bcb, {0xbf, 0xa0, 0xc9, 0x70, 0x5f, 0x65, 0x54, 0xf1}}
+
+class nsDownloadHistory : public nsIDownloadHistory
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOWNLOADHISTORY
+};
+
+#endif // __nsDownloadHistory_h__
new file mode 100644
--- /dev/null
+++ b/docshell/base/nsIDownloadHistory.idl
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * This interface can be used to add a download to history.  There is a separate
+ * interface specifically for downloads in case embedders choose to track
+ * downloads differently from other types of history.
+ */
+[scriptable, uuid(202533cd-a8f1-4ee4-8d20-3a6a0d2c6c51)]
+interface nsIDownloadHistory : nsISupports {
+  /**
+   * Adds a download to history.  This will also notify observers that the
+   * URI aSource is visited with the topic NS_LINK_VISITED_EVENT_TOPIC if
+   * aSource has not yet been visited.
+   *
+   * @param aSource
+   *        The source of the download we are adding to history.  This cannot be
+   *        null.
+   * @param aReferrer
+   *        [optional] The referrer of source URI.
+   * @param aStartTime
+   *        [optional] The time the download was started.  If the start time
+   *        is not given, the current time is used.
+   * @throws NS_ERROR_NOT_AVAILABLE
+   *         In a situation where a history implementation is not available,
+   *         where 'history implementation' refers to something like
+   *         nsIGlobalHistory and friends.
+   */
+  void addDownload(in nsIURI aSource, [optional] in nsIURI aReferrer,
+                   [optional] in PRTime aStartTime);
+};
+
--- a/docshell/build/nsDocShellCID.h
+++ b/docshell/build/nsDocShellCID.h
@@ -1,10 +1,11 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 sts=4
+ * ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
  * Software distributed under the License is distributed on an "AS IS" basis,
@@ -37,16 +38,26 @@
 
 #ifndef nsDocShellCID_h__
 #define nsDocShellCID_h__
 
 #define NS_GLOBALHISTORY2_CONTRACTID \
     "@mozilla.org/browser/global-history;2"
 
 /**
+ * A contract for a service that will track download history.  This can be
+ * overridden by embedders if they would like to track additional information
+ * about downloads.
+ *
+ * @implements nsIDownloadHistory
+ */
+#define NS_DOWNLOADHISTORY_CONTRACTID \
+    "@mozilla.org/browser/download-history;1"
+
+/**
  * A contract that can be used to get a service that provides
  * meta-information about nsIWebNavigation objects' capabilities.
  * @implements nsIWebNavigationInfo
  */
 #define NS_WEBNAVIGATION_INFO_CONTRACTID \
     "@mozilla.org/webnavigation-info;1"
 
 /**
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -61,16 +61,19 @@
 #include "nsSHEntry.h"
 #include "nsSHistory.h"
 #include "nsSHTransaction.h"
 
 // global history
 #include "nsGlobalHistoryAdapter.h"
 #include "nsGlobalHistory2Adapter.h"
 
+// download history
+#include "nsDownloadHistory.h"
+
 static PRBool gInitialized = PR_FALSE;
 
 // The one time initialization for this module
 // static
 PR_STATIC_CALLBACK(nsresult)
 Initialize(nsIModule* aSelf)
 {
   NS_PRECONDITION(!gInitialized, "docshell module already initialized");
@@ -114,16 +117,19 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(PlatformL
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsInternetConfigService)
 #endif
 
 // session history
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHEntry)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHTransaction)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHistory)
 
+// download history
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDownloadHistory)
+
 // Currently no-one is instantiating docshell's directly because
 // nsWebShell is still our main "shell" class. nsWebShell is a subclass
 // of nsDocShell. Once migration is complete, docshells will be the main
 // "shell" class and this module will need to register the docshell as
 // a component
 //NS_GENERIC_FACTORY_CONSTRUCTOR(nsDocShell)
 
 static const nsModuleComponentInfo gDocShellModuleInfo[] = {
@@ -235,15 +241,19 @@ static const nsModuleComponentInfo gDocS
       NS_SHISTORY_INTERNAL_CONTRACTID, nsSHistoryConstructor },
 
     // global history adapters
     { "nsGlobalHistoryAdapter", NS_GLOBALHISTORYADAPTER_CID,
       nsnull, nsGlobalHistoryAdapter::Create,
       nsGlobalHistoryAdapter::RegisterSelf },
     { "nsGlobalHistory2Adapter", NS_GLOBALHISTORY2ADAPTER_CID,
       nsnull, nsGlobalHistory2Adapter::Create,
-      nsGlobalHistory2Adapter::RegisterSelf }
+      nsGlobalHistory2Adapter::RegisterSelf },
+    
+    // download history
+    { "nsDownloadHistory", NS_DOWNLOADHISTORY_CID,
+      NS_DOWNLOADHISTORY_CONTRACTID, nsDownloadHistoryConstructor }
 };
 
 // "docshell provider" to illustrate that this thing really *should*
 // be dispensing docshells rather than webshells.
 NS_IMPL_NSGETMODULE_WITH_CTOR_DTOR(docshell_provider, gDocShellModuleInfo,
                                    Initialize, Shutdown)
--- a/docshell/test/Makefile.in
+++ b/docshell/test/Makefile.in
@@ -36,20 +36,24 @@
 # ***** END LICENSE BLOCK *****
 
 DEPTH		= ../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = docshell/test
 
+MODULE = test_docshell
+
 DIRS += chrome \
 	browser \
 	$(NULL)
 
+XPCSHELL_TESTS = unit
+
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
 		test_bug344861.html \
 		test_bug384014.html \
 		test_bug387979.html \
 		test_bug404548.html \
new file mode 100644
--- /dev/null
+++ b/docshell/test/unit/head_docshell.js
@@ -0,0 +1,90 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+             getService(Ci.nsIProperties);
+var profileDir = dirSvc.get("CurProcD", Ci.nsILocalFile);
+profileDir.append("test_docshell_profile");
+
+// Register our own provider for the profile directory.
+// It will return our special docshell profile directory.
+var provider = {
+  getFile: function(prop, persistent) {
+    persistent.value = true;
+    if (prop == "ProfD") {
+      var retVal = dirSvc.get("CurProcD", Ci.nsILocalFile);
+      retVal.append("test_docshell_profile");
+      if (!retVal.exists())
+        retVal.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
+      return retVal;
+    }
+    throw Cr.NS_ERROR_FAILURE;
+  },
+  QueryInterface: function(iid) {
+    if (iid.equals(Ci.nsIDirectoryProvider) ||
+        iid.equals(Ci.nsISupports)) {
+      return this;
+    }
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  }
+};
+dirSvc.QueryInterface(Ci.nsIDirectoryService).registerProvider(provider);
+
+function cleanup()
+{
+  // we need to remove the folder that we created for the profile
+  try {
+    if (profileDir.exists())
+      profileDir.remove(true);
+  } catch (e) {
+    // windows has a slight problem with sqlite databases and trying to remove
+    // them to quickly after you might expect to be done with them.  Eat any
+    // errors we'll get.  This should be OK because we cleanup before and after
+    // each test run.
+  }
+}
+
+
+// cleanup from any failed test runs in the past
+cleanup();
+
+// make sure we have our profile directory available to us
+profileDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
new file mode 100644
--- /dev/null
+++ b/docshell/test/unit/test_nsIDownloadHistory.js
@@ -0,0 +1,81 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const NS_DOWNLOADHISTORY_CID = "{2ee83680-2af0-4bcb-bfa0-c9705f6554f1}";
+
+function testLinkVistedObserver()
+{
+  const NS_LINK_VISITED_EVENT_TOPIC = "link-visited";
+  var ios = Cc["@mozilla.org/network/io-service;1"].
+            getService(Ci.nsIIOService);
+  var testURI = ios.newURI("http://google.com/", null, null);
+
+  var gh = Cc["@mozilla.org/browser/global-history;2"].
+           getService(Ci.nsIGlobalHistory2);
+  do_check_false(gh.isVisited(testURI));
+
+  var topicReceived = false;
+  var obs = {
+    observe: function tlvo_observe(aSubject, aTopic, aData)
+    {
+      if (NS_LINK_VISITED_EVENT_TOPIC == aTopic) {
+        do_check_eq(testURI, aSubject);
+        topicReceived = true;
+      }
+    }
+  };
+
+  var os = Cc["@mozilla.org/observer-service;1"].
+           getService(Ci.nsIObserverService);
+  os.addObserver(obs, NS_LINK_VISITED_EVENT_TOPIC, false);
+
+  var dh = Components.classesByID[NS_DOWNLOADHISTORY_CID].
+           getService(Ci.nsIDownloadHistory);
+  dh.addDownload(testURI);
+  do_check_true(topicReceived);
+  do_check_true(gh.isVisited(testURI));
+}
+
+var tests = [testLinkVistedObserver];
+
+function run_test()
+{
+  for (var i = 0; i < tests.length; i++)
+    tests[i]();
+
+  cleanup();
+}
--- a/netwerk/base/public/nsNetUtil.h
+++ b/netwerk/base/public/nsNetUtil.h
@@ -87,16 +87,17 @@
 #include "nsINetUtil.h"
 #include "nsIAuthPrompt.h"
 #include "nsIAuthPrompt2.h"
 #include "nsIAuthPromptAdapterFactory.h"
 #include "nsComponentManagerUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsINestedURI.h"
 #include "nsIMutable.h"
+#include "nsIPropertyBag2.h"
 
 // Helper, to simplify getting the I/O service.
 inline const nsGetServiceByContractIDWithError
 do_GetIOService(nsresult* error = 0)
 {
     return nsGetServiceByContractIDWithError(NS_IOSERVICE_CONTRACTID, error);
 }
 
@@ -694,16 +695,55 @@ NS_GetURLSpecFromFile(nsIFile      *file
     nsresult rv;
     nsCOMPtr<nsIFileProtocolHandler> fileHandler;
     rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
     if (NS_SUCCEEDED(rv))
         rv = fileHandler->GetURLSpecFromFile(file, url);
     return rv;
 }
 
+/**
+ * Obtains the referrer for a given channel.  This first tries to obtain the
+ * referrer from the property docshell.internalReferrer, and if that doesn't
+ * work and the channel is an nsIHTTPChannel, we check it's referrer property.
+ *
+ * @returns NS_ERROR_NOT_AVAILABLE if no referrer is available.
+ */
+inline nsresult
+NS_GetReferrerFromChannel(nsIChannel *channel,
+                          nsIURI **referrer)
+{
+    nsresult rv = NS_ERROR_NOT_AVAILABLE;
+    *referrer = nsnull;
+
+    nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(channel));
+    if (props) {
+      // We have to check for a property on a property bag because the
+      // referrer may be empty for security reasons (for example, when loading
+      // an http page with an https referrer).
+      rv = props->GetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"),
+                                         NS_GET_IID(nsIURI),
+                                         reinterpret_cast<void **>(referrer));
+      if (NS_FAILED(rv))
+        *referrer = nsnull;
+    }
+
+    // if that didn't work, we can still try to get the referrer from the
+    // nsIHttpChannel (if we can QI to it)
+    if (!(*referrer)) {
+      nsCOMPtr<nsIHttpChannel> chan(do_QueryInterface(channel));
+      if (chan) {
+        rv = chan->GetReferrer(referrer);
+        if (NS_FAILED(rv))
+          *referrer = nsnull;
+      }
+    }
+    return rv;
+}
+
 #ifdef MOZILLA_INTERNAL_API
 inline nsresult
 NS_ExamineForProxy(const char    *scheme,
                    const char    *host,
                    PRInt32        port, 
                    nsIProxyInfo **proxyInfo)
 {
     nsresult rv;
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -104,18 +104,18 @@
 #include "nsIPrompt.h"
 
 #include "nsITextToSubURI.h" // to unescape the filename
 #include "nsIMIMEHeaderParam.h"
 
 #include "nsIPrefService.h"
 #include "nsIWindowWatcher.h"
 
-#include "nsIGlobalHistory.h" // to mark downloads as visited
-#include "nsIGlobalHistory2.h" // to mark downloads as visited
+#include "nsIDownloadHistory.h" // to mark downloads as visited
+#include "nsDocShellCID.h"
 
 #include "nsIDOMWindow.h"
 #include "nsIDOMWindowInternal.h"
 #include "nsIDocShell.h"
 
 #include "nsCRT.h"
 
 #include "nsLocalHandlerApp.h"
@@ -1492,34 +1492,23 @@ NS_IMETHODIMP nsExternalAppHandler::OnSt
         rv = LaunchWithApplication(nsnull, PR_FALSE);
     }
     else // Various unknown actions go here too
     {
         rv = SaveToDisk(nsnull, PR_FALSE);
     }
   }
 
-  // Now let's mark the downloaded URL as visited
-  nsCOMPtr<nsIGlobalHistory> history(do_GetService(NS_GLOBALHISTORY_CONTRACTID));
-  nsCAutoString spec;
-  mSourceUrl->GetSpec(spec);
-  if (history && !spec.IsEmpty())
-  {
-    PRBool visited;
-    rv = history->IsVisited(spec.get(), &visited);
-    if (NS_FAILED(rv))
-        return rv;
-    history->AddPage(spec.get());
-    if (!visited) {
-      nsCOMPtr<nsIObserverService> obsService =
-          do_GetService("@mozilla.org/observer-service;1");
-      if (obsService) {
-        obsService->NotifyObservers(mSourceUrl, NS_LINK_VISITED_EVENT_TOPIC, nsnull);
-      }
-    }
+  // Now let's add the download to history
+  nsCOMPtr<nsIDownloadHistory> dh(do_GetService(NS_DOWNLOADHISTORY_CONTRACTID));
+  if (dh) {
+    nsCOMPtr<nsIURI> referrer;
+    if (aChannel)
+      NS_GetReferrerFromChannel(aChannel, getter_AddRefs(referrer));
+    dh->AddDownload(mSourceUrl, referrer, mTimeDownloadStarted);
   }
 
   return NS_OK;
 }
 
 // Convert error info into proper message text and send OnStatusChange notification
 // to the web progress listener.
 void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv, nsIRequest *aRequest, const nsAFlatString &path)