Merge fx-team to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 02 Jul 2014 17:24:00 -0700
changeset 191980 38ecfc3922b8f96c4d552b86b5bec0f681b03521
parent 191971 5d9af625f42e8a634bbac2e0a0a8b9619c4e388b (current diff)
parent 191979 dfe3a1df68e730cc44c848c2a635cf4e44351e2c (diff)
child 191981 0dd44135ac327f464dfa4b5953dcda38a9ec5a9b
child 191993 736f93e1eeeda0f6de74a2625c07c6f4ce1d242d
child 192053 0cce91c77c3f1e649180df99644a670dde6825ab
child 192086 ac60de9486482c926844e95e711df6a06e7e7e88
push id27070
push userkwierso@gmail.com
push dateThu, 03 Jul 2014 00:24:14 +0000
treeherdermozilla-central@38ecfc3922b8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone33.0a1
first release with
nightly linux32
38ecfc3922b8 / 33.0a1 / 20140703030200 / files
nightly linux64
38ecfc3922b8 / 33.0a1 / 20140703030200 / files
nightly mac
38ecfc3922b8 / 33.0a1 / 20140703030200 / files
nightly win32
38ecfc3922b8 / 33.0a1 / 20140703030200 / files
nightly win64
38ecfc3922b8 / 33.0a1 / 20140703030200 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c a=merge
browser/base/content/browser.js
browser/base/content/test/general/browser_locationBarExternalLoad.js
browser/base/content/test/general/browser_middleMouse_inherit.js
mobile/android/base/db/SearchHistoryProvider.java
mobile/android/base/tests/testSearchHistoryProvider.java
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1905,43 +1905,40 @@ function loadURI(uri, referrer, postData
   }
 
   try {
     gBrowser.loadURIWithFlags(uri, flags, referrer, null, postData);
   } catch (e) {}
 }
 
 function getShortcutOrURIAndPostData(aURL, aCallback) {
-  let mayInheritPrincipal = false;
   let postData = null;
   let shortcutURL = null;
   let keyword = aURL;
   let param = "";
 
   let offset = aURL.indexOf(" ");
   if (offset > 0) {
     keyword = aURL.substr(0, offset);
     param = aURL.substr(offset + 1);
   }
 
   let engine = Services.search.getEngineByAlias(keyword);
   if (engine) {
     let submission = engine.getSubmission(param);
     postData = submission.postData;
-    aCallback({ postData: submission.postData, url: submission.uri.spec,
-                mayInheritPrincipal: mayInheritPrincipal });
+    aCallback({ postData: submission.postData, url: submission.uri.spec });
     return;
   }
 
   [shortcutURL, postData] =
     PlacesUtils.getURLAndPostDataForKeyword(keyword);
 
   if (!shortcutURL) {
-    aCallback({ postData: postData, url: aURL,
-                mayInheritPrincipal: mayInheritPrincipal });
+    aCallback({ postData: postData, url: aURL });
     return;
   }
 
   let escapedPostData = "";
   if (postData)
     escapedPostData = unescape(postData);
 
   if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) {
@@ -1963,22 +1960,17 @@ function getShortcutOrURIAndPostData(aUR
         encodedParam = encodeURIComponent(param);
 
       shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param);
 
       if (/%s/i.test(escapedPostData)) // POST keyword
         postData = getPostDataStream(escapedPostData, param, encodedParam,
                                                "application/x-www-form-urlencoded");
 
-      // This URL came from a bookmark, so it's safe to let it inherit the current
-      // document's principal.
-      mayInheritPrincipal = true;
-
-      aCallback({ postData: postData, url: shortcutURL,
-                  mayInheritPrincipal: mayInheritPrincipal });
+      aCallback({ postData: postData, url: shortcutURL });
     }
 
     if (matches) {
       [, shortcutURL, charset] = matches;
       continueOperation();
     } else {
       // Try to get the saved character-set.
       // makeURI throws if URI is invalid.
@@ -1991,25 +1983,19 @@ function getShortcutOrURIAndPostData(aUR
       }
     }
   }
   else if (param) {
     // This keyword doesn't take a parameter, but one was provided. Just return
     // the original URL.
     postData = null;
 
-    aCallback({ postData: postData, url: aURL,
-                mayInheritPrincipal: mayInheritPrincipal });
+    aCallback({ postData: postData, url: aURL });
   } else {
-    // This URL came from a bookmark, so it's safe to let it inherit the current
-    // document's principal.
-    mayInheritPrincipal = true;
-
-    aCallback({ postData: postData, url: shortcutURL,
-                mayInheritPrincipal: mayInheritPrincipal });
+    aCallback({ postData: postData, url: shortcutURL });
   }
 }
 
 function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType) {
   var dataStream = Cc["@mozilla.org/io/string-input-stream;1"].
                    createInstance(Ci.nsIStringInputStream);
   aStringData = aStringData.replace(/%s/g, aEncKeyword).replace(/%S/g, aKeyword);
   dataStream.data = aStringData;
@@ -5146,16 +5132,18 @@ function middleMousePaste(event) {
   let clipboard = readFromClipboard();
   if (!clipboard)
     return;
 
   // Strip embedded newlines and surrounding whitespace, to match the URL
   // bar's behavior (stripsurroundingwhitespace)
   clipboard = clipboard.replace(/\s*\n\s*/g, "");
 
+  clipboard = stripUnsafeProtocolOnPaste(clipboard);
+
   // if it's not the current tab, we don't need to do anything because the 
   // browser doesn't exist.
   let where = whereToOpenLink(event, true, false);
   let lastLocationChange;
   if (where == "current") {
     lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
   }
 
@@ -5173,24 +5161,42 @@ function middleMousePaste(event) {
       // Things may go wrong when adding url to session history,
       // but don't let that interfere with the loading of the url.
       Cu.reportError(ex);
     }
 
     if (where != "current" ||
         lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
       openUILink(data.url, event,
-                 { ignoreButton: true,
-                   disallowInheritPrincipal: !data.mayInheritPrincipal });
+                 { ignoreButton: true });
     }
   });
 
   event.stopPropagation();
 }
 
+function stripUnsafeProtocolOnPaste(pasteData) {
+  // Don't allow pasting in full URIs which inherit the security context.
+  const URI_INHERITS_SECURITY_CONTEXT = Ci.nsIProtocolHandler.URI_INHERITS_SECURITY_CONTEXT;
+  let pastedURI;
+  pasteData = pasteData.trim();
+  do {
+    if (pastedURI) {
+      pasteData = pastedURI.path.trim();
+    }
+    try {
+      pastedURI = makeURI(pasteData);
+    } catch (ex) {
+      break;
+    }
+  } while (Services.netutil.URIChainHasFlags(pastedURI, URI_INHERITS_SECURITY_CONTEXT));
+
+  return pasteData;
+}
+
 function handleDroppedLink(event, url, name)
 {
   let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
 
   getShortcutOrURIAndPostData(url, data => {
     if (data.url &&
         lastLocationChange == gBrowser.selectedBrowser.lastLocationChange)
       loadURI(data.url, null, data.postData, false);
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -304,21 +304,19 @@ skip-if = e10s # Bug ?????? - this test 
 [browser_keywordSearch.js]
 skip-if = e10s # Bug 921957 - remote webprogress doesn't supply originalURI attribute on the request object
 [browser_keywordSearch_postData.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (gBrowser.contentDocument.body.textContent)
 [browser_lastAccessedTab.js]
 skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bug 969405)
 [browser_locationBarCommand.js]
 skip-if = os == "linux" || e10s # Linux: Intermittent failures, bug 917535; e10s: Bug ?????? - Focus issues (There should be no focused element - Got [object XULElement], expected null)
-[browser_locationBarExternalLoad.js]
-skip-if = e10s # Bug ?????? - URL bar issues ("There should be no focused element - Got [object XULElement], expected null")
 [browser_menuButtonFitts.js]
 skip-if = os != "win" || e10s # The Fitts Law menu button is only supported on Windows (bug 969376); # Bug ?????? - URL bar issues ("There should be no focused element - Got [object XULElement], expected null")
-[browser_middleMouse_inherit.js]
+[browser_middleMouse_noJSPaste.js]
 skip-if = e10s # Bug 921952 - Content:Click event issues
 [browser_minimize.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (TypeError: gBrowser.docShell is null)
 [browser_mixedcontent_securityflags.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content ("cannot ipc non-cpow object")
 [browser_notification_tab_switching.js]
 skip-if = e10s # Bug ?????? - uncaught exception - Error: cannot ipc non-cpow object at chrome://mochitests/content/browser/browser/base/content/test/general/browser_notification_tab_switching.js:32
 [browser_offlineQuotaNotification.js]
@@ -342,16 +340,17 @@ skip-if = toolkit == "windows" || e10s #
 skip-if = e10s # Bug ?????? - test directly manipulates content (tries to get a popup element directly from content)
 [browser_printpreview.js]
 skip-if = e10s # Bug ?????? - timeout after logging "Error: Channel closing: too late to send/recv, messages will be lost"
 [browser_private_browsing_window.js]
 [browser_private_no_prompt.js]
 [browser_relatedTabs.js]
 [browser_removeTabsToTheEnd.js]
 skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
+[browser_removeUnsafeProtocolsFromURLBarPaste.js]
 [browser_sanitize-download-history.js]
 skip-if = true # bug 432425
 [browser_sanitize-passwordDisabledHosts.js]
 [browser_sanitize-sitepermissions.js]
 [browser_sanitize-timespans.js]
 [browser_sanitizeDialog.js]
 [browser_sanitizeDialog_treeView.js]
 skip-if = true  # disabled until the tree view is added
--- a/browser/base/content/test/general/browser_getshortcutoruri.js
+++ b/browser/base/content/test/general/browser_getshortcutoruri.js
@@ -6,20 +6,19 @@ function getPostDataString(aIS) {
             createInstance(Ci.nsIScriptableInputStream);
   sis.init(aIS);
   var dataLines = sis.read(aIS.available()).split("\n");
 
   // only want the last line
   return dataLines[dataLines.length-1];
 }
 
-function keywordResult(aURL, aPostData, aIsUnsafe) {
+function keywordResult(aURL, aPostData) {
   this.url = aURL;
   this.postData = aPostData;
-  this.isUnsafe = aIsUnsafe;
 }
 
 function keyWordData() {}
 keyWordData.prototype = {
   init: function(aKeyWord, aURL, aPostData, aSearchWord) {
     this.keyword = aKeyWord;
     this.uri = makeURI(aURL);
     this.postData = aPostData;
@@ -48,30 +47,30 @@ var testData = [
 
   [new bmKeywordData("bmpostget", "http://bmpostget/search1=%s", "search2=%s", "foo3"),
    new keywordResult("http://bmpostget/search1=foo3", "search2=foo3")],
 
   [new bmKeywordData("bmget-nosearch", "http://bmget-nosearch/", null, ""),
    new keywordResult("http://bmget-nosearch/", null)],
 
   [new searchKeywordData("searchget", "http://searchget/?search={searchTerms}", null, "foo4"),
-   new keywordResult("http://searchget/?search=foo4", null, true)],
+   new keywordResult("http://searchget/?search=foo4", null)],
 
   [new searchKeywordData("searchpost", "http://searchpost/", "search={searchTerms}", "foo5"),
-   new keywordResult("http://searchpost/", "search=foo5", true)],
+   new keywordResult("http://searchpost/", "search=foo5")],
 
   [new searchKeywordData("searchpostget", "http://searchpostget/?search1={searchTerms}", "search2={searchTerms}", "foo6"),
-   new keywordResult("http://searchpostget/?search1=foo6", "search2=foo6", true)],
+   new keywordResult("http://searchpostget/?search1=foo6", "search2=foo6")],
 
   // Bookmark keywords that don't take parameters should not be activated if a
   // parameter is passed (bug 420328).
   [new bmKeywordData("bmget-noparam", "http://bmget-noparam/", null, "foo7"),
-   new keywordResult(null, null, true)],
+   new keywordResult(null, null)],
   [new bmKeywordData("bmpost-noparam", "http://bmpost-noparam/", "not_a=param", "foo8"),
-   new keywordResult(null, null, true)],
+   new keywordResult(null, null)],
 
   // Test escaping (%s = escaped, %S = raw)
   // UTF-8 default
   [new bmKeywordData("bmget-escaping", "http://bmget/?esc=%s&raw=%S", null, "fo"),
    new keywordResult("http://bmget/?esc=fo%C3%A9&raw=fo", null)],
   // Explicitly-defined ISO-8859-1
   [new bmKeywordData("bmget-escaping2", "http://bmget/?esc=%s&raw=%S&mozcharset=ISO-8859-1", null, "fo"),
    new keywordResult("http://bmget/?esc=fo%E9&raw=fo", null)],
@@ -83,17 +82,17 @@ var testData = [
   // Explicitly-defined ISO-8859-1
   [new bmKeywordData("bmget-escaping2", "http://bmget/?esc=%s&raw=%S&mozcharset=ISO-8859-1", null, "+/@"),
    new keywordResult("http://bmget/?esc=%2B%2F%40&raw=+/@", null)],
 
   // Test using a non-bmKeywordData object, to test the behavior of
   // getShortcutOrURIAndPostData for non-keywords (setupKeywords only adds keywords for
   // bmKeywordData objects)
   [{keyword: "http://gavinsharp.com"},
-   new keywordResult(null, null, true)]
+   new keywordResult(null, null)]
 ];
 
 function test() {
   waitForExplicitFinish();
 
   setupKeywords();
 
   Task.spawn(function() {
@@ -104,17 +103,16 @@ function test() {
       if (data.searchWord)
         query += " " + data.searchWord;
       let returnedData = yield new Promise(
         resolve => getShortcutOrURIAndPostData(query, resolve));
       // null result.url means we should expect the same query we sent in
       let expected = result.url || query;
       is(returnedData.url, expected, "got correct URL for " + data.keyword);
       is(getPostDataString(returnedData.postData), result.postData, "got correct postData for " + data.keyword);
-      is(returnedData.mayInheritPrincipal, !result.isUnsafe, "got correct mayInheritPrincipal for " + data.keyword);
     }
     cleanupKeywords();
   }).then(finish);
 }
 
 var gBMFolder = null;
 var gAddedEngines = [];
 function setupKeywords() {
deleted file mode 100644
--- a/browser/base/content/test/general/browser_locationBarExternalLoad.js
+++ /dev/null
@@ -1,65 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-function test() {
-  waitForExplicitFinish();
-
-  nextTest();
-}
-
-let urls = [
-  "javascript:'foopy';",
-  "data:text/html,<body>hi"
-];
-
-function urlEnter(url) {
-  gURLBar.value = url;
-  gURLBar.focus();
-  EventUtils.synthesizeKey("VK_RETURN", {});
-}
-
-function urlClick(url) {
-  gURLBar.value = url;
-  gURLBar.focus();
-  let goButton = document.getElementById("urlbar-go-button");
-  EventUtils.synthesizeMouseAtCenter(goButton, {});
-}
-
-function nextTest() {
-  let url = urls.shift();
-  if (url) {
-    testURL(url, urlEnter, function () {
-      testURL(url, urlClick, nextTest);
-    });
-  }
-  else
-    finish();
-}
-
-function testURL(url, loadFunc, endFunc) {
-  let tab = gBrowser.selectedTab = gBrowser.addTab();
-  registerCleanupFunction(function () {
-    gBrowser.removeTab(tab);
-  });
-  addPageShowListener(function () {
-    let pagePrincipal = gBrowser.contentPrincipal;
-    loadFunc(url);
-
-    addPageShowListener(function () {
-      let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
-      is(fm.focusedElement, null, "should be no focused element");
-      is(fm.focusedWindow, gBrowser.contentWindow, "content window should be focused");
-
-      ok(!gBrowser.contentPrincipal.equals(pagePrincipal),
-         "load of " + url + " by " + loadFunc.name + " should produce a page with a different principal");
-      endFunc();
-    });
-  });
-}
-
-function addPageShowListener(func) {
-  gBrowser.selectedBrowser.addEventListener("pageshow", function loadListener() {
-    gBrowser.selectedBrowser.removeEventListener("pageshow", loadListener, false);
-    func();
-  });
-}
rename from browser/base/content/test/general/browser_middleMouse_inherit.js
rename to browser/base/content/test/general/browser_middleMouse_noJSPaste.js
--- a/browser/base/content/test/general/browser_middleMouse_inherit.js
+++ b/browser/base/content/test/general/browser_middleMouse_noJSPaste.js
@@ -15,17 +15,17 @@ function test() {
     Services.prefs.clearUserPref(autoScrollPref);
     gBrowser.removeTab(tab);
   });
 
   addPageShowListener(function () {
     let pagePrincipal = gBrowser.contentPrincipal;
 
     // copy javascript URI to the clipboard
-    let url = "javascript:1+1";
+    let url = "javascript:http://www.example.com/";
     waitForClipboard(url,
       function() {
         Components.classes["@mozilla.org/widget/clipboardhelper;1"]
                   .getService(Components.interfaces.nsIClipboardHelper)
                   .copyString(url, document);
       },
       function () {
         // Middle click on the content area
@@ -34,19 +34,17 @@ function test() {
       },
       function() {
         ok(false, "Failed to copy URL to the clipboard");
         finish();
       }
     );
 
     addPageShowListener(function () {
-      is(gBrowser.currentURI.spec, url, "url loaded by middle click");
-      ok(!gBrowser.contentPrincipal.equals(pagePrincipal),
-         "middle click load of " + url + " should produce a page with a different principal");
+      is(gBrowser.currentURI.spec, url.replace(/^javascript:/, ""), "url loaded by middle click doesn't include JS");
       finish();
     });
   });
 }
 
 function addPageShowListener(func) {
   gBrowser.selectedBrowser.addEventListener("pageshow", function loadListener() {
     gBrowser.selectedBrowser.removeEventListener("pageshow", loadListener, false);
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_removeUnsafeProtocolsFromURLBarPaste.js
@@ -0,0 +1,49 @@
+function test() {
+  waitForExplicitFinish();
+  testNext();
+}
+
+let pairs = [
+  ["javascript:", ""],
+  ["javascript:1+1", "1+1"],
+  ["javascript:document.domain", "document.domain"],
+  ["data:text/html,<body>hi</body>", "text/html,<body>hi</body>"],
+  // Nested things get confusing because some things don't parse as URIs:
+  ["javascript:javascript:alert('hi!')", "alert('hi!')"],
+  ["data:data:text/html,<body>hi</body>", "text/html,<body>hi</body>"],
+  ["javascript:data:javascript:alert('hi!')", "data:javascript:alert('hi!')"],
+  ["javascript:data:text/html,javascript:alert('hi!')", "text/html,javascript:alert('hi!')"],
+  ["data:data:text/html,javascript:alert('hi!')", "text/html,javascript:alert('hi!')"],
+];
+
+let clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
+
+function paste(input, cb) {
+  waitForClipboard(input, function() {
+    clipboardHelper.copyString(input);
+  }, function() {
+    document.commandDispatcher.getControllerForCommand("cmd_paste").doCommand("cmd_paste");
+    cb();
+  }, function() {
+    ok(false, "Failed to copy string '" + input + "' to clipboard");
+    cb();
+  });
+}
+
+function testNext() {
+  gURLBar.value = '';
+  if (!pairs.length) {
+    finish();
+    return;
+  }
+
+  let [inputValue, expectedURL] = pairs.shift();
+
+  gURLBar.focus();
+  paste(inputValue, function() {
+    is(gURLBar.value, expectedURL, "entering '" + inputValue + "' strips relevant bits.");
+
+    setTimeout(testNext, 0);
+  });
+}
+
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -57,16 +57,17 @@
         this.doubleClickSelectsAll = this._prefs.getBoolPref("doubleClickSelectsAll");
         this.completeDefaultIndex = this._prefs.getBoolPref("autoFill");
         this.timeout = this._prefs.getIntPref("delay");
         this._formattingEnabled = this._prefs.getBoolPref("formatting.enabled");
         this._mayTrimURLs = this._prefs.getBoolPref("trimURLs");
         this._ignoreNextSelect = false;
 
         this.inputField.controllers.insertControllerAt(0, this._copyCutController);
+        this.inputField.addEventListener("paste", this, false);
         this.inputField.addEventListener("mousedown", this, false);
         this.inputField.addEventListener("mousemove", this, false);
         this.inputField.addEventListener("mouseout", this, false);
         this.inputField.addEventListener("overflow", this, false);
         this.inputField.addEventListener("underflow", this, false);
 
         try {
           if (this._prefs.getBoolPref("unifiedcomplete")) {
@@ -106,16 +107,17 @@
           cxmenu.insertBefore(pasteAndGo, insertLocation.nextSibling);
         }
       ]]></constructor>
 
       <destructor><![CDATA[
         this._prefs.removeObserver("", this);
         this._prefs = null;
         this.inputField.controllers.removeController(this._copyCutController);
+        this.inputField.removeEventListener("paste", this, false);
         this.inputField.removeEventListener("mousedown", this, false);
         this.inputField.removeEventListener("mousemove", this, false);
         this.inputField.removeEventListener("mouseout", this, false);
         this.inputField.removeEventListener("overflow", this, false);
         this.inputField.removeEventListener("underflow", this, false);
       ]]></destructor>
 
       <field name="_value">""</field>
@@ -258,17 +260,16 @@
 
       <method name="handleCommand">
         <parameter name="aTriggeringEvent"/>
         <body><![CDATA[
           if (aTriggeringEvent instanceof MouseEvent && aTriggeringEvent.button == 2)
             return; // Do nothing for right clicks
 
           var url = this.value;
-          var mayInheritPrincipal = false;
           var postData = null;
 
           var action = this._parseActionUrl(url);
           let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
 
           let matchLastLocationChange = true;
           if (action) {
             url = action.param;
@@ -281,17 +282,17 @@
                   gBrowser.removeTab(prevTab);
               }
               return;
             }
             continueOperation.call(this);
           }
           else {
             this._canonizeURL(aTriggeringEvent, response => {
-              [url, postData, mayInheritPrincipal] = response;
+              [url, postData] = response;
               if (url) {
                 matchLastLocationChange = (lastLocationChange ==
                                            gBrowser.selectedBrowser.lastLocationChange);
                 continueOperation.call(this);
               }
             });
           }
 
@@ -306,22 +307,16 @@
               // but don't let that interfere with the loading of the url.
               Cu.reportError(ex);
             }
 
             function loadCurrent() {
               let webnav = Ci.nsIWebNavigation;
               let flags = webnav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
                           webnav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
-              // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from
-              // inheriting the currently loaded document's principal, unless this
-              // URL is marked as safe to inherit (e.g. came from a bookmark
-              // keyword).
-              if (!mayInheritPrincipal)
-                flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
               gBrowser.loadURIWithFlags(url, flags, null, null, postData);
             }
 
             // Focus the content area before triggering loads, since if the load
             // occurs in a new tab, we want focus to be restored to the content
             // area when the current tab is re-selected.
             gBrowser.selectedBrowser.focus();
 
@@ -422,17 +417,17 @@
                 url = url + suffix;
               }
 
               url = "http://www." + url;
             }
           }
 
           getShortcutOrURIAndPostData(url, data => {
-            aCallback([data.url, data.postData, data.mayInheritPrincipal]);
+            aCallback([data.url, data.postData]);
           });
         ]]></body>
       </method>
 
       <field name="_contentIsCropped">false</field>
 
       <method name="_initURLTooltip">
         <body><![CDATA[
@@ -619,16 +614,46 @@
           }
         ]]></body>
       </method>
 
       <method name="handleEvent">
         <parameter name="aEvent"/>
         <body><![CDATA[
           switch (aEvent.type) {
+            case "paste":
+              let originalPasteData = aEvent.clipboardData.getData("text/plain");
+              if (!originalPasteData) {
+                return;
+              }
+
+              let oldValue = this.inputField.value;
+              let oldStart = oldValue.substring(0, this.inputField.selectionStart);
+              // If there is already non-whitespace content in the URL bar
+              // preceding the pasted content, it's not necessary to check
+              // protocols used by the pasted content:
+              if (oldStart.trim()) {
+                return;
+              }
+              let oldEnd = oldValue.substring(this.inputField.selectionEnd);
+
+              let pasteData = stripUnsafeProtocolOnPaste(originalPasteData);
+              if (originalPasteData != pasteData) {
+                // Unfortunately we're not allowed to set the bits being pasted
+                // so cancel this event:
+                aEvent.preventDefault();
+                aEvent.stopPropagation();
+
+                this.inputField.value = oldStart + pasteData + oldEnd;
+                // Fix up cursor/selection:
+                let newCursorPos = oldStart.length + pasteData.length;
+                this.inputField.selectionStart = newCursorPos;
+                this.inputField.selectionEnd = newCursorPos;
+              }
+              break;
             case "mousedown":
               if (this.doubleClickSelectsAll &&
                   aEvent.button == 0 && aEvent.detail == 2) {
                 this.editor.selectAll();
                 aEvent.preventDefault();
               }
               break;
             case "mousemove":
--- a/browser/devtools/shared/autocomplete-popup.js
+++ b/browser/devtools/shared/autocomplete-popup.js
@@ -150,17 +150,18 @@ AutocompletePopup.prototype = {
   {
     this._panel.hidePopup();
   },
 
   /**
    * Check if the autocomplete popup is open.
    */
   get isOpen() {
-    return this._panel.state == "open" || this._panel.state == "showing";
+    return this._panel &&
+           (this._panel.state == "open" || this._panel.state == "showing");
   },
 
   /**
    * Destroy the object instance. Please note that the panel DOM elements remain
    * in the DOM, because they might still be in use by other instances of the
    * same code. It is the responsability of the client code to perform DOM
    * cleanup.
    */
--- a/browser/devtools/shared/widgets/Tooltip.js
+++ b/browser/devtools/shared/widgets/Tooltip.js
@@ -252,17 +252,19 @@ Tooltip.prototype = {
    * Hide the tooltip
    */
   hide: function() {
     this.panel.hidden = true;
     this.panel.hidePopup();
   },
 
   isShown: function() {
-    return this.panel.state !== "closed" && this.panel.state !== "hiding";
+    return this.panel &&
+           this.panel.state !== "closed" &&
+           this.panel.state !== "hiding";
   },
 
   setSize: function(width, height) {
     this.panel.sizeTo(width, height);
   },
 
   /**
    * Empty the tooltip's content
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -162,17 +162,17 @@ ElementStyle.prototype = {
 
   destroy: function() {
     this.dummyElement = null;
     this.dummyElementPromise.then(dummyElement => {
       if (dummyElement.parentNode) {
         dummyElement.parentNode.removeChild(dummyElement);
       }
       this.dummyElementPromise = null;
-    });
+    }, console.error);
   },
 
   /**
    * Called by the Rule object when it has been changed through the
    * setProperty* methods.
    */
   _changed: function() {
     if (this.onChanged) {
@@ -506,17 +506,17 @@ Rule.prototype = {
     return this.domRule.getOriginalLocation().then(({href, line}) => {
       let sourceStrings = {
         full: href + ":" + line,
         short: CssLogic.shortSource({href: href}) + ":" + line
       };
 
       this._originalSourceStrings = sourceStrings;
       return sourceStrings;
-    });
+    }, console.error);
   },
 
   /**
    * Returns true if the rule matches the creation options
    * specified.
    *
    * @param {object} aOptions
    *        Creation options.  See the Rule constructor for documentation.
@@ -1390,29 +1390,30 @@ CssRuleView.prototype = {
     for (let rule of this._elementStyle.rules) {
       if (rule.editor) {
         rule.editor.updateSourceLink();
       }
     }
   },
 
   destroy: function() {
+    this.isDestroyed = true;
     this.clear();
 
     gDummyPromise = null;
 
     this._prefObserver.off(PREF_ORIG_SOURCES, this._onSourcePrefChanged);
     this._prefObserver.off(PREF_UA_STYLES, this._handlePrefChange);
     this._prefObserver.off(PREF_DEFAULT_COLOR_UNIT, this._handlePrefChange);
     this._prefObserver.destroy();
 
     this.element.removeEventListener("copy", this._onCopy);
-    delete this._onCopy;
-
-    delete this._outputParser;
+    this._onCopy = null;
+
+    this._outputParser = null;
 
     // Remove context menu
     if (this._contextmenu) {
       // Destroy the Select All menuitem.
       this.menuitemSelectAll.removeEventListener("command", this._onSelectAll);
       this.menuitemSelectAll = null;
 
       // Destroy the Copy menuitem.
@@ -1853,17 +1854,17 @@ RuleEditor.prototype = {
       sourceLabel.setAttribute("value", this.rule.title);
     }
 
     let showOrig = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
     if (showOrig && !this.rule.isSystem && this.rule.domRule.type != ELEMENT_STYLE) {
       this.rule.getOriginalSourceStrings().then((strings) => {
         sourceLabel.setAttribute("value", strings.short);
         sourceLabel.setAttribute("tooltiptext", strings.full);
-      })
+      }, console.error);
     }
   },
 
   /**
    * Update the rule editor with the contents of the rule.
    */
   populate: function() {
     // Clear out existing viewers.
@@ -2314,16 +2315,20 @@ TextPropertyEditor.prototype = {
 
     return uri;
   },
 
   /**
    * Populate the span based on changes to the TextProperty.
    */
   update: function() {
+    if (this.ruleEditor.ruleView.isDestroyed) {
+      return;
+    }
+
     if (this.prop.enabled) {
       this.enable.style.removeProperty("visibility");
       this.enable.setAttribute("checked", "");
     } else {
       this.enable.style.visibility = "visible";
       this.enable.removeAttribute("checked");
     }
 
--- a/browser/devtools/styleinspector/test/browser_ruleview_edit-property-increments.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_edit-property-increments.js
@@ -18,28 +18,32 @@ let test = asyncTest(function*() {
   yield testVariousUnitIncrements(view);
   yield testHexIncrements(view);
   yield testRgbIncrements(view);
   yield testShorthandIncrements(view);
   yield testOddCases(view);
 });
 
 function createDocument() {
-  content.document.body.innerHTML = '<div id="test" style="' +
-                           'margin-top:0px;' +
-                           'padding-top: 0px;' +
-                           'color:#000000;' +
-                           'background-color: #000000;" >'+
-                       '</div>';
+  content.document.body.innerHTML = '' +
+    '<style>' +
+    '  #test {' +
+    '    margin-top:0px;' +
+    '    padding-top: 0px;' +
+    '    color:#000000;' +
+    '    background-color: #000000;' +
+    '  }' +
+    '</style>' +
+    '<div id="test"></div>';
 }
 
 function* testMarginIncrements(view) {
   info("Testing keyboard increments on the margin property");
 
-  let idRuleEditor = getRuleViewRuleEditor(view, 0);
+  let idRuleEditor = getRuleViewRuleEditor(view, 1);
   let marginPropEditor = idRuleEditor.rule.textProps[0].editor;
 
   yield runIncrementTest(marginPropEditor, view, {
     1: {alt: true, start: "0px", end: "0.1px", selectAll: true},
     2: {start: "0px", end: "1px", selectAll: true},
     3: {shift: true, start: "0px", end: "10px", selectAll: true},
     4: {down: true, alt: true, start: "0.1px", end: "0px", selectAll: true},
     5: {down: true, start: "0px", end: "-1px", selectAll: true},
@@ -47,17 +51,17 @@ function* testMarginIncrements(view) {
     7: {pageUp: true, shift: true, start: "0px", end: "100px", selectAll: true},
     8: {pageDown: true, shift: true, start: "0px", end: "-100px", selectAll: true}
   });
 }
 
 function* testVariousUnitIncrements(view) {
   info("Testing keyboard increments on values with various units");
 
-  let idRuleEditor = getRuleViewRuleEditor(view, 0);
+  let idRuleEditor = getRuleViewRuleEditor(view, 1);
   let paddingPropEditor = idRuleEditor.rule.textProps[1].editor;
 
   yield runIncrementTest(paddingPropEditor, view, {
     1: {start: "0px", end: "1px", selectAll: true},
     2: {start: "0pt", end: "1pt", selectAll: true},
     3: {start: "0pc", end: "1pc", selectAll: true},
     4: {start: "0em", end: "1em", selectAll: true},
     5: {start: "0%",  end: "1%",  selectAll: true},
@@ -66,65 +70,65 @@ function* testVariousUnitIncrements(view
     8: {start: "0mm", end: "1mm", selectAll: true},
     9: {start: "0ex", end: "1ex", selectAll: true}
   });
 };
 
 function* testHexIncrements(view) {
   info("Testing keyboard increments with hex colors");
 
-  let idRuleEditor = getRuleViewRuleEditor(view, 0);
+  let idRuleEditor = getRuleViewRuleEditor(view, 1);
   let hexColorPropEditor = idRuleEditor.rule.textProps[2].editor;
 
   yield runIncrementTest(hexColorPropEditor, view, {
     1: {start: "#CCCCCC", end: "#CDCDCD", selectAll: true},
     2: {shift: true, start: "#CCCCCC", end: "#DCDCDC", selectAll: true},
     3: {start: "#CCCCCC", end: "#CDCCCC", selection: [1,3]},
     4: {shift: true, start: "#CCCCCC", end: "#DCCCCC", selection: [1,3]},
     5: {start: "#FFFFFF", end: "#FFFFFF", selectAll: true},
     6: {down: true, shift: true, start: "#000000", end: "#000000", selectAll: true}
   });
 };
 
 function* testRgbIncrements(view) {
   info("Testing keyboard increments with rgb colors");
 
-  let idRuleEditor = getRuleViewRuleEditor(view, 0);
+  let idRuleEditor = getRuleViewRuleEditor(view, 1);
   let rgbColorPropEditor = idRuleEditor.rule.textProps[3].editor;
 
   yield runIncrementTest(rgbColorPropEditor, view, {
     1: {start: "rgb(0,0,0)", end: "rgb(0,1,0)", selection: [6,7]},
     2: {shift: true, start: "rgb(0,0,0)", end: "rgb(0,10,0)", selection: [6,7]},
     3: {start: "rgb(0,255,0)", end: "rgb(0,255,0)", selection: [6,9]},
     4: {shift: true, start: "rgb(0,250,0)", end: "rgb(0,255,0)", selection: [6,9]},
     5: {down: true, start: "rgb(0,0,0)", end: "rgb(0,0,0)", selection: [6,7]},
     6: {down: true, shift: true, start: "rgb(0,5,0)", end: "rgb(0,0,0)", selection: [6,7]}
   });
 };
 
 function* testShorthandIncrements(view) {
   info("Testing keyboard increments within shorthand values");
 
-  let idRuleEditor = getRuleViewRuleEditor(view, 0);
+  let idRuleEditor = getRuleViewRuleEditor(view, 1);
   let paddingPropEditor = idRuleEditor.rule.textProps[1].editor;
 
   yield runIncrementTest(paddingPropEditor, view, {
     1: {start: "0px 0px 0px 0px", end: "0px 1px 0px 0px", selection: [4,7]},
     2: {shift: true, start: "0px 0px 0px 0px", end: "0px 10px 0px 0px", selection: [4,7]},
     3: {start: "0px 0px 0px 0px", end: "1px 0px 0px 0px", selectAll: true},
     4: {shift: true, start: "0px 0px 0px 0px", end: "10px 0px 0px 0px", selectAll: true},
     5: {down: true, start: "0px 0px 0px 0px", end: "0px 0px -1px 0px", selection: [8,11]},
     6: {down: true, shift: true, start: "0px 0px 0px 0px", end: "-10px 0px 0px 0px", selectAll: true}
   });
 };
 
 function* testOddCases(view) {
   info("Testing some more odd cases");
 
-  let idRuleEditor = getRuleViewRuleEditor(view, 0);
+  let idRuleEditor = getRuleViewRuleEditor(view, 1);
   let marginPropEditor = idRuleEditor.rule.textProps[0].editor;
 
   yield runIncrementTest(marginPropEditor, view, {
     1: {start: "98.7%", end: "99.7%", selection: [3,3]},
     2: {alt: true, start: "98.7%", end: "98.8%", selection: [3,3]},
     3: {start: "0", end: "1"},
     4: {down: true, start: "0", end: "-1"},
     5: {start: "'a=-1'", end: "'a=0'", selection: [4,4]},
@@ -139,38 +143,36 @@ function* testOddCases(view) {
     14: {alt: true, start: "url('test--0.1.png')", end: "url('test-0.png')", selection: [10,14]}
   });
 };
 
 function* runIncrementTest(propertyEditor, view, tests) {
   let editor = yield focusEditableField(propertyEditor.valueSpan);
 
   for(let test in tests) {
-    yield testIncrement(editor, tests[test], view);
+    yield testIncrement(editor, tests[test], view, propertyEditor);
   }
-
-  // Once properties have been set, wait for the inspector to update
-  yield view.inspector.once("inspector-updated");
 }
 
-function* testIncrement(editor, options, view) {
+function* testIncrement(editor, options, view, {ruleEditor}) {
   editor.input.value = options.start;
   let input = editor.input;
 
   if (options.selectAll) {
     input.select();
   } else if (options.selection) {
     input.setSelectionRange(options.selection[0], options.selection[1]);
   }
 
   is(input.value, options.start, "Value initialized at " + options.start);
 
+  let onModifications = ruleEditor.rule._applyingModifications;
   let onKeyUp = once(input, "keyup");
-
   let key;
   key = options.down ? "VK_DOWN" : "VK_UP";
   key = options.pageDown ? "VK_PAGE_DOWN" : options.pageUp ? "VK_PAGE_UP" : key;
-  EventUtils.synthesizeKey(key, {altKey: options.alt, shiftKey: options.shift}, view.doc.defaultView);
-
+  EventUtils.synthesizeKey(key, {altKey: options.alt, shiftKey: options.shift},
+    view.doc.defaultView);
   yield onKeyUp;
-  input = editor.input;
-  is(input.value, options.end, "Value changed to " + options.end);
+  yield onModifications;
+
+  is(editor.input.value, options.end, "Value changed to " + options.end);
 }
--- a/browser/themes/shared/devtools/computedview.css
+++ b/browser/themes/shared/devtools/computedview.css
@@ -142,18 +142,19 @@ body {
 .onlyuserstyles {
   cursor: pointer;
 }
 
 .legendKey {
   margin: 0 5px;
 }
 
-.devtools-toolbar {
+#root .devtools-toolbar {
   width: 100%;
+  border-bottom-width: 0;
 }
 
 .link {
   padding: 0 3px;
   cursor: pointer;
   float: right;
 }
 
--- a/browser/themes/shared/devtools/toolbars.inc.css
+++ b/browser/themes/shared/devtools/toolbars.inc.css
@@ -9,18 +9,19 @@
 %define solidSeparatorDark linear-gradient(#2d5b7d, #2d5b7d)
 %define solidSeparatorLight linear-gradient(#aaa, #aaa)
 
 /* Toolbars */
 .devtools-toolbar,
 .devtools-sidebar-tabs > tabs {
   -moz-appearance: none;
   padding: 0;
+  border-width: 0;
   border-bottom-width: 1px;
-  border-bottom-style: solid;
+  border-style: solid;
   height: 24px;
   line-height: 24px;
   box-sizing: border-box;
 }
 
 .devtools-toolbar {
   padding: 0 3px;
 }
@@ -28,16 +29,19 @@
 .devtools-toolbar checkbox {
   margin: 0 2px;
   padding: 0;
 }
 .devtools-toolbar checkbox .checkbox-check {
   margin: 0;
   padding: 0;
 }
+.devtools-toolbar checkbox .checkbox-label-box {
+  border: none !important; /* overrides .checkbox-label-box from checkbox.css */
+}
 .devtools-toolbar checkbox .checkbox-label-box .checkbox-label {
   margin: 0 6px !important; /* overrides .checkbox-label from checkbox.css */
   padding: 0;
 }
 
 /* Toolbar buttons */
 .devtools-menulist,
 .devtools-toolbarbutton {
--- a/mobile/android/base/db/BrowserContract.java
+++ b/mobile/android/base/db/BrowserContract.java
@@ -28,19 +28,16 @@ public class BrowserContract {
     public static final Uri HOME_AUTHORITY_URI = Uri.parse("content://" + HOME_AUTHORITY);
 
     public static final String PROFILES_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".profiles";
     public static final Uri PROFILES_AUTHORITY_URI = Uri.parse("content://" + PROFILES_AUTHORITY);
 
     public static final String READING_LIST_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.readinglist";
     public static final Uri READING_LIST_AUTHORITY_URI = Uri.parse("content://" + READING_LIST_AUTHORITY);
 
-    public static final String SEARCH_HISTORY_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.searchhistory";
-    public static final Uri SEARCH_HISTORY_AUTHORITY_URI = Uri.parse("content://" + SEARCH_HISTORY_AUTHORITY);
-
     public static final String PARAM_PROFILE = "profile";
     public static final String PARAM_PROFILE_PATH = "profilePath";
     public static final String PARAM_LIMIT = "limit";
     public static final String PARAM_IS_SYNC = "sync";
     public static final String PARAM_SHOW_DELETED = "show_deleted";
     public static final String PARAM_IS_TEST = "test";
     public static final String PARAM_INSERT_IF_NEEDED = "insert_if_needed";
     public static final String PARAM_INCREMENT_VISITS = "increment_visits";
@@ -432,25 +429,14 @@ public class BrowserContract {
         public static final String BOOKMARK_ID = "bookmark_id";
         public static final String HISTORY_ID = "history_id";
         public static final String DISPLAY = "display";
 
         public static final String TYPE = "type";
     }
 
     @RobocopTarget
-    public static final class SearchHistory implements CommonColumns, HistoryColumns {
-        private SearchHistory() {}
-
-        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/searchhistory";
-        public static final String QUERY = "query";
-        public static final String TABLE_NAME = "searchhistory";
-
-        public static final Uri CONTENT_URI = Uri.withAppendedPath(SEARCH_HISTORY_AUTHORITY_URI, "searchhistory");
-    }
-
-    @RobocopTarget
     public static final class SuggestedSites implements CommonColumns, URLColumns {
         private SuggestedSites() {}
 
         public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "suggestedsites");
     }
 }
--- a/mobile/android/base/db/BrowserDatabaseHelper.java
+++ b/mobile/android/base/db/BrowserDatabaseHelper.java
@@ -11,17 +11,16 @@ import java.util.List;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserContract.FaviconColumns;
 import org.mozilla.gecko.db.BrowserContract.Favicons;
 import org.mozilla.gecko.db.BrowserContract.History;
 import org.mozilla.gecko.db.BrowserContract.Obsolete;
 import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
-import org.mozilla.gecko.db.BrowserContract.SearchHistory;
 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
 import org.mozilla.gecko.sync.Utils;
 
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.SQLException;
@@ -30,17 +29,17 @@ import android.database.sqlite.SQLiteOpe
 import android.net.Uri;
 import android.os.Build;
 import android.util.Log;
 
 
 final class BrowserDatabaseHelper extends SQLiteOpenHelper {
 
     private static final String LOGTAG = "GeckoBrowserDBHelper";
-    public static final int DATABASE_VERSION = 19;
+    public static final int DATABASE_VERSION = 18;
     public static final String DATABASE_NAME = "browser.db";
 
     final protected Context mContext;
 
     static final String TABLE_BOOKMARKS = Bookmarks.TABLE_NAME;
     static final String TABLE_HISTORY = History.TABLE_NAME;
     static final String TABLE_FAVICONS = Favicons.TABLE_NAME;
     static final String TABLE_THUMBNAILS = Thumbnails.TABLE_NAME;
@@ -745,30 +744,16 @@ final class BrowserDatabaseHelper extend
         createHistoryWithFaviconsView(db);
         createCombinedViewOn16(db);
 
         createOrUpdateSpecialFolder(db, Bookmarks.PLACES_FOLDER_GUID,
             R.string.bookmarks_folder_places, 0);
 
         createOrUpdateAllSpecialFolders(db);
         createReadingListTable(db);
-        createSearchHistoryTable(db);
-    }
-
-    private void createSearchHistoryTable(SQLiteDatabase db) {
-        debug("Creating " + SearchHistory.TABLE_NAME + " table");
-
-        db.execSQL("CREATE TABLE " + SearchHistory.TABLE_NAME + "(" +
-                    SearchHistory._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
-                    SearchHistory.QUERY + " TEXT UNIQUE NOT NULL, " +
-                    SearchHistory.DATE_LAST_VISITED + " INTEGER, " +
-                    SearchHistory.VISITS + " INTEGER ) ");
-
-        db.execSQL("CREATE INDEX idx_search_history_last_visited ON " +
-                SearchHistory.TABLE_NAME + "(" + SearchHistory.DATE_LAST_VISITED + ")");
     }
 
     private void createReadingListTable(SQLiteDatabase db) {
         debug("Creating " + TABLE_READING_LIST + " table");
 
         db.execSQL("CREATE TABLE " + TABLE_READING_LIST + "(" +
                     ReadingListItems._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                     ReadingListItems.URL + " TEXT NOT NULL, " +
@@ -1388,20 +1373,16 @@ final class BrowserDatabaseHelper extend
         } finally {
             if (cursor != null) {
                 cursor.close();
             }
             db.endTransaction();
         }
     }
 
-    private void upgradeDatabaseFrom18to19(SQLiteDatabase db) {
-        createSearchHistoryTable(db);
-    }
-
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
         debug("Upgrading browser.db: " + db.getPath() + " from " +
                 oldVersion + " to " + newVersion);
 
         // We have to do incremental upgrades until we reach the current
         // database schema version.
         for (int v = oldVersion + 1; v <= newVersion; v++) {
@@ -1468,20 +1449,16 @@ final class BrowserDatabaseHelper extend
 
                 case 17:
                     upgradeDatabaseFrom16to17(db);
                     break;
 
                 case 18:
                     upgradeDatabaseFrom17to18(db);
                     break;
-
-                case 19:
-                    upgradeDatabaseFrom18to19(db);
-                    break;
             }
         }
 
         // If an upgrade after 12->13 fails, the entire upgrade is rolled
         // back, but we can't undo the deletion of favicon_urls.db if we
         // delete this in step 13; therefore, we wait until all steps are
         // complete before removing it.
         if (oldVersion < 13 && newVersion >= 13
@@ -1584,9 +1561,9 @@ final class BrowserDatabaseHelper extend
                 bookmark.put(Bookmarks.TYPE, Bookmarks.TYPE_BOOKMARK);
             } else {
                 bookmark.put(Bookmarks.TYPE, Bookmarks.TYPE_FOLDER);
             }
 
             bookmark.remove("folder");
         }
     }
-}
+}
\ No newline at end of file
deleted file mode 100644
--- a/mobile/android/base/db/SearchHistoryProvider.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/* 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/. */
-
-package org.mozilla.gecko.db;
-
-import org.mozilla.gecko.db.BrowserContract.SearchHistory;
-
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.text.TextUtils;
-
-public class SearchHistoryProvider extends SharedBrowserDatabaseProvider {
-
-    /**
-     * Collapse whitespace.
-     */
-    private String stripWhitespace(String query) {
-        if (TextUtils.isEmpty(query)) {
-            return "";
-        }
-
-        // Collapse whitespace
-        return query.trim().replaceAll("\\s+", " ");
-    }
-
-
-    @Override
-    public Uri insertInTransaction(Uri uri, ContentValues cv) {
-        final String query = stripWhitespace(cv.getAsString(SearchHistory.QUERY));
-
-        // We don't support inserting empty search queries.
-        if (TextUtils.isEmpty(query)) {
-            return null;
-        }
-
-        final SQLiteDatabase db = getWritableDatabase(uri);
-
-        /*
-         * FIRST: Try incrementing the VISITS counter and updating the DATE_LAST_VISITED.
-         */
-        final String sql = "UPDATE " + SearchHistory.TABLE_NAME + " SET " +
-                SearchHistory.VISITS + " = " + SearchHistory.VISITS + " + 1, " +
-                SearchHistory.DATE_LAST_VISITED + " = " + System.currentTimeMillis() +
-                " WHERE " + SearchHistory.QUERY + " = ?";
-        final Cursor c = db.rawQuery(sql, new String[] { query });
-
-        try {
-            if (c.getCount() > 1) {
-                // There is a UNIQUE constraint on the QUERY column,
-                // so there should only be one match.
-                return null;
-            }
-            if (c.moveToFirst()) {
-                return ContentUris
-                    .withAppendedId(uri, c.getInt(c.getColumnIndex(SearchHistory._ID)));
-            }
-        } finally {
-            c.close();
-        }
-
-        /*
-         * SECOND: If the update failed, then insert a new record.
-         */
-        cv.put(SearchHistory.QUERY, query);
-        cv.put(SearchHistory.VISITS, 1);
-        cv.put(SearchHistory.DATE_LAST_VISITED, System.currentTimeMillis());
-
-        long id = db.insert(SearchHistory.TABLE_NAME, null, cv);
-
-        if (id < 0) {
-            return null;
-        }
-
-        return ContentUris.withAppendedId(uri, id);
-    }
-
-    @Override
-    public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
-        return getWritableDatabase(uri)
-                .delete(SearchHistory.TABLE_NAME, selection, selectionArgs);
-    }
-
-    /**
-     * Since we are managing counts and the full-text db, an update
-     * could mangle the internal state. So we disable it.
-     */
-    @Override
-    public int updateInTransaction(Uri uri, ContentValues values, String selection,
-            String[] selectionArgs) {
-        throw new UnsupportedOperationException(
-                "This content provider does not support updating items");
-    }
-
-    @Override
-    public Cursor query(Uri uri, String[] projection, String selection,
-            String[] selectionArgs, String sortOrder) {
-        String groupBy = null;
-        String having = null;
-        return getReadableDatabase(uri)
-                .query(SearchHistory.TABLE_NAME, projection, selection, selectionArgs,
-                        groupBy, having, sortOrder);
-    }
-
-    @Override
-    public String getType(Uri uri) {
-        return SearchHistory.CONTENT_TYPE;
-    }
-}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -145,17 +145,16 @@ gbjar.sources += [
     'db/DBUtils.java',
     'db/FormHistoryProvider.java',
     'db/HomeProvider.java',
     'db/LocalBrowserDB.java',
     'db/PasswordsProvider.java',
     'db/PerProfileDatabaseProvider.java',
     'db/PerProfileDatabases.java',
     'db/ReadingListProvider.java',
-    'db/SearchHistoryProvider.java',
     'db/SharedBrowserDatabaseProvider.java',
     'db/SQLiteBridgeContentProvider.java',
     'db/SuggestedSites.java',
     'db/TabsProvider.java',
     'db/TopSitesCursorWrapper.java',
     'distribution/Distribution.java',
     'DoorHangerPopup.java',
     'DynamicToolbar.java',
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -54,17 +54,17 @@ skip-if = android_version == "10" || pro
 [testLinkContextMenu]
 # [testHomeListsProvider] # see bug 952310
 [testHomeProvider]
 [testLoad]
 [testMailToContextMenu]
 # [testMasterPassword] disabled for being finicky, see bug 1033013
 # disabled on 2.3; bug 979603
 # disabled on 4.0; bug 1006242
-skip-if = android_version == "10" || android_version == "15"
+# skip-if = android_version == "10" || android_version == "15"
 [testNewTab]
 # disabled on 2.3; bug 995696
 skip-if = android_version == "10"
 [testOverscroll]
 # disabled on 2.3; bug 836818
 skip-if = android_version == "10"
 [testPanCorrectness]
 # disabled on x86 only; bug 927476
@@ -75,17 +75,16 @@ skip-if = processor == "x86"
 [testPictureLinkContextMenu]
 [testPrefsObserver]
 [testPrivateBrowsing]
 [testPromptGridInput]
 # disabled on x86 only; bug 957185
 skip-if = processor == "x86"
 # [testReaderMode] # see bug 913254, 936224
 [testReadingListProvider]
-[testSearchHistoryProvider]
 [testSearchSuggestions]
 # disabled on x86; bug 907768
 skip-if = processor == "x86"
 [testSessionOOMSave]
 # disabled on x86 and 2.3; bug 945395
 skip-if = android_version == "10" || processor == "x86"
 [testSessionOOMRestore]
 # disabled on Android 2.3; bug 979600
deleted file mode 100644
--- a/mobile/android/base/tests/testSearchHistoryProvider.java
+++ /dev/null
@@ -1,269 +0,0 @@
-/* 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/. */
-
-package org.mozilla.gecko.tests;
-
-import java.util.concurrent.Callable;
-
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.SearchHistory;
-import org.mozilla.gecko.db.SearchHistoryProvider;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-
-public class testSearchHistoryProvider extends ContentProviderTest {
-
-    // Translations of "United Kingdom" in several different languages
-    private static final String[] testStrings = {"An Ríocht Aontaithe", // Irish
-            "Angli", // Albanian
-            "Britanniarum Regnum", // Latin
-            "Britio", // Esperanto
-            "Büyük Britanya", // Turkish
-            "Egyesült Királyság", // Hungarian
-            "Erresuma Batua", // Basque
-            "Inggris Raya", // Indonesian
-            "Ir-Renju Unit", // Maltese
-            "Iso-Britannia", // Finnish
-            "Jungtinė Karalystė", // Lithuanian
-            "Lielbritānija", // Latvian
-            "Regatul Unit", // Romanian
-            "Regne Unit", // Catalan, Valencian
-            "Regno Unito", // Italian
-            "Royaume-Uni", // French
-            "Spojené království", // Czech
-            "Spojené kráľovstvo", // Slovak
-            "Storbritannia", // Norwegian
-            "Storbritannien", // Danish
-            "Suurbritannia", // Estonian
-            "Ujedinjeno Kraljevstvo", // Bosnian
-            "United Alaeze", // Igbo
-            "United Kingdom", // English
-            "Vereinigtes Königreich", // German
-            "Verenigd Koninkrijk", // Dutch
-            "Verenigde Koninkryk", // Afrikaans
-            "Vương quốc Anh", // Vietnamese
-            "Wayòm Ini", // Haitian, Haitian Creole
-            "Y Deyrnas Unedig", // Welsh
-            "Združeno kraljestvo", // Slovene
-            "Zjednoczone Królestwo", // Polish
-            "Ηνωμένο Βασίλειο", // Greek (modern)
-            "Великобритания", // Russian
-            "Нэгдсэн Вант Улс", // Mongolian
-            "Обединетото Кралство", // Macedonian
-            "Уједињено Краљевство", // Serbian
-            "Միացյալ Թագավորություն", // Armenian
-            "בריטניה", // Hebrew (modern)
-            "פֿאַראייניקטע מלכות", // Yiddish
-            "المملكة المتحدة", // Arabic
-            "برطانیہ", // Urdu
-            "پادشاهی متحده", // Persian (Farsi)
-            "यूनाइटेड किंगडम", // Hindi
-            "संयुक्त राज्य", // Nepali
-            "যুক্তরাজ্য", // Bengali, Bangla
-            "યુનાઇટેડ કિંગડમ", // Gujarati
-            "ஐக்கிய ராஜ்யம்", // Tamil
-            "สหราชอาณาจักร", // Thai
-            "ສະ​ຫະ​ປະ​ຊາ​ຊະ​ອາ​ນາ​ຈັກ", // Lao
-            "გაერთიანებული სამეფო", // Georgian
-            "イギリス", // Japanese
-            "联合王国" // Chinese
-    };
-
-
-    private static final String DB_NAME = "searchhistory.db";
-
-    /**
-     * Boilerplate alert.
-     * <p/>
-     * Make sure this method is present and that it returns a new
-     * instance of your class.
-     */
-    private static Callable<ContentProvider> sProviderFactory =
-            new Callable<ContentProvider>() {
-                @Override
-                public ContentProvider call() {
-                    return new SearchHistoryProvider();
-                }
-            };
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp(sProviderFactory, BrowserContract.SEARCH_HISTORY_AUTHORITY, DB_NAME);
-        mTests.add(new TestInsert());
-        mTests.add(new TestUnicodeQuery());
-        mTests.add(new TestTimestamp());
-        mTests.add(new TestDelete());
-        mTests.add(new TestIncrement());
-    }
-
-    public void testSearchHistory() throws Exception {
-        for (Runnable test : mTests) {
-            String testName = test.getClass().getSimpleName();
-            setTestName(testName);
-            mAsserter.dumpLog(
-                    "testBrowserProvider: Database empty - Starting " + testName + ".");
-            // Clear the db
-            mProvider.delete(SearchHistory.CONTENT_URI, null, null);
-            test.run();
-        }
-    }
-
-    /**
-     * Verify that we can insert values into the DB, including unicode.
-     */
-    private class TestInsert extends TestCase {
-        @Override
-        public void test() throws Exception {
-            ContentValues cv;
-            for (int i = 0; i < testStrings.length; i++) {
-                cv = new ContentValues();
-                cv.put(SearchHistory.QUERY, testStrings[i]);
-                mProvider.insert(SearchHistory.CONTENT_URI, cv);
-            }
-
-            Cursor c = mProvider.query(SearchHistory.CONTENT_URI, null, null, null, null);
-            mAsserter.is(c.getCount(), testStrings.length,
-                    "Should have one row for each insert");
-
-            c.close();
-        }
-    }
-
-    /**
-     * Verify that we can insert values into the DB, including unicode.
-     */
-    private class TestUnicodeQuery extends TestCase {
-        @Override
-        public void test() throws Exception {
-            ContentValues cv;
-            Cursor c = null;
-            String selection = SearchHistory.QUERY + " = ?";
-
-            for (int i = 0; i < testStrings.length; i++) {
-                cv = new ContentValues();
-                cv.put(SearchHistory.QUERY, testStrings[i]);
-                mProvider.insert(SearchHistory.CONTENT_URI, cv);
-
-                c = mProvider.query(SearchHistory.CONTENT_URI, null, selection,
-                        new String[]{ testStrings[i] }, null);
-                mAsserter.is(c.getCount(), 1,
-                        "Should have one row for insert of " + testStrings[i]);
-            }
-
-            if (c != null) {
-                c.close();
-            }
-        }
-    }
-
-    /**
-     * Verify that timestamps are updated on insert.
-     */
-    private class TestTimestamp extends TestCase {
-        @Override
-        public void test() throws Exception {
-            String insertedTerm = "Courtside Seats";
-            long insertStart;
-            long insertFinish;
-            long t1Db;
-            long t2Db;
-
-            ContentValues cv = new ContentValues();
-            cv.put(SearchHistory.QUERY, insertedTerm);
-
-            // First check that the DB has a value that is close to the
-            // system time.
-            insertStart = System.currentTimeMillis();
-            mProvider.insert(SearchHistory.CONTENT_URI, cv);
-            Cursor c = mProvider.query(SearchHistory.CONTENT_URI, null, null, null, null);
-            c.moveToFirst();
-            t1Db = c.getLong(c.getColumnIndex(SearchHistory.DATE_LAST_VISITED));
-            c.close();
-            insertFinish = System.currentTimeMillis();
-            mAsserter.ok(t1Db >= insertStart, "DATE_LAST_VISITED",
-                    "Date last visited should be set on insert.");
-            mAsserter.ok(t1Db <= insertFinish, "DATE_LAST_VISITED",
-                    "Date last visited should be set on insert.");
-
-            cv = new ContentValues();
-            cv.put(SearchHistory.QUERY, insertedTerm);
-
-            insertStart = System.currentTimeMillis();
-            mProvider.insert(SearchHistory.CONTENT_URI, cv);
-            c = mProvider.query(SearchHistory.CONTENT_URI, null, null, null, null);
-            c.moveToFirst();
-            t2Db = c.getLong(c.getColumnIndex(SearchHistory.DATE_LAST_VISITED));
-            c.close();
-            insertFinish = System.currentTimeMillis();
-
-            mAsserter.ok(t2Db >= insertStart, "DATE_LAST_VISITED",
-                    "Date last visited should be set on insert.");
-            mAsserter.ok(t2Db <= insertFinish, "DATE_LAST_VISITED",
-                    "Date last visited should be set on insert.");
-            mAsserter.ok(t2Db > t1Db, "DATE_LAST_VISITED",
-                    "Date last visited should be updated on key increment.");
-        }
-    }
-
-    /**
-     * Verify that sending a delete command empties the database.
-     */
-    private class TestDelete extends TestCase {
-        @Override
-        public void test() throws Exception {
-            String insertedTerm = "Courtside Seats";
-
-            ContentValues cv = new ContentValues();
-            cv.put(SearchHistory.QUERY, insertedTerm);
-            mProvider.insert(SearchHistory.CONTENT_URI, cv);
-
-            Cursor c = mProvider.query(SearchHistory.CONTENT_URI, null, null, null, null);
-            mAsserter.is(c.getCount(), 1, "Should have one value");
-            mProvider.delete(SearchHistory.CONTENT_URI, null, null);
-            c.close();
-
-            c = mProvider.query(SearchHistory.CONTENT_URI, null, null, null, null);
-            mAsserter.is(c.getCount(), 0, "Should be empty");
-            mProvider.insert(SearchHistory.CONTENT_URI, cv);
-            c.close();
-
-            c = mProvider.query(SearchHistory.CONTENT_URI, null, null, null, null);
-            mAsserter.is(c.getCount(), 1, "Should have one value");
-            c.close();
-        }
-    }
-
-
-    /**
-     * Ensure that we only increment when the case matches.
-     */
-    private class TestIncrement extends TestCase {
-        @Override
-        public void test() throws Exception {
-            ContentValues cv = new ContentValues();
-            cv.put(SearchHistory.QUERY, "omaha");
-            mProvider.insert(SearchHistory.CONTENT_URI, cv);
-
-            cv = new ContentValues();
-            cv.put(SearchHistory.QUERY, "omaha");
-            mProvider.insert(SearchHistory.CONTENT_URI, cv);
-
-            Cursor c = mProvider.query(SearchHistory.CONTENT_URI, null, null, null, null);
-            c.moveToFirst();
-            mAsserter.is(c.getCount(), 1, "Should have one result");
-            mAsserter.is(c.getInt(c.getColumnIndex(SearchHistory.VISITS)), 2,
-                    "Counter should be 2");
-            c.close();
-
-            cv = new ContentValues();
-            cv.put(SearchHistory.QUERY, "Omaha");
-            mProvider.insert(SearchHistory.CONTENT_URI, cv);
-            c = mProvider.query(SearchHistory.CONTENT_URI, null, null, null, null);
-            mAsserter.is(c.getCount(), 2, "Should have two results");
-            c.close();
-        }
-    }
-}
--- a/toolkit/devtools/server/actors/inspector.js
+++ b/toolkit/devtools/server/actors/inspector.js
@@ -2771,16 +2771,20 @@ function nodeDocument(node) {
 
 /**
  * Similar to a TreeWalker, except will dig in to iframes and it doesn't
  * implement the good methods like previousNode and nextNode.
  *
  * See TreeWalker documentation for explanations of the methods.
  */
 function DocumentWalker(aNode, aRootWin, aShow, aFilter, aExpandEntityReferences) {
+  if (!aRootWin.location) {
+    throw new Error("Got an invalid root window in DocumentWalker");
+  }
+
   let doc = nodeDocument(aNode);
   this.layoutHelpers = new LayoutHelpers(aRootWin);
   this.walker = doc.createTreeWalker(doc,
     aShow, aFilter, aExpandEntityReferences);
   this.walker.currentNode = aNode;
   this.filter = aFilter;
 }
 
--- a/toolkit/modules/Services.jsm
+++ b/toolkit/modules/Services.jsm
@@ -56,16 +56,17 @@ let initTable = [
   ["contentPrefs", "@mozilla.org/content-pref/service;1", "nsIContentPrefService"],
   ["cookies", "@mozilla.org/cookiemanager;1", "nsICookieManager2"],
   ["downloads", "@mozilla.org/download-manager;1", "nsIDownloadManager"],
   ["droppedLinkHandler", "@mozilla.org/content/dropped-link-handler;1", "nsIDroppedLinkHandler"],
   ["eTLD", "@mozilla.org/network/effective-tld-service;1", "nsIEffectiveTLDService"],
   ["io", "@mozilla.org/network/io-service;1", "nsIIOService2"],
   ["locale", "@mozilla.org/intl/nslocaleservice;1", "nsILocaleService"],
   ["logins", "@mozilla.org/login-manager;1", "nsILoginManager"],
+  ["netutil", "@mozilla.org/network/util;1", "nsINetUtil"],
   ["obs", "@mozilla.org/observer-service;1", "nsIObserverService"],
   ["perms", "@mozilla.org/permissionmanager;1", "nsIPermissionManager"],
   ["prompt", "@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService"],
 #ifdef MOZ_ENABLE_PROFILER_SPS
   ["profiler", "@mozilla.org/tools/profiler;1", "nsIProfiler"],
 #endif
   ["scriptloader", "@mozilla.org/moz/jssubscript-loader;1", "mozIJSSubScriptLoader"],
   ["scriptSecurityManager", "@mozilla.org/scriptsecuritymanager;1", "nsIScriptSecurityManager"],