Bug 1041678 - CTRL/Command K should goto search bar in new tab if open, rather than opening about:home. r=Gijs, r=adw
authorAlex Bardas <abardas@mozilla.com>
Thu, 14 Aug 2014 15:48:00 -0400
changeset 199824 9390b8124699a0ae49349a206ecd48ee1af8e3bb
parent 199823 f9a519ff098f331b83683366d17788fad0dc6fb0
child 199825 abd2919b124b47afd188ffe709517835811150a6
push id27320
push userryanvm@gmail.com
push dateFri, 15 Aug 2014 20:23:01 +0000
treeherdermozilla-central@b9da9928d061 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs, adw
bugs1041678
milestone34.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 1041678 - CTRL/Command K should goto search bar in new tab if open, rather than opening about:home. r=Gijs, r=adw
browser/base/content/browser.js
browser/base/content/content.js
browser/base/content/newtab/search.js
browser/base/content/test/general/browser_aboutHome.js
browser/base/content/test/general/head.js
browser/base/content/test/newtab/browser_newtab_search.js
browser/modules/AboutHome.jsm
browser/modules/ContentSearch.jsm
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -18,16 +18,20 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
                                   "resource://gre/modules/CharsetMenu.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
                                   "resource://gre/modules/ShortcutUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "GMPInstallManager",
                                   "resource://gre/modules/GMPInstallManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
+                                  "resource:///modules/ContentSearch.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
+                                  "resource:///modules/AboutHome.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
                                    "@mozilla.org/network/dns-service;1",
                                    "nsIDNSService");
 
 const nsIWebNavigation = Ci.nsIWebNavigation;
 
 var gLastBrowserCharset = null;
 var gPrevCharset = null;
@@ -3090,18 +3094,27 @@ const BrowserSearch = {
         win = window.openDialog(getBrowserURL(), "_blank",
                                 "chrome,all,dialog=no", "about:blank");
         Services.obs.addObserver(observer, "browser-delayed-startup-finished", false);
       }
       return;
     }
 #endif
     let openSearchPageIfFieldIsNotActive = function(aSearchBar) {
-      if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField)
+      let doc = gBrowser.selectedBrowser.contentDocument;
+      let url = doc.documentURI.toLowerCase();
+      let mm = gBrowser.selectedBrowser.messageManager;
+
+      if (url === "about:home") {
+        AboutHome.focusInput(mm);
+      } else if (url === "about:newtab") {
+        ContentSearch.focusInput(mm);
+      } else if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField) {
         openUILinkIn("about:home", "current");
+      }
     };
 
     let searchBar = this.searchBar;
     let placement = CustomizableUI.getPlacementOfWidget("search-container");
     let focusSearchBar = () => {
       searchBar = this.searchBar;
       searchBar.select();
       openSearchPageIfFieldIsNotActive(searchBar);
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -101,16 +101,19 @@ let AboutHomeListener = {
     }
   },
 
   receiveMessage: function(aMessage) {
     switch (aMessage.name) {
       case "AboutHome:Update":
         this.onUpdate(aMessage.data);
         break;
+      case "AboutHome:FocusInput":
+        this.onFocusInput();
+        break;
     }
   },
 
   onUpdate: function(aData) {
     let doc = content.document;
     if (doc.documentURI.toLowerCase() != "about:home")
       return;
 
@@ -133,16 +136,17 @@ let AboutHomeListener = {
     if (doc.documentURI.toLowerCase() != "about:home" ||
         doc.documentElement.hasAttribute("hasBrowserHandlers")) {
       return;
     }
 
     doc.documentElement.setAttribute("hasBrowserHandlers", "true");
     let self = this;
     addMessageListener("AboutHome:Update", self);
+    addMessageListener("AboutHome:FocusInput", self);
     addEventListener("click", this.onClick, true);
     addEventListener("pagehide", function onPageHide(event) {
       if (event.target.defaultView.frameElement)
         return;
       removeMessageListener("AboutHome:Update", self);
       removeEventListener("click", self.onClick, true);
       removeEventListener("pagehide", onPageHide, true);
       if (event.target.documentElement)
@@ -207,16 +211,20 @@ let AboutHomeListener = {
         sendAsyncMessage("AboutHome:Sync");
         break;
 
       case "settings":
         sendAsyncMessage("AboutHome:Settings");
         break;
     }
   },
+
+  onFocusInput: function () {
+    content.document.getElementById("searchText").focus();
+  },
 };
 AboutHomeListener.init(this);
 
 
 // An event listener for custom "WebChannelMessageToChrome" events on pages
 addEventListener("WebChannelMessageToChrome", function (e) {
   // if target is window then we want the document principal, otherwise fallback to target itself.
   let principal = e.target.nodePrincipal ? e.target.nodePrincipal : e.target.document.nodePrincipal;
--- a/browser/base/content/newtab/search.js
+++ b/browser/base/content/newtab/search.js
@@ -71,16 +71,20 @@ let gSearch = {
 
   onCurrentEngine: function (engineName) {
     if (this._initialStateReceived) {
       this._nodes.panel.hidePopup();
       this._setCurrentEngine(engineName);
     }
   },
 
+  onFocusInput: function () {
+    this._nodes.text.focus();
+  },
+
   _nodeIDSuffixes: [
     "form",
     "logo",
     "manage",
     "panel",
     "text",
   ],
 
--- a/browser/base/content/test/general/browser_aboutHome.js
+++ b/browser/base/content/test/general/browser_aboutHome.js
@@ -414,16 +414,32 @@ let gTests = [
 
       // Empty the search input, causing the suggestions to be hidden.
       EventUtils.synthesizeKey("a", { accelKey: true });
       EventUtils.synthesizeKey("VK_DELETE", {});
       ok(table.hidden, "Search suggestion table hidden");
     });
   }
 },
+{
+  desc: "Cmd+k should focus the search bar element",
+  setup: function () {},
+  run: Task.async(function* () {
+    let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
+    let logo = doc.getElementById("brandLogo");
+    let searchInput = doc.getElementById("searchText");
+
+    EventUtils.synthesizeMouseAtCenter(logo, {});
+    isnot(searchInput, doc.activeElement, "Search input should not be the active element.");
+
+    EventUtils.synthesizeKey("k", { accelKey: true });
+    yield promiseWaitForCondition(() => doc.activeElement === searchInput);
+    is(searchInput, doc.activeElement, "Search input should be the active element.");
+  })
+},
 
 ];
 
 function test()
 {
   waitForExplicitFinish();
   requestLongerTimeout(2);
   ignoreAllUncaughtExceptions();
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -77,16 +77,22 @@ function waitForCondition(condition, nex
     if (conditionPassed) {
       moveOn();
     }
     tries++;
   }, 100);
   var moveOn = function() { clearInterval(interval); nextTest(); };
 }
 
+function promiseWaitForCondition(aConditionFn) {
+  let deferred = Promise.defer();
+  waitForCondition(aConditionFn, deferred.resolve, "Condition didn't pass.");
+  return deferred.promise;
+}
+
 function getTestPlugin(aName) {
   var pluginName = aName || "Test Plug-in";
   var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
   var tags = ph.getPluginTags();
 
   // Find the test plugin
   for (var i = 0; i < tags.length; i++) {
     if (tags[i].name == pluginName)
--- a/browser/base/content/test/newtab/browser_newtab_search.js
+++ b/browser/base/content/test/newtab/browser_newtab_search.js
@@ -181,16 +181,26 @@ function runTests() {
   yield undefined;
   yield suggestionsPromise.then(TestRunner.next);
 
   // Empty the search input, causing the suggestions to be hidden.
   EventUtils.synthesizeKey("a", { accelKey: true });
   EventUtils.synthesizeKey("VK_DELETE", {});
   ok(table.hidden, "Search suggestion table hidden");
 
+  // Focus a different element than the search input.
+  let btn = getContentDocument().getElementById("newtab-customize-button");
+  yield promiseClick(btn).then(TestRunner.next);
+
+  isnot(input, getContentDocument().activeElement, "Search input should not be focused");
+  // Test that Ctrl/Cmd + K will focus the input field.
+  EventUtils.synthesizeKey("k", { accelKey: true });
+  yield promiseSearchEvents(["FocusInput"]).then(TestRunner.next);
+  is(input, getContentDocument().activeElement, "Search input should be focused");
+
   // Done.  Revert the current engine and remove the new engines.
   Services.search.currentEngine = oldCurrentEngine;
   yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next);
 
   let events = [];
   for (let engine of gNewEngines) {
     Services.search.removeEngine(engine);
     events.push("CurrentState");
--- a/browser/modules/AboutHome.jsm
+++ b/browser/modules/AboutHome.jsm
@@ -246,9 +246,18 @@ let AboutHome = {
       } else {
         let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
         mm.broadcastAsyncMessage("AboutHome:Update", data);
       }
     }).then(null, function onError(x) {
       Cu.reportError("Error in AboutHome.sendAboutHomeData: " + x);
     });
   },
+
+  /**
+   * Focuses the search input in the page with the given message manager.
+   * @param  messageManager
+   *         The MessageManager object of the selected browser.
+   */
+  focusInput: function (messageManager) {
+    messageManager.sendAsyncMessage("AboutHome:FocusInput");
+  }
 };
--- a/browser/modules/ContentSearch.jsm
+++ b/browser/modules/ContentSearch.jsm
@@ -89,16 +89,27 @@ this.ContentSearch = {
 
   init: function () {
     Cc["@mozilla.org/globalmessagemanager;1"].
       getService(Ci.nsIMessageListenerManager).
       addMessageListener(INBOUND_MESSAGE, this);
     Services.obs.addObserver(this, "browser-search-engine-modified", false);
   },
 
+  /**
+   * Focuses the search input in the page with the given message manager.
+   * @param  messageManager
+   *         The MessageManager object of the selected browser.
+   */
+  focusInput: function (messageManager) {
+    messageManager.sendAsyncMessage(OUTBOUND_MESSAGE, {
+      type: "FocusInput"
+    });
+  },
+
   receiveMessage: function (msg) {
     // Add a temporary event handler that exists only while the message is in
     // the event queue.  If the message's source docshell changes browsers in
     // the meantime, then we need to update msg.target.  event.detail will be
     // the docshell's new parent <xul:browser> element.
     msg.handleEvent = event => {
       let browserData = this._suggestionMap.get(msg.target);
       if (browserData) {