Bug 693808 - part 1: entering numbers+Enter in the location bar should bring search results immediately if domain is not whitelisted, r=bz
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Thu, 24 Apr 2014 23:42:00 +0100
changeset 194910 17a0436f86ee8b456528f9659eb2d74f335ae8c4
parent 194909 ec060e0603dc7069f64fbbd0611553d128f824dc
child 194911 e92d6fb6dd5ffc9a60fb9cb53ac932c4879489d7
push id27158
push userryanvm@gmail.com
push dateFri, 18 Jul 2014 19:21:33 +0000
treeherdermozilla-central@5e5eb00a12e9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs693808
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 693808 - part 1: entering numbers+Enter in the location bar should bring search results immediately if domain is not whitelisted, r=bz
b2g/app/b2g.js
browser/app/profile/firefox.js
browser/metro/profile/metro.js
docshell/base/nsDefaultURIFixup.cpp
docshell/base/nsDefaultURIFixup.h
docshell/base/nsDocShell.cpp
docshell/base/nsIURIFixup.idl
docshell/test/unit/test_nsDefaultURIFixup_info.js
docshell/test/unit/xpcshell.ini
mobile/android/app/mobile.js
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -147,16 +147,17 @@ pref("browser.formfill.enable", true);
 /* spellcheck */
 pref("layout.spellcheckDefault", 0);
 
 /* block popups by default, and notify the user about blocked popups */
 pref("dom.disable_open_during_load", true);
 pref("privacy.popups.showBrowserMessage", true);
 
 pref("keyword.enabled", true);
+pref("browser.fixup.domainwhitelist.localhost", true);
 
 pref("accessibility.typeaheadfind", false);
 pref("accessibility.typeaheadfind.timeout", 5000);
 pref("accessibility.typeaheadfind.flashBar", 1);
 pref("accessibility.typeaheadfind.linksonly", false);
 pref("accessibility.typeaheadfind.casesensitive", 0);
 
 // SSL error page behaviour
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -254,16 +254,17 @@ pref("browser.uitour.themeOrigin", "http
 pref("browser.uitour.pinnedTabUrl", "https://support.mozilla.org/%LOCALE%/kb/pinned-tabs-keep-favorite-websites-open");
 pref("browser.uitour.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/tour/");
 pref("browser.uitour.whitelist.add.260", "www.mozilla.org,support.mozilla.org");
 
 pref("browser.customizemode.tip0.shown", false);
 pref("browser.customizemode.tip0.learnMoreUrl", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/customize");
 
 pref("keyword.enabled", true);
+pref("browser.fixup.domainwhitelist.localhost", true);
 
 pref("general.useragent.locale", "@AB_CD@");
 pref("general.skins.selectedSkin", "classic/1.0");
 
 pref("general.smoothScroll", true);
 #ifdef UNIX_BUT_NOT_MAC
 pref("general.autoScroll", false);
 #else
--- a/browser/metro/profile/metro.js
+++ b/browser/metro/profile/metro.js
@@ -247,16 +247,17 @@ pref("privacy.popups.showBrowserMessage"
 
 // Metro Firefox keeps this set to -1 when donottrackheader.enabled is false.
 pref("privacy.donottrackheader.value", -1);
 
 /* disable opening windows with the dialog feature */
 pref("dom.disable_window_open_dialog_feature", true);
 
 pref("keyword.enabled", true);
+pref("browser.fixup.domainwhitelist.localhost", true);
 
 pref("accessibility.typeaheadfind", false);
 pref("accessibility.typeaheadfind.timeout", 5000);
 pref("accessibility.typeaheadfind.flashBar", 1);
 pref("accessibility.typeaheadfind.linksonly", false);
 pref("accessibility.typeaheadfind.casesensitive", 0);
 
 // Trun on F7 caret browsing hot key
--- a/docshell/base/nsDefaultURIFixup.cpp
+++ b/docshell/base/nsDefaultURIFixup.cpp
@@ -119,21 +119,34 @@ nsDefaultURIFixup::CreateExposableURI(ns
     return NS_OK;
 }
 
 /* nsIURI createFixupURI (in nsAUTF8String aURIText, in unsigned long aFixupFlags); */
 NS_IMETHODIMP
 nsDefaultURIFixup::CreateFixupURI(const nsACString& aStringURI, uint32_t aFixupFlags,
                                   nsIInputStream **aPostData, nsIURI **aURI)
 {
+  nsCOMPtr<nsIURIFixupInfo> fixupInfo;
+  nsresult rv = GetFixupURIInfo(aStringURI, aFixupFlags, aPostData,
+                                getter_AddRefs(fixupInfo));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  fixupInfo->GetPreferredURI(aURI);
+  return rv;
+}
+
+NS_IMETHODIMP
+nsDefaultURIFixup::GetFixupURIInfo(const nsACString& aStringURI, uint32_t aFixupFlags,
+                                   nsIInputStream **aPostData, nsIURIFixupInfo **aInfo)
+{
     NS_ENSURE_ARG(!aStringURI.IsEmpty());
-    NS_ENSURE_ARG_POINTER(aURI);
 
     nsresult rv;
-    *aURI = nullptr;
+    nsRefPtr<nsDefaultURIFixupInfo> info = new nsDefaultURIFixupInfo(aStringURI);
+    NS_ADDREF(*aInfo = info);
 
     nsAutoCString uriString(aStringURI);
     uriString.Trim(" ");  // Cleanup the empty spaces that might be on each end.
 
     // Eliminate embedded newlines, which single-line text fields now allow:
     uriString.StripChars("\r\n");
 
     NS_ENSURE_TRUE(!uriString.IsEmpty(), NS_ERROR_FAILURE);
@@ -144,36 +157,45 @@ nsDefaultURIFixup::CreateFixupURI(const 
     ioService->ExtractScheme(aStringURI, scheme);
     
     // View-source is a pseudo scheme. We're interested in fixing up the stuff
     // after it. The easiest way to do that is to call this method again with the
     // "view-source:" lopped off and then prepend it again afterwards.
 
     if (scheme.LowerCaseEqualsLiteral("view-source"))
     {
-        nsCOMPtr<nsIURI> uri;
+        nsCOMPtr<nsIURIFixupInfo> uriInfo;
         uint32_t newFixupFlags = aFixupFlags & ~FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
 
-        rv =  CreateFixupURI(Substring(uriString,
+        rv = GetFixupURIInfo(Substring(uriString,
                                        sizeof("view-source:") - 1,
                                        uriString.Length() -
                                          (sizeof("view-source:") - 1)),
-                             newFixupFlags, aPostData, getter_AddRefs(uri));
+                             newFixupFlags, aPostData, getter_AddRefs(uriInfo));
         if (NS_FAILED(rv))
             return NS_ERROR_FAILURE;
         nsAutoCString spec;
+        nsCOMPtr<nsIURI> uri;
+        uriInfo->GetPreferredURI(getter_AddRefs(uri));
+        if (!uri)
+            return NS_ERROR_FAILURE;
         uri->GetSpec(spec);
         uriString.AssignLiteral("view-source:");
         uriString.Append(spec);
     }
     else {
         // Check for if it is a file URL
-        FileURIFixup(uriString, aURI);
-        if(*aURI)
+        nsCOMPtr<nsIURI> uri;
+        FileURIFixup(uriString, getter_AddRefs(uri));
+        if (uri)
+        {
+            uri.swap(info->mFixedURI);
+            info->mPreferredURI = info->mFixedURI;
             return NS_OK;
+        }
 
 #if defined(XP_WIN)
         // Not a file URL, so translate '\' to '/' for convenience in the common protocols
         // e.g. catch
         //
         //   http:\\broken.com\address
         //   http:\\broken.com/blah
         //   broken.com\blah
@@ -218,16 +240,18 @@ nsDefaultURIFixup::CreateFixupURI(const 
                 "Failed to observe \"browser.fixup.typo.scheme\"");
 
       rv = Preferences::AddBoolVarCache(&sFixupKeywords, "keyword.enabled",
                                         sFixupKeywords);
       MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to observe \"keyword.enabled\"");
       sInitializedPrefCaches = true;
     }
 
+    info->mInputHasProtocol = !scheme.IsEmpty();
+
     // Fix up common scheme typos.
     if (sFixTypos && (aFixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS)) {
 
         // Fast-path for common cases.
         if (scheme.IsEmpty() ||
             scheme.LowerCaseEqualsLiteral("http") ||
             scheme.LowerCaseEqualsLiteral("https") ||
             scheme.LowerCaseEqualsLiteral("ftp") ||
@@ -261,120 +285,107 @@ nsDefaultURIFixup::CreateFixupURI(const 
     }
 
     // Now we need to check whether "scheme" is something we don't
     // really know about.
     nsCOMPtr<nsIProtocolHandler> ourHandler, extHandler;
     
     ioService->GetProtocolHandler(scheme.get(), getter_AddRefs(ourHandler));
     extHandler = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX"default");
-    
+
+    nsCOMPtr<nsIURI> uri;
     if (ourHandler != extHandler || !PossiblyHostPortUrl(uriString)) {
         // Just try to create an URL out of it
-        rv = NS_NewURI(aURI, uriString, nullptr);
+        rv = NS_NewURI(getter_AddRefs(uri), uriString, nullptr);
+        if (NS_SUCCEEDED(rv)) {
+            info->mFixedURI = uri;
+            // Figure out whether this had a domain or just a single hostname:
+            nsAutoCString host;
+            uri->GetHost(host);
+            info->mInputHostHasDot = host.FindChar('.') != kNotFound;
+        }
 
-        if (!*aURI && rv != NS_ERROR_MALFORMED_URI) {
+        if (!uri && rv != NS_ERROR_MALFORMED_URI) {
             return rv;
         }
     }
 
-    if (*aURI && ourHandler == extHandler && sFixupKeywords &&
+    if (uri && ourHandler == extHandler && sFixupKeywords &&
         (aFixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS)) {
         nsCOMPtr<nsIExternalProtocolService> extProtService =
             do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID);
         if (extProtService) {
             bool handlerExists = false;
             rv = extProtService->ExternalProtocolHandlerExists(scheme.get(), &handlerExists);
             if (NS_FAILED(rv)) {
                 return rv;
             }
             // This basically means we're dealing with a theoretically valid
             // URI... but we have no idea how to load it. (e.g. "christmas:humbug")
             // It's more likely the user wants to search, and so we
             // chuck this over to their preferred search provider instead:
             if (!handlerExists) {
-                NS_RELEASE(*aURI);
-                KeywordToURI(uriString, aPostData, aURI);
+                nsresult rv = KeywordToURI(uriString, aPostData, getter_AddRefs(uri));
+                if (NS_SUCCEEDED(rv) && uri) {
+                  info->mFixupUsedKeyword = true;
+                }
             }
         }
     }
     
-    if (*aURI) {
+    if (uri) {
         if (aFixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI)
-            MakeAlternateURI(*aURI);
+            MakeAlternateURI(uri);
+        info->mPreferredURI = uri;
         return NS_OK;
     }
 
+    // Fix up protocol string before calling KeywordURIFixup, because
+    // it cares about the hostname of such URIs:
+    nsCOMPtr<nsIURI> uriWithProtocol;
+    // NB: this rv gets returned at the end of this method if we never
+    // do a keyword fixup after this (because the pref or the flags passed
+    // might not let us).
+    rv = FixupURIProtocol(uriString, getter_AddRefs(uriWithProtocol));
+    if (uriWithProtocol) {
+        info->mFixedURI = uriWithProtocol;
+        nsAutoCString host;
+        uriWithProtocol->GetHost(host);
+        info->mInputHostHasDot = host.FindChar('.') != kNotFound;
+    }
+
     // See if it is a keyword
     // Test whether keywords need to be fixed up
     if (sFixupKeywords && (aFixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP)) {
-        KeywordURIFixup(uriString, aPostData, aURI);
-        if(*aURI)
+        KeywordURIFixup(uriString, info, aPostData);
+        if (info->mPreferredURI)
             return NS_OK;
     }
 
-    // Prune duff protocol schemes
-    //
-    //   ://totallybroken.url.com
-    //   //shorthand.url.com
-    //
-    if (StringBeginsWith(uriString, NS_LITERAL_CSTRING("://")))
-    {
-        uriString = StringTail(uriString, uriString.Length() - 3);
-    }
-    else if (StringBeginsWith(uriString, NS_LITERAL_CSTRING("//")))
-    {
-        uriString = StringTail(uriString, uriString.Length() - 2);
-    }
-
-    // Add ftp:// or http:// to front of url if it has no spec
-    //
-    // Should fix:
-    //
-    //   no-scheme.com
-    //   ftp.no-scheme.com
-    //   ftp4.no-scheme.com
-    //   no-scheme.com/query?foo=http://www.foo.com
-    //
-    int32_t schemeDelim = uriString.Find("://",0);
-    int32_t firstDelim = uriString.FindCharInSet("/:");
-    if (schemeDelim <= 0 ||
-        (firstDelim != -1 && schemeDelim > firstDelim)) {
-        // find host name
-        int32_t hostPos = uriString.FindCharInSet("/:?#");
-        if (hostPos == -1) 
-            hostPos = uriString.Length();
-
-        // extract host name
-        nsAutoCString hostSpec;
-        uriString.Left(hostSpec, hostPos);
-
-        // insert url spec corresponding to host name
-        if (IsLikelyFTP(hostSpec))
-            uriString.InsertLiteral("ftp://", 0);
-        else 
-            uriString.InsertLiteral("http://", 0);
-    } // end if checkprotocol
-
-    rv = NS_NewURI(aURI, uriString, nullptr);
-
     // Did the caller want us to try an alternative URI?
     // If so, attempt to fixup http://foo into http://www.foo.com
 
-    if (*aURI && aFixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI) {
-        MakeAlternateURI(*aURI);
+    if (info->mFixedURI && aFixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI) {
+        MakeAlternateURI(info->mFixedURI);
     }
 
     // If we still haven't been able to construct a valid URI, try to force a
     // keyword match.  This catches search strings with '.' or ':' in them.
-    if (!*aURI && sFixupKeywords)
+    if (info->mFixedURI)
+    {
+        info->mPreferredURI = info->mFixedURI;
+    }
+    else if (sFixupKeywords && (aFixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP))
     {
-        KeywordToURI(aStringURI, aPostData, aURI);
-        if(*aURI)
+        rv = KeywordToURI(aStringURI, aPostData, getter_AddRefs(info->mPreferredURI));
+        if (NS_SUCCEEDED(rv) && info->mPreferredURI)
+        {
+            info->mFixupUsedKeyword = true;
             return NS_OK;
+        }
     }
 
     return rv;
 }
 
 NS_IMETHODIMP nsDefaultURIFixup::KeywordToURI(const nsACString& aKeyword,
                                               nsIInputStream **aPostData,
                                               nsIURI **aURI)
@@ -703,16 +714,71 @@ nsresult nsDefaultURIFixup::ConvertFileT
             NS_GetURLSpecFromFile(filePath, aOut);
             return NS_OK;
         }
     }
 
     return NS_ERROR_FAILURE;
 }
 
+
+nsresult
+nsDefaultURIFixup::FixupURIProtocol(const nsACString & aURIString,
+                                         nsIURI** aURI)
+{
+    nsAutoCString uriString(aURIString);
+    *aURI = nullptr;
+
+    // Prune duff protocol schemes
+    //
+    //   ://totallybroken.url.com
+    //   //shorthand.url.com
+    //
+    if (StringBeginsWith(uriString, NS_LITERAL_CSTRING("://")))
+    {
+        uriString = StringTail(uriString, uriString.Length() - 3);
+    }
+    else if (StringBeginsWith(uriString, NS_LITERAL_CSTRING("//")))
+    {
+        uriString = StringTail(uriString, uriString.Length() - 2);
+    }
+
+    // Add ftp:// or http:// to front of url if it has no spec
+    //
+    // Should fix:
+    //
+    //   no-scheme.com
+    //   ftp.no-scheme.com
+    //   ftp4.no-scheme.com
+    //   no-scheme.com/query?foo=http://www.foo.com
+    //
+    int32_t schemeDelim = uriString.Find("://",0);
+    int32_t firstDelim = uriString.FindCharInSet("/:");
+    if (schemeDelim <= 0 ||
+        (firstDelim != -1 && schemeDelim > firstDelim)) {
+        // find host name
+        int32_t hostPos = uriString.FindCharInSet("/:?#");
+        if (hostPos == -1)
+            hostPos = uriString.Length();
+
+        // extract host name
+        nsAutoCString hostSpec;
+        uriString.Left(hostSpec, hostPos);
+
+        // insert url spec corresponding to host name
+        if (IsLikelyFTP(hostSpec))
+            uriString.InsertLiteral("ftp://", 0);
+        else
+            uriString.InsertLiteral("http://", 0);
+    } // end if checkprotocol
+
+    return NS_NewURI(aURI, uriString, nullptr);
+}
+
+
 bool nsDefaultURIFixup::PossiblyHostPortUrl(const nsACString &aUrl)
 {
     // Oh dear, the protocol is invalid. Test if the protocol might
     // actually be a url without a protocol:
     //
     //   http://www.faqs.org/rfcs/rfc1738.html
     //   http://www.faqs.org/rfcs/rfc2396.html
     //
@@ -825,26 +891,27 @@ bool nsDefaultURIFixup::PossiblyByteExpa
         if (*iter >= 0x0080 && *iter <= 0x00FF)
             return true;
         ++iter;
     }
     return false;
 }
 
 void nsDefaultURIFixup::KeywordURIFixup(const nsACString & aURIString,
-                                        nsIInputStream **aPostData,
-                                        nsIURI** aURI)
+                                        nsDefaultURIFixupInfo* aFixupInfo,
+                                        nsIInputStream **aPostData)
 {
     // These are keyword formatted strings
     // "what is mozilla"
     // "what is mozilla?"
     // "docshell site:mozilla.org" - has no dot/colon in the first space-separated substring
     // "?mozilla" - anything that begins with a question mark
     // "?site:mozilla.org docshell"
     // Things that have a quote before the first dot/colon
+    // "mozilla" - checked against a whitelist to see if it's a host or not
 
     // These are not keyword formatted strings
     // "www.blah.com" - first space-separated substring contains a dot, doesn't start with "?"
     // "www.blah.com stuff"
     // "nonQualifiedHost:80" - first space-separated substring contains a colon, doesn't start with "?"
     // "nonQualifiedHost:80 args"
     // "nonQualifiedHost?"
     // "nonQualifiedHost?args"
@@ -860,28 +927,133 @@ void nsDefaultURIFixup::KeywordURIFixup(
     if (spaceLoc == 0) {
         // Treat this as not found
         spaceLoc = uint32_t(kNotFound);
     }
     uint32_t qMarkLoc = uint32_t(aURIString.FindChar('?'));
     uint32_t quoteLoc = std::min(uint32_t(aURIString.FindChar('"')),
                                uint32_t(aURIString.FindChar('\'')));
 
+    nsresult rv;
     if (((spaceLoc < dotLoc || quoteLoc < dotLoc) &&
          (spaceLoc < colonLoc || quoteLoc < colonLoc) &&
          (spaceLoc < qMarkLoc || quoteLoc < qMarkLoc)) ||
         qMarkLoc == 0)
     {
-        KeywordToURI(aURIString, aPostData, aURI);
+        rv = KeywordToURI(aURIString, aPostData,
+                          getter_AddRefs(aFixupInfo->mPreferredURI));
+        if (NS_SUCCEEDED(rv) && aFixupInfo->mPreferredURI)
+        {
+            aFixupInfo->mFixupUsedKeyword = true;
+        }
+    }
+    else if (dotLoc == uint32_t(kNotFound) && colonLoc == uint32_t(kNotFound) &&
+             qMarkLoc == uint32_t(kNotFound))
+    {
+        nsAutoCString asciiHost;
+        if (NS_SUCCEEDED(aFixupInfo->mFixedURI->GetAsciiHost(asciiHost)) &&
+            !asciiHost.IsEmpty())
+        {
+            // Check if this domain is whitelisted as an actual
+            // domain (which will prevent a keyword query)
+            nsAutoCString pref("browser.fixup.domainwhitelist.");
+            pref.Append(asciiHost);
+            if (Preferences::GetBool(pref.get(), false))
+            {
+                return;
+            }
+        }
+        // If we get here, we don't have a valid URI, or we did but the
+        // host is not whitelisted, so we do a keyword search *anyway*:
+        rv = KeywordToURI(aURIString, aPostData,
+                          getter_AddRefs(aFixupInfo->mPreferredURI));
+        if (NS_SUCCEEDED(rv) && aFixupInfo->mPreferredURI)
+        {
+            aFixupInfo->mFixupUsedKeyword = true;
+        }
     }
 }
 
-
 nsresult NS_NewURIFixup(nsIURIFixup **aURIFixup)
 {
     nsDefaultURIFixup *fixup = new nsDefaultURIFixup;
     if (fixup == nullptr)
     {
         return NS_ERROR_OUT_OF_MEMORY;
     }
     return fixup->QueryInterface(NS_GET_IID(nsIURIFixup), (void **) aURIFixup);
 }
 
+
+/* Implementation of nsIURIFixupInfo */
+NS_IMPL_ISUPPORTS(nsDefaultURIFixupInfo, nsIURIFixupInfo)
+
+nsDefaultURIFixupInfo::nsDefaultURIFixupInfo(const nsACString& aOriginalInput):
+    mFixupUsedKeyword(false),
+    mInputHasProtocol(false),
+    mInputHostHasDot(false)
+{
+  mOriginalInput = aOriginalInput;
+}
+
+
+nsDefaultURIFixupInfo::~nsDefaultURIFixupInfo()
+{
+}
+
+NS_IMETHODIMP
+nsDefaultURIFixupInfo::GetConsumer(nsISupports** aConsumer)
+{
+    *aConsumer = mConsumer;
+    NS_IF_ADDREF(*aConsumer);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDefaultURIFixupInfo::SetConsumer(nsISupports* aConsumer)
+{
+    mConsumer = aConsumer;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDefaultURIFixupInfo::GetPreferredURI(nsIURI** aPreferredURI)
+{
+    *aPreferredURI = mPreferredURI;
+    NS_IF_ADDREF(*aPreferredURI);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDefaultURIFixupInfo::GetFixedURI(nsIURI** aFixedURI)
+{
+    *aFixedURI = mFixedURI;
+    NS_IF_ADDREF(*aFixedURI);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDefaultURIFixupInfo::GetFixupUsedKeyword(bool* aOut)
+{
+    *aOut = mFixupUsedKeyword;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDefaultURIFixupInfo::GetInputHasProtocol(bool* aOut)
+{
+    *aOut = mInputHasProtocol;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDefaultURIFixupInfo::GetInputHostHasDot(bool* aOut)
+{
+    *aOut = mInputHostHasDot;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDefaultURIFixupInfo::GetOriginalInput(nsACString& aInput)
+{
+    aInput = mOriginalInput;
+    return NS_OK;
+}
--- a/docshell/base/nsDefaultURIFixup.h
+++ b/docshell/base/nsDefaultURIFixup.h
@@ -4,32 +4,59 @@
  * 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/. */
 
 #ifndef NSDEFAULTURIFIXUP_H
 #define NSDEFAULTURIFIXUP_H
 
 #include "nsIURIFixup.h"
 
+class nsDefaultURIFixupInfo;
+
 /* Header file */
 class nsDefaultURIFixup : public nsIURIFixup
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIURIFIXUP
 
     nsDefaultURIFixup();
 
 protected:
     virtual ~nsDefaultURIFixup();
 
 private:
     /* additional members */
     nsresult FileURIFixup(const nsACString &aStringURI, nsIURI** aURI);
     nsresult ConvertFileToStringURI(const nsACString& aIn, nsCString& aOut);
-    void KeywordURIFixup(const nsACString &aStringURI, nsIInputStream** aPostData, nsIURI** aURI);
+    nsresult FixupURIProtocol(const nsACString& aIn, nsIURI** aURI);
+    void KeywordURIFixup(const nsACString &aStringURI,
+                         nsDefaultURIFixupInfo* aFixupInfo,
+                         nsIInputStream** aPostData);
     bool PossiblyByteExpandedFileName(const nsAString& aIn);
     bool PossiblyHostPortUrl(const nsACString& aUrl);
     bool MakeAlternateURI(nsIURI *aURI);
     bool IsLikelyFTP(const nsCString& aHostSpec);
 };
 
+class nsDefaultURIFixupInfo : public nsIURIFixupInfo
+{
+public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIURIFIXUPINFO
+
+    nsDefaultURIFixupInfo(const nsACString& aOriginalInput);
+
+    friend class nsDefaultURIFixup;
+
+protected:
+    virtual ~nsDefaultURIFixupInfo();
+
+private:
+    nsCOMPtr<nsISupports> mConsumer;
+    nsCOMPtr<nsIURI> mPreferredURI;
+    nsCOMPtr<nsIURI> mFixedURI;
+    bool mFixupUsedKeyword;
+    bool mInputHasProtocol;
+    bool mInputHostHasDot;
+    nsAutoCString mOriginalInput;
+};
 #endif
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4357,25 +4357,39 @@ nsDocShell::LoadURIWithBase(const char16
         uint32_t fixupFlags = 0;
         if (aLoadFlags & LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
           fixupFlags |= nsIURIFixup::FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
         }
         if (aLoadFlags & LOAD_FLAGS_FIXUP_SCHEME_TYPOS) {
           fixupFlags |= nsIURIFixup::FIXUP_FLAG_FIX_SCHEME_TYPOS;
         }
         nsCOMPtr<nsIInputStream> fixupStream;
-        rv = sURIFixup->CreateFixupURI(uriString, fixupFlags,
-                                       getter_AddRefs(fixupStream),
-                                       getter_AddRefs(uri));
+        nsCOMPtr<nsIURIFixupInfo> fixupInfo;
+        rv = sURIFixup->GetFixupURIInfo(uriString, fixupFlags,
+                                        getter_AddRefs(fixupStream),
+                                        getter_AddRefs(fixupInfo));
+
+        if (NS_SUCCEEDED(rv)) {
+            fixupInfo->GetPreferredURI(getter_AddRefs(uri));
+            fixupInfo->SetConsumer(GetAsSupports(this));
+        }
+
         if (fixupStream) {
             // CreateFixupURI only returns a post data stream if it succeeded
             // and changed the URI, in which case we should override the
             // passed-in post data.
             postStream = fixupStream;
         }
+
+        if (aLoadFlags & LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
+            nsCOMPtr<nsIObserverService> serv = services::GetObserverService();
+            if (serv) {
+                serv->NotifyObservers(fixupInfo, "keyword-uri-fixup", aURI);
+            }
+        }
     }
     // else no fixup service so just use the URI we created and see
     // what happens
 
     if (NS_ERROR_MALFORMED_URI == rv) {
         DisplayLoadError(rv, uri, aURI, nullptr);
     }
 
--- a/docshell/base/nsIURIFixup.idl
+++ b/docshell/base/nsIURIFixup.idl
@@ -5,19 +5,70 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIURI;
 interface nsIInputStream;
 
 /**
+ * Interface indicating what we found/corrected when fixing up a URI
+ */
+[scriptable, uuid(c9b6cc32-c24e-4283-adaa-9290577fd609)]
+interface nsIURIFixupInfo : nsISupports
+{
+  /**
+   * Consumer that asked for fixed up URI.
+   */
+  attribute nsISupports consumer;
+
+  /**
+   * Our best guess as to what URI the consumer will want. Might
+   * be null if we couldn't salvage anything (for instance, because
+   * the input was invalid as a URI and FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP
+   * was not passed)
+   */
+  readonly attribute nsIURI preferredURI;
+
+  /**
+   * The fixed-up original input, *never* using a keyword search.
+   * (might be null if the original input was not recoverable as
+   * a URL, e.g. "foo bar"!)
+   */
+  readonly attribute nsIURI fixedURI;
+
+  /**
+   * Whether the preferred option ended up using a keyword search.
+   */
+  readonly attribute boolean fixupUsedKeyword;
+
+  /**
+   * Whether we think there was a protocol specified in some way,
+   * even if we corrected it (e.g. "ttp://foo.com/bar")
+   */
+  readonly attribute boolean inputHasProtocol;
+
+  /**
+   * Whether the input included a dot in the hostname, e.g. "mozilla.org"
+   * rather than just "mozilla". This makes a difference in terms of when we
+   * decide to do a keyword search or not.
+   */
+  readonly attribute boolean inputHostHasDot;
+
+  /**
+   * The original input
+   */
+  readonly attribute AUTF8String originalInput;
+};
+
+
+/**
  * Interface implemented by objects capable of fixing up strings into URIs
  */
-[scriptable, uuid(731877f8-973b-414c-b772-9ca1f3fffb7e)]
+[scriptable, uuid(80d4932e-bb2e-4afb-98e0-de9cc9ea7d82)]
 interface nsIURIFixup : nsISupports
 {
     /** No fixup flags. */
     const unsigned long FIXUP_FLAG_NONE = 0;
 
     /**
      * Allow the fixup to use a keyword lookup service to complete the URI.
      * The fixup object implementer should honour this flag and only perform
@@ -59,16 +110,30 @@ interface nsIURIFixup : nsISupports
      * @param aFixupFlags Flags that govern ways the URI may be fixed up.
      * @param aPostData   The POST data to submit with the returned
      *                    URI (see nsISearchSubmission).
      */
     nsIURI createFixupURI(in AUTF8String aURIText, in unsigned long aFixupFlags,
                           [optional] out nsIInputStream aPostData);
 
     /**
+     * Same as createFixupURI, but returns information about what it corrected
+     * (e.g. whether we could rescue the URI or "just" generated a keyword
+     * search URI instead).
+     *
+     * @param aURIText    Candidate URI.
+     * @param aFixupFlags Flags that govern ways the URI may be fixed up.
+     * @param aPostData   The POST data to submit with the returned
+     *                    URI (see nsISearchSubmission).
+     */
+    nsIURIFixupInfo getFixupURIInfo(in AUTF8String aURIText,
+                                    in unsigned long aFixupFlags,
+                                    [optional] out nsIInputStream aPostData);
+
+    /**
      * Converts the specified keyword string into a URI.  Note that it's the
      * caller's responsibility to check whether keywords are enabled and
      * whether aKeyword is a sensible keyword.
      *
      * @param aKeyword  The keyword string to convert into a URI
      * @param aPostData The POST data to submit to the returned URI
      *                  (see nsISearchSubmission).
      *
new file mode 100644
--- /dev/null
+++ b/docshell/test/unit/test_nsDefaultURIFixup_info.js
@@ -0,0 +1,147 @@
+let urifixup = Cc["@mozilla.org/docshell/urifixup;1"].
+               getService(Ci.nsIURIFixup);
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+let prefList = ["browser.fixup.typo.scheme", "keyword.enabled"];
+for (let pref of prefList) {
+  Services.prefs.setBoolPref(pref, true);
+}
+
+const kSearchEngineID = "test_urifixup_search_engine";
+const kSearchEngineURL = "http://www.example.org/?search={searchTerms}";
+Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get",
+                                     kSearchEngineURL);
+
+let oldDefaultEngine = Services.search.defaultEngine;
+Services.search.defaultEngine = Services.search.getEngineByName(kSearchEngineID);
+
+let selectedName = Services.search.defaultEngine.name;
+do_check_eq(selectedName, kSearchEngineID);
+
+do_register_cleanup(function() {
+  if (oldDefaultEngine) {
+    Services.search.defaultEngine = oldDefaultEngine;
+  }
+  let engine = Services.search.getEngineByName(kSearchEngineID);
+  if (engine) {
+    Services.search.removeEngine(engine);
+  }
+  Services.prefs.clearUserPref("keyword.enabled");
+  Services.prefs.clearUserPref("browser.fixup.typo.scheme");
+});
+
+let flagInputs = [
+  urifixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP,
+  urifixup.FIXUP_FLAGS_MAKE_ALTERNATE_URI,
+  urifixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
+];
+
+flagInputs.concat([
+  flagInputs[0] | flagInputs[1],
+  flagInputs[1] | flagInputs[2],
+  flagInputs[0] | flagInputs[2],
+  flagInputs[0] | flagInputs[1] | flagInputs[2]
+]);
+
+let testcases = [
+  ["http://www.mozilla.org", "http://www.mozilla.org/"],
+  ["://www.mozilla.org", "http://www.mozilla.org/"],
+  ["www.mozilla.org", "http://www.mozilla.org/"],
+  ["http://mozilla/", "http://mozilla/"],
+  ["127.0.0.1", "http://127.0.0.1/"],
+  ["1234", "http://1234/"],
+  ["host/foo.txt", "http://host/foo.txt"],
+  ["mozilla", "http://mozilla/"],
+  ["mozilla is amazing", null],
+  ["", null],
+];
+
+if (Services.appinfo.OS.toLowerCase().startsWith("win")) {
+  testcases.push(["C:\\some\\file.txt", "file:///C:/some/file.txt"]);
+} else {
+  testcases.push(["/some/file.txt", "file:///some/file.txt"]);
+}
+
+function run_test() {
+  for (let [testInput, expectedFixedURI] of testcases) {
+    for (let flags of flagInputs) {
+      let info;
+      let fixupURIOnly = null;
+      try {
+        fixupURIOnly = urifixup.createFixupURI(testInput, flags);
+      } catch (ex) {
+        do_check_eq(expectedFixedURI, null);
+      }
+
+      try {
+        info = urifixup.getFixupURIInfo(testInput, flags);
+      } catch (ex) {
+        // Both APIs should return an error in the same cases.
+        do_check_eq(expectedFixedURI, null);
+        do_check_eq(fixupURIOnly, null);
+        continue;
+      }
+
+      // Both APIs should then also be using the same spec.
+      do_check_eq(fixupURIOnly.spec, info.preferredURI.spec);
+
+      let isFileURL = expectedFixedURI && expectedFixedURI.startsWith("file");
+
+      // Check the fixedURI:
+      let alternateURI = flags & urifixup.FIXUP_FLAGS_MAKE_ALTERNATE_URI;
+      if (!isFileURL && alternateURI && !info.inputHostHasDot && info.fixedURI) {
+        let originalURI = Services.io.newURI(expectedFixedURI, null, null);
+        do_check_eq(info.fixedURI.host, "www." + originalURI.host + ".com");
+      } else {
+        do_check_eq(info.fixedURI && info.fixedURI.spec, expectedFixedURI);
+      }
+
+      // Check booleans on input:
+      if (isFileURL) {
+        do_check_eq(info.inputHasProtocol, testInput.startsWith("file:"));
+        do_check_eq(info.inputHostHasDot, false);
+      } else {
+        // The duff protocol doesn't count, so > 0 rather than -1:
+        do_check_eq(info.inputHasProtocol, testInput.indexOf(":") > 0);
+        let dotIndex = testInput.indexOf(".");
+        let slashIndex = testInput.replace("://", "").indexOf("/");
+        slashIndex = slashIndex == -1 ? testInput.length : slashIndex;
+        do_check_eq(info.inputHostHasDot, dotIndex != -1 && slashIndex > dotIndex);
+      }
+
+      let couldDoKeywordLookup = flags & urifixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
+      // Check the preferred URI
+      if (info.inputHostHasDot || info.inputHasProtocol) {
+        // In these cases, we should never be doing a keyword lookup and
+        // the fixed URI should be preferred:
+        do_check_eq(info.preferredURI.spec, info.fixedURI.spec);
+      } else if (!isFileURL && couldDoKeywordLookup && testInput.indexOf(".") == -1) {
+        // Otherwise, and assuming we're allowed, there will be a search URI:
+        let urlparamInput = testInput.replace(/ /g, '+');
+        let searchURL = kSearchEngineURL.replace("{searchTerms}", urlparamInput);
+        do_check_eq(info.preferredURI.spec, searchURL);
+      } else if (info.fixedURI) {
+        // This is for lack of keyword lookup, combined with hostnames with no
+        // protocol:
+        do_check_eq(info.fixedURI, info.preferredURI);
+        if (isFileURL) {
+          do_check_eq(info.fixedURI.host, "");
+        } else {
+          let hostMatch = testInput.match(/(?:[^:\/]*:\/\/)?([^\/]+)(\/|$)/);
+          let host = hostMatch ? hostMatch[1] : "";
+          if (alternateURI) {
+            do_check_eq(info.fixedURI.host, "www." + host + ".com");
+          } else {
+            do_check_eq(info.fixedURI.host, host);
+          }
+        }
+      } else {
+        do_check_true(false, "There should be no cases where we got here, " +
+                             "there's no keyword lookup, and no fixed URI." +
+                             "Offending input: " + testInput);
+      }
+      do_check_eq(testInput, info.originalInput);
+    }
+  }
+}
--- a/docshell/test/unit/xpcshell.ini
+++ b/docshell/test/unit/xpcshell.ini
@@ -2,13 +2,15 @@
 head = head_docshell.js
 tail = 
 
 [test_bug414201_jfif.js]
 [test_bug442584.js]
 [test_nsDefaultURIFixup.js]
 [test_nsDefaultURIFixup_search.js]
 skip-if = os == 'android'
+[test_nsDefaultURIFixup_info.js]
+skip-if = os == 'android'
 [test_nsIDownloadHistory.js]
 [test_pb_notification.js]
 # Bug 751575: unrelated JS changes cause timeouts on random platforms
 skip-if = true
 [test_privacy_transition.js]
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -225,16 +225,17 @@ pref("privacy.popups.showBrowserMessage"
 
 /* disable opening windows with the dialog feature */
 pref("dom.disable_window_open_dialog_feature", true);
 pref("dom.disable_window_showModalDialog", true);
 pref("dom.disable_window_print", true);
 pref("dom.disable_window_find", true);
 
 pref("keyword.enabled", true);
+pref("browser.fixup.domainwhitelist.localhost", true);
 
 pref("accessibility.typeaheadfind", false);
 pref("accessibility.typeaheadfind.timeout", 5000);
 pref("accessibility.typeaheadfind.flashBar", 1);
 pref("accessibility.typeaheadfind.linksonly", false);
 pref("accessibility.typeaheadfind.casesensitive", 0);
 pref("accessibility.browsewithcaret_shortcut.enabled", false);