Bug 1219505 - Unified urlbar experiment. r=mak,felipe
authorDrew Willcoxon <adw@mozilla.com>
Tue, 05 Jan 2016 21:16:51 +0100
changeset 65 c6764956fa7050da00e496cd6bfb865b33475ca5
parent 64 14a8a6325cc3d002da6715fa1d883e51713c3285
child 66 1d43fe62a47827c3f1197f2c9c1ab0b82c9db094
push id49
push usermak77@bonardo.net
push dateTue, 05 Jan 2016 20:19:00 +0000
reviewersmak, felipe
bugs1219505
Bug 1219505 - Unified urlbar experiment. r=mak,felipe Additional fixes from Marco Bonardo <mak@mozilla.com>. r=felipe
experiments/unified-urlbar/code/bootstrap.js
experiments/unified-urlbar/code/chrome.manifest
experiments/unified-urlbar/code/content/BrowserListener.jsm
experiments/unified-urlbar/code/content/Panel.jsm
experiments/unified-urlbar/code/content/Telemetry.jsm
experiments/unified-urlbar/code/content/gear.svg
experiments/unified-urlbar/code/content/style.css
experiments/unified-urlbar/code/install.rdf
experiments/unified-urlbar/experiment.xpi
experiments/unified-urlbar/filter.js
experiments/unified-urlbar/manifest.json
new file mode 100755
--- /dev/null
+++ b/experiments/unified-urlbar/code/bootstrap.js
@@ -0,0 +1,79 @@
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/experiments/Experiments.jsm");
+Cu.import("resource:///modules/CustomizableUI.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Telemetry",
+                                  "chrome://unified-urlbar/content/Telemetry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserListener",
+                                  "chrome://unified-urlbar/content/BrowserListener.jsm");
+
+var gStarted = false;
+
+function startup(data, reason) {
+  // Seems startup() function is launched twice after install, we're
+  // unsure why so far. We only want it to run once.
+  if (gStarted) {
+    return;
+  }
+  gStarted = true;
+
+  // Workaround until bug 1228359 is fixed.
+  //Components.manager.addBootstrappedManifestLocation(data.installPath);
+
+  ensureExperimentBranch().then(branch => {
+    BrowserListener.init(branch);
+    Telemetry.init(branch);
+  });
+}
+
+function shutdown(data, reason) {
+  // Workaround until bug 1228359 is fixed.
+  //Components.manager.removeBootstrappedManifestLocation(data.installPath);
+
+  BrowserListener.destroy();
+  Telemetry.destroy();
+}
+
+function install(data, reason) {}
+function uninstall(data, reason) {}
+
+/**
+ * Ensures that the experiment branch is set and returns it.
+ *
+ * @return Promise<String> Resolved with the branch.
+ */
+function ensureExperimentBranch() {
+  return new Promise(resolve => {
+    // TESTING CODE
+    try {
+      let forcedBranch =
+        Services.prefs.getCharPref("browser.urlbar.experiment.unified-urlbar.branch");
+      resolve(forcedBranch);
+    } catch (ex) {}
+
+    let experiments = Experiments.instance();
+    // This experiment has 3 user groups:
+    //  * "control"   : Users with default search bar setup.
+    //                  No UI changes.
+    //  * "customized": Users who customized the search bar position.
+    //                  Add one-off buttons.
+    //  * "unified"   : Add one-off search buttons to the location bar and
+    //                  customize away the search bar.
+    let branch = experiments.getActiveExperimentBranch();
+    if (branch) {
+      resolve(branch);
+      return;
+    }
+    let placement = CustomizableUI.getPlacementOfWidget("search-container");
+    if (!placement || placement.area != "nav-bar") {
+      branch = "customized";
+    } else {
+      let coinFlip = Math.floor(2 * Math.random());
+      branch = coinFlip ? "control" : "unified";
+    }
+    let id = experiments.getActiveExperimentID();
+    experiments.setExperimentBranch(id, branch).then(() => resolve(branch));
+  });
+}
new file mode 100755
--- /dev/null
+++ b/experiments/unified-urlbar/code/chrome.manifest
@@ -0,0 +1,1 @@
+content unified-urlbar content/
new file mode 100755
--- /dev/null
+++ b/experiments/unified-urlbar/code/content/BrowserListener.jsm
@@ -0,0 +1,153 @@
+this.EXPORTED_SYMBOLS = [
+  "BrowserListener",
+];
+
+const STYLE_URL = "chrome://unified-urlbar/content/style.css";
+const SEARCH_BAR_WIDGET_ID = "search-container";
+const XHTML_NS = "http://www.w3.org/1999/xhtml";
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/CustomizableUI.jsm");
+Cu.import("chrome://unified-urlbar/content/Panel.jsm");
+
+var gBranch = "control";
+var gBrowsers = null;
+
+this.BrowserListener = Object.freeze({
+  init(branch) {
+    gBranch = branch;
+
+    if (gBrowsers) {
+      return;
+    }
+    gBrowsers = new Set();
+    getBrowserWindows();
+    Services.ww.registerNotification(this);
+  },
+
+  destroy() {
+    if (!gBrowsers) {
+      return;
+    }
+
+    Services.ww.unregisterNotification(this);
+    for (let browser of gBrowsers) {
+      browser.destroy();
+    }
+    gBrowsers.clear();
+    gBrowsers = null;
+  },
+
+  observe(subj, topic, data) {
+    let win = subj.QueryInterface(Ci.nsIDOMWindow);
+    if (!win) {
+      return;
+    }
+
+    if (topic == "domwindowopened") {
+      whenWindowLoaded(win, () => {
+        if (isValidBrowserWindow(win)) {
+          gBrowsers.add(new Browser(win));
+        }
+      });
+    } else if (topic == "domwindowclosed") {
+      for (let browser of gBrowsers) {
+        if (browser.window == win) {
+          browser.destroy();
+          gBrowsers.delete(browser);
+          break;
+        }
+      }
+    }
+  },
+});
+
+function Browser(win) {
+  this.window = win;
+
+  switch (gBranch) {
+    case "unified":
+      this._moveSearchBar();
+      // Fall through.
+    case "customized":
+      this._injectStyle();
+      this._initPanel();
+      break;
+    default:
+      // Nothing!
+      break;
+  }
+}
+
+Browser.prototype = {
+  get document() {
+    return this.window.document;
+  },
+
+  destroy() {
+    if (this._styleLink) {
+      this._styleLink.remove();
+      delete this._styleLink;
+    }
+
+    if (this._panel) {
+      this._panel.destroy();
+    }
+
+    if (this._searchbarPlacement) {
+      CustomizableUI.addWidgetToArea(SEARCH_BAR_WIDGET_ID,
+                                     this._searchbarPlacement.area,
+                                     this._searchbarPlacement.position);
+    }
+  },
+
+  _injectStyle() {
+    this._styleLink = this.document.createElementNS(XHTML_NS, "link");
+    this._styleLink.setAttribute("href", STYLE_URL);
+    this._styleLink.setAttribute("rel", "stylesheet");
+    this.document.documentElement.appendChild(this._styleLink);
+  },
+
+  _initPanel() {
+    let elt = this.document.getElementById("PopupAutoCompleteRichResult");
+    this._panel = new Panel(elt);
+  },
+
+  _moveSearchBar() {
+    this._searchbarPlacement =
+      CustomizableUI.getPlacementOfWidget(SEARCH_BAR_WIDGET_ID);
+    CustomizableUI.removeWidgetFromArea(SEARCH_BAR_WIDGET_ID);
+  },
+};
+
+function isValidBrowserWindow(win) {
+  return !win.closed && win.toolbar.visible &&
+          win.document.documentElement.getAttribute("windowtype") == "navigator:browser";
+}
+
+function getBrowserWindows() {
+  let wins = Services.ww.getWindowEnumerator();
+  while (wins.hasMoreElements()) {
+    let win = wins.getNext().QueryInterface(Ci.nsIDOMWindow);
+    whenWindowLoaded(win, () => {
+      if (isValidBrowserWindow(win)) {
+        gBrowsers.add(new Browser(win));
+      }
+    });
+  }
+}
+
+function whenWindowLoaded(win, callback) {
+  if (win.document.readyState == "complete") {
+    callback();
+    return;
+  }
+  win.addEventListener("load", function onLoad(event) {
+    if (event.target == win.document) {
+      win.removeEventListener("load", onLoad, true);
+      win.setTimeout(callback, 0);
+    }
+  }, true);
+}
new file mode 100755
--- /dev/null
+++ b/experiments/unified-urlbar/code/content/Panel.jsm
@@ -0,0 +1,538 @@
+this.EXPORTED_SYMBOLS = [
+  "Panel",
+];
+
+const EXISTING_FOOTER_ID = "urlbar-search-footer";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("chrome://unified-urlbar/content/Telemetry.jsm");
+
+this.Panel = function (panelElt) {
+  this.panelElement = panelElt;
+  this._initPanelElement();
+  this._initKeyHandler();
+  this.urlbar.addEventListener("input", this);
+};
+
+this.Panel.prototype = {
+  get document() {
+    return this.panelElement.ownerDocument;
+  },
+
+  get window() {
+    return this.document.defaultView;
+  },
+
+  get urlbar() {
+    return this.window.gURLBar;
+  },
+
+  destroy() {
+    this.urlbar.handleKeyPress = this.urlbar._handleKeyPress;
+    delete this.urlbar._handleKeyPress;
+
+    this.urlbar.handleCommand = this.urlbar._handleCommand;
+    delete this.urlbar._handleCommand;
+
+    this.urlbar.removeEventListener("input", this);
+
+    this.panelElement.removeEventListener("popupshowing", this);
+
+    this.footer.remove();
+    if (this._existingFooter) {
+      this._existingFooterParent.appendChild(this._existingFooter);
+    }
+  },
+
+  _initPanelElement() {
+    this._existingFooter = this.document.getElementById(EXISTING_FOOTER_ID);
+    if (this._existingFooter) {
+      this._existingFooterParent = this._existingFooter.parentNode;
+      this._existingFooter.remove();
+    }
+
+    let footer = this._makeFooter();
+    this.footer = footer;
+
+    let header = this._makeHeader();
+    this.header = header;
+    footer.appendChild(header);
+
+    let hbox = this._makeFooterHbox();
+    footer.appendChild(hbox);
+
+    let list = this._makeButtonList();
+    this.buttonList = list;
+    hbox.appendChild(list);
+
+    this.settingsButton = this._makeSettingsButton();
+    hbox.appendChild(this.settingsButton);
+
+    this.panelElement.appendChild(footer);
+
+    this.panelElement.addEventListener("popupshowing", this);
+  },
+
+  _makeFooter() {
+    let footer = this.document.createElementNS(XUL_NS, "vbox");
+    footer.id = "urlbar-search-footer2";
+    footer.setAttribute("flex", "1");
+    return footer;
+  },
+
+  _makeHeader() {
+    let header = this.document.createElementNS(XUL_NS, "deck");
+    header.id = "urlbar-one-offs-header";
+    header.className = "urlbar-header urlbar-current-input";
+    header.setAttribute("selectedIndex", "0");
+
+    let label = this.document.createElementNS(XUL_NS, "label");
+    label.id = "urlbar-oneoffheader-search"
+    label.setAttribute("value", "Search with:"); // searchWithHeader.label
+    header.appendChild(label);
+
+    let hbox = this.document.createElementNS(XUL_NS, "hbox");
+    hbox.id = "urlbar-searchforwith";
+    hbox.className = "urlbar-current-input";
+    label = this.document.createElementNS(XUL_NS, "label");
+    label.id = "urlbar-oneoffheader-before";
+    label.setAttribute("value", "Search for "); // searchFor.label
+    hbox.appendChild(label);
+    label = this.document.createElementNS(XUL_NS, "label");
+    label.id = "urlbar-oneoffheader-searchtext";
+    label.className = "urlbar-input-value";
+    label.setAttribute("flex", "1");
+    label.setAttribute("crop", "end");
+    hbox.appendChild(label);
+    label = this.document.createElementNS(XUL_NS, "label");
+    label.id = "urlbar-oneoffheader-after";
+    label.setAttribute("flex", "10000");
+    label.setAttribute("value", " with:"); // searchWith.label
+    hbox.appendChild(label);
+    header.appendChild(hbox);
+
+    hbox = this.document.createElementNS(XUL_NS, "hbox");
+    hbox.id = "urlbar-searchonengine";
+    hbox.className = "urlbar-current-input";
+    label = this.document.createElementNS(XUL_NS, "label");
+    label.id = "urlbar-oneoffheader-beforeengine";
+    label.setAttribute("value", "Search "); // search.label
+    hbox.appendChild(label);
+    label = this.document.createElementNS(XUL_NS, "label");
+    label.id = "urlbar-oneoffheader-engine";
+    label.className = "urlbar-input-value";
+    label.setAttribute("flex", "1");
+    label.setAttribute("crop", "end");
+    hbox.appendChild(label);
+    label = this.document.createElementNS(XUL_NS, "label");
+    label.id = "urlbar-oneoffheader-afterengine";
+    label.setAttribute("flex", "10000");
+    label.setAttribute("value", ""); // searchAfter.label
+    hbox.appendChild(label);
+    header.appendChild(hbox);
+
+    return header;
+  },
+
+  _makeFooterHbox() {
+    let box = this.document.createElementNS(XUL_NS, "hbox");
+    box.setAttribute("flex", "1");
+    box.setAttribute("align", "stretch");
+    box.setAttribute("pack", "end");
+    return box;
+  },
+
+  _makeSettingsButton() {
+    let button = this.document.createElementNS(XUL_NS, "button");
+    button.className = "urlbar-engine-one-off-item search-setting-button";
+    button.id = "urlbar-search-settings2";
+    button.addEventListener("command", this);
+    return button;
+  },
+
+  _makeButtonList() {
+    let list = this.document.createElementNS(XUL_NS, "description");
+    list.id = "urlbar-one-offs";
+    list.className = "urlbar-one-offs";
+    list.setAttribute("flex", "1");
+
+    let eventNames = ["click", "mouseout", "mouseover"];
+    for (let name of eventNames) {
+      list.addEventListener(name, this);
+    }
+
+    return list;
+  },
+
+  _updateHeader() {
+    let headerSearchText =
+      this.document.getElementById("urlbar-oneoffheader-searchtext");
+
+    let searchStr = this.urlbar.controller.searchString;
+    headerSearchText.setAttribute("value", searchStr);
+
+    let groupText;
+    let isOneOffSelected =
+      this.selectedButton &&
+      this.selectedButton.classList.contains("urlbar-engine-one-off-item");
+    // Typing de-selects the settings or opensearch buttons at the bottom
+    // of the search panel, as typing shows the user intends to search.
+    if (this.selectedButton && !isOneOffSelected) {
+      this.selectedButton = null;
+    }
+    if (searchStr) {
+      groupText = headerSearchText.previousSibling.value +
+                  '"' + headerSearchText.value + '"' +
+                  headerSearchText.nextSibling.value;
+      if (!isOneOffSelected) {
+        this.header.setAttribute("selectedIndex", "1");
+      }
+    }
+    else {
+      let noSearchHeader =
+        this.document.getElementById("urlbar-oneoffheader-search");
+      groupText = noSearchHeader.value;
+      if (!isOneOffSelected) {
+        this.header.setAttribute("selectedIndex", "0");
+      }
+    }
+    this.buttonList.setAttribute("aria-label", groupText);
+  },
+
+  _initKeyHandler() {
+    this.urlbar._handleKeyPress = this.urlbar.handleKeyPress;
+    this.urlbar.handleKeyPress = event => this._handleKeyPress(event);
+
+    this.urlbar._handleCommand = this.urlbar.handleCommand;
+    this.urlbar.handleCommand = event => this._handleCommand(event);
+  },
+
+  _handleKeyPress(event) {
+    if (!this.panelElement.popupOpen ||
+        this.panelElement.disableKeyNavigation) {
+      this.urlbar._handleKeyPress(event);
+      return;
+    }
+
+    let keyCode = event.keyCode;
+
+    // Handle Tab and Shift+Tab like Down and Up arrow keys.
+    if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_TAB &&
+        this.urlbar.tabScrolling) {
+      keyCode = event.shiftKey ? Ci.nsIDOMKeyEvent.DOM_VK_UP :
+                                 Ci.nsIDOMKeyEvent.DOM_VK_DOWN;
+    }
+
+    // Only handle Up and Down (and Tab and Shift+Tab).  Delegate everything
+    // else to the urlbar.
+    switch (keyCode) {
+      case Ci.nsIDOMKeyEvent.DOM_VK_UP:
+        if (this.panelElement.selectedIndex == 0) {
+          this.selectedButtonIndex = this.numButtons - 1;
+          break;
+        }
+        if (this.selectedButtonIndex >= 0) {
+          this.selectedButtonIndex--;
+          if (this.selectedButtonIndex >= 0) {
+            break;
+          }
+        }
+        this.urlbar._handleKeyPress(event);
+        return;
+      case Ci.nsIDOMKeyEvent.DOM_VK_DOWN:
+        if (this.panelElement.selectedIndex ==
+            this.panelElement._matchCount - 1) {
+          this.selectedButtonIndex = 0;
+          break;
+        }
+        if (this.selectedButtonIndex >= 0) {
+          this.selectedButtonIndex++;
+          if (this.selectedButtonIndex >= 0) {
+            break;
+          }
+        }
+        this.urlbar._handleKeyPress(event);
+        return;
+      default:
+        this.urlbar._handleKeyPress(event);
+        return;
+    }
+
+    if (this.selectedButton != this.settingsButton) {
+      Telemetry.incrementValue("oneOffButtonSelectedByKeypress");
+    }
+    event.preventDefault();
+  },
+
+  // Called on the settings button.
+  _onCommand(event) {
+    this._handleCommand(event);
+  },
+
+  _handleCommand(event) {
+    // This function only handles Return key presses in the urlbar when a one-
+    // off button is selected.
+    if (!event || event.type != "keypress" || !this.selectedButton) {
+      this.urlbar._handleCommand(event);
+      return;
+    }
+    if (this.selectedButton == this.settingsButton) {
+      Telemetry.incrementValue("searchSettingsClicked");
+      this.window.openPreferences("paneSearch");
+    } else {
+      this._doSearchFromButton(this.selectedButton, event);
+    }
+    event.preventDefault();
+  },
+
+  handleEvent(event) {
+    let methName = "_on" + event.type[0].toUpperCase() + event.type.substr(1);
+    this[methName](event);
+  },
+
+  _onInput(event) {
+    this._updateHeader();
+  },
+
+  _onPopupshowing(event) {
+    this.selectedButton = null;
+    this._buildButtonList();
+    this._updateHeader();
+  },
+
+  _onMouseover(event) {
+    let target = event.originalTarget;
+    if (target.localName != "button") {
+      return;
+    }
+
+    if ((target.classList.contains("urlbar-engine-one-off-item") &&
+         !target.classList.contains("dummy")) ||
+        target.classList.contains("addengine-item") ||
+        target.classList.contains("search-setting-button")) {
+      Telemetry.incrementValue("oneOffButtonSelectedByMouseover");
+      this.selectedButton = target;
+    }
+  },
+
+  _onMouseout(event) {
+    let target = event.originalTarget;
+    if (target.localName != "button") {
+      return;
+    }
+
+    if (this.selectedButton == target) {
+      this.selectedButton = null;
+    }
+  },
+
+  _onClick(event) {
+    if (event.button == 2) {
+      return; // ignore right clicks.
+    }
+    let button = event.originalTarget;
+    this._doSearchFromButton(button, event);
+  },
+
+  _doSearchFromButton(button, event) {
+    let engine = button.engine || button.parentNode.engine;
+    if (!engine) {
+      return;
+    }
+
+    let win = button.ownerDocument.defaultView;
+    if (event instanceof win.KeyboardEvent) {
+      Telemetry.incrementValue("searchByReturnKeyOnOneOffButton", { engine } );
+    } else if (event instanceof win.MouseEvent) {
+      Telemetry.incrementValue("searchByClickOnOneOffButton", { engine });
+    }
+
+    let query = this.urlbar.controller.searchString;
+    let submission = engine.getSubmission(query, null, "keyword");
+    let url = submission.uri.spec;
+    let postData = submission.postData;
+
+//     // close the autocomplete popup and revert the entered address
+//     urlBar.popup.closePopup();
+//     controller.handleEscape();
+
+    // respect the usual clicking subtleties
+    this.window.openUILink(url, event, { postData });
+  },
+
+  _buildButtonList() {
+    while (this.buttonList.firstChild) {
+      this.buttonList.firstChild.remove();
+    }
+
+    let Preferences =
+      Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
+    let pref = Preferences.get("browser.search.hiddenOneOffs");
+    let hiddenList = pref ? pref.split(",") : [];
+
+    let currentEngineName = Services.search.currentEngine.name;
+    let engines = Services.search.getVisibleEngines()
+                          .filter(e => e.name != currentEngineName &&
+                                       hiddenList.indexOf(e.name) == -1);
+
+    // header is a xul:deck so collapsed doesn't work on it, see bug 589569.
+    this.header.hidden = this.buttonList.collapsed = !engines.length;
+
+    // 49px is the min-width of each search engine button,
+    // adapt this const when changing the css.
+    // It's actually 48px + 1px of right border.
+    const ENGINE_WIDTH = 49;
+    let minWidth = parseInt(this.panelElement.width);
+    if (engines.length) {
+      // Ensure the panel is wide enough to fit at least 3 engines.
+      minWidth = Math.max(minWidth, ENGINE_WIDTH * 3);
+    }
+    this.panelElement.style.minWidth = minWidth + "px";
+
+    if (!engines.length) {
+      return;
+    }
+
+    let listWidth = parseInt(this.buttonList.clientWidth);
+    // The + 1 is because the last button doesn't have a right border.
+    let enginesPerRow = Math.floor((listWidth + 1) / ENGINE_WIDTH);
+    let buttonWidth = Math.floor(listWidth / enginesPerRow);
+    // There will be an emtpy area of:
+    //   panelWidth - enginesPerRow * buttonWidth  px
+    // at the end of each row.
+
+    // If the <description> tag with the list of search engines doesn't have
+    // a fixed height, the panel will be sized incorrectly, causing the bottom
+    // of the suggestion <tree> to be hidden.
+    let rowCount = Math.ceil(engines.length / enginesPerRow);
+    let height = rowCount * 33; // 32px per row, 1px border.
+    this.buttonList.setAttribute("height", height + "px");
+
+    let dummyItems =
+      enginesPerRow - (engines.length % enginesPerRow || enginesPerRow);
+    for (let i = 0; i < engines.length; ++i) {
+      let engine = engines[i];
+      let button = this.document.createElementNS(XUL_NS, "button");
+      button.id = "urlbar-engine-one-off-item-" + engine.name.replace(/ /g, '-');
+      button.className = "urlbar-engine-one-off-item";
+      let uri = "chrome://browser/skin/search-engine-placeholder.png";
+      if (engine.iconURI) {
+        uri = engine.iconURI.spec;
+      }
+      button.setAttribute("image", uri);
+      button.setAttribute("tooltiptext", engine.name);
+      button.setAttribute("width", buttonWidth);
+      button.engine = engine;
+
+      if ((i + 1) % enginesPerRow == 0) {
+        button.classList.add("last-of-row");
+      }
+
+      if (i >= engines.length + dummyItems - enginesPerRow) {
+        button.classList.add("last-row");
+      }
+
+      this.buttonList.appendChild(button);
+    }
+
+    while (dummyItems) {
+      let button = this.document.createElementNS(XUL_NS, "button");
+      button.className = "urlbar-engine-one-off-item dummy last-row";
+      button.setAttribute("width", buttonWidth);
+
+      if (!--dummyItems) {
+        button.classList.add("last-of-row");
+      }
+
+      this.buttonList.appendChild(button);
+    }
+
+    // Add a data point for the engine count if it's changed (or this is the
+    // first time reaching here).
+    this._engineCount = this._engineCount || 0;
+    if (engines.length != this._engineCount) {
+      this._engineCount = engines.length;
+      Telemetry.setValue("engineCount", engines.length);
+    }
+  },
+
+  get numButtons() {
+    return Array.reduce(this.buttonList.children, (num, button) => {
+      if (!button.classList.contains("dummy")) {
+        num++;
+      }
+      return num;
+    }, 1); // Also take into account the settings button.
+  },
+
+  get selectedButtonIndex() {
+    if (this.selectedButton) {
+      if (this.settingsButton == this.selectedButton) {
+        return Array.filter(this.buttonList.children,
+                            b => !b.classList.contains("dummy"))
+                    .length;
+      }
+      for (let i = 0; i < this.buttonList.children.length; i++) {
+        let child = this.buttonList.children[i];
+        if (child == this.selectedButton) {
+          return i;
+        }
+      }
+    }
+    return -1;
+  },
+
+  set selectedButtonIndex(index) {
+    let button = null;
+    let validChildrenLen = Array.filter(this.buttonList.children,
+                                        b => !b.classList.contains("dummy"))
+                                .length;
+    if (index == validChildrenLen) {
+      button = this.settingsButton;
+    } else if (0 <= index && index < validChildrenLen) {
+      button = this.buttonList.children[index];
+    }
+    this.selectedButton = button;
+  },
+
+  get selectedButton() {
+    return this._selectedButton || null;
+  },
+
+  set selectedButton(val) {
+    if (this._selectedButton) {
+      this._selectedButton.removeAttribute("selected");
+    }
+
+    // Avoid selecting dummy buttons.
+    if (val && !val.classList.contains("dummy")) {
+      val.setAttribute("selected", "true");
+      this._selectedButton = val;
+
+      // Clear the panel's selection and make sure the input shows the search
+      // string.  Set selectedIndex on the richlistbox directly so that the
+      // panel does not scroll the listbox to the top.
+      this.panelElement.richlistbox.selectedIndex = -1;
+      this.urlbar.textValue = this.urlbar.controller.searchString;
+
+      if (val.classList.contains("urlbar-engine-one-off-item") && val.engine) {
+        let headerEngineText =
+          this.document.getElementById("urlbar-oneoffheader-engine");
+        this.header.selectedIndex = 2;
+        headerEngineText.value = val.engine.name;
+      }
+      else {
+        this.header.selectedIndex = this.urlbar.textValue ? 1 : 0;
+      }
+      this.urlbar.setAttribute("aria-activedescendant", val.id);
+
+      return;
+    }
+
+    this.header.selectedIndex = this.urlbar.textValue ? 1 : 0;
+    this.urlbar.removeAttribute("aria-activedescendant");
+    this._selectedButton = null;
+  },
+};
new file mode 100755
--- /dev/null
+++ b/experiments/unified-urlbar/code/content/Telemetry.jsm
@@ -0,0 +1,137 @@
+this.EXPORTED_SYMBOLS = [
+  "Telemetry",
+];
+
+const SEARCH_SUGGESTIONS_OPT_IN_CHOICE_PREF =
+  "browser.urlbar.userMadeSearchSuggestionsChoice";
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource:///modules/BrowserUITelemetry.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+var gBranch = "control";
+var gValues = new Map();
+
+this.Telemetry = Object.freeze({
+
+  init(branch) {
+    gBranch = branch;
+    addSearchSuggestionsOptInTelemetry();
+    Services.obs.addObserver(this, "autocomplete-did-enter-text", false);
+  },
+
+  destroy() {
+    Services.obs.removeObserver(this, "autocomplete-did-enter-text", false);
+    Preferences.ignore(SEARCH_SUGGESTIONS_OPT_IN_CHOICE_PREF);
+    gValues.clear();
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    let input = aSubject.QueryInterface(Ci.nsIAutoCompleteInput);
+    if (!input || input.id != "urlbar" || input.inPrivateContext ||
+        input.popup.selectedIndex == -1) {
+      return;
+    }
+    let controller = input.popup.view.QueryInterface(Ci.nsIAutoCompleteController);
+    let idx = input.popup.selectedIndex;
+    let value = controller.getValueAt(idx);
+    let action = input._parseActionUrl(value);
+    let actionType;
+    if (action) {
+      actionType = action.type == "searchengine" && action.params.searchSuggestion ?
+                      "searchsuggestion" : action.type;
+    }
+    if (actionType == "searchengine") {
+      this.incrementValue("searchByDefaultEngine");
+    } else if (actionType == "searchSuggestion") {
+      this.incrementValue("searchBySuggestion");
+    }
+  },
+
+  /**
+   * Registers the presence of an event.
+   *
+   * @param eventName The data is logged with this name.
+   */
+  incrementValue(key, optionalData={}) {
+    // Since we only care about search volume, ignore the rare cases where a
+    // search opens in a new tab (currently onlye CTRL + Go button) and always
+    // set where to "current".
+    function recordOneOff(engine, source="unknown", type="unknown") {
+        let engineId = engine ? engine.identifier ? engine.identifier
+                                                  : "other-" + engine.name
+                              : "other";
+        BrowserUITelemetry.countOneoffSearchEvent(`${engineId}.${source}`, type,
+                                                  "current");
+    }
+
+    // Increment "search" / "urlbar" countable.
+    // Since we only care about search volume, don't report details, like the
+    // suggestion index clicked, to countSearchEvent.
+    function recordSearch(engine=Services.search.currentEngine, source) {
+      BrowserUITelemetry.countSearchEvent(source, null);
+      let engineId = engine ? engine.identifier ? engine.identifier
+                                                : "other-" + engine.name
+                            : "other";
+      let count = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS");
+      count.add(`${engineId}.${source}`);
+    }
+
+    switch(key) {
+      case "searchSettingsClicked":
+        BrowserUITelemetry.countSearchSettingsEvent("urlbar");
+        break;
+      case "searchByReturnKeyOnOneOffButton":
+        recordOneOff(optionalData.engine, "urlbar-oneoff", "key");
+        recordSearch(optionalData.engine, "urlbar");
+        break;
+      case "searchByClickOnOneOffButton":
+        recordOneOff(optionalData.engine, "urlbar-oneoff", "mouse");
+        recordSearch(optionalData.engine, "urlbar");
+        break;
+      case "searchByDefaultEngine":
+        // Already increments "search" / "urlbar" countable, it must also
+        // increment oneoff countable for the current engine.
+        // We don't have data about mouse or keyboard interaction here yet, so
+        // just pass unknown for the type.
+        recordOneOff(Services.search.currentEngine, "urlbar", "unknown");
+        break;
+      default:
+        // This data is currently unused, but stored for reference.
+        let val = gValues.get(key) || 0;
+        gValues.set(key, val);
+    }
+  },
+
+  /**
+   * Registers a key-value.
+   *
+   * @param key The data is logged with this name.
+   * @param value The value of the data.
+   */
+  setValue(key, value) {
+    // This data is currently unused, but stored for reference.
+    gValues.set(key, value);
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver ]),
+});
+
+/**
+ * Collects data about the user's urlbar search suggestions opt-in choice, or
+ * if none has been made yet, adds a pref observer for it.
+ */
+function addSearchSuggestionsOptInTelemetry() {
+  let userMadeChoice = Preferences.get(SEARCH_SUGGESTIONS_OPT_IN_CHOICE_PREF);
+  Telemetry.setValue("userMadeSuggestionsChoice", userMadeChoice);
+  if (!userMadeChoice) {
+    Preferences.observe(SEARCH_SUGGESTIONS_OPT_IN_CHOICE_PREF, () => {
+      Preferences.ignore(SEARCH_SUGGESTIONS_OPT_IN_CHOICE_PREF);
+      addSearchSuggestionsOptInTelemetry();
+    });
+  }
+  let optedIn = Preferences.get("browser.urlbar.suggest.searches");
+  Telemetry.setValue("suggestionsEnabled", optedIn);
+}
new file mode 100755
--- /dev/null
+++ b/experiments/unified-urlbar/code/content/gear.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 32 32">
+  <style>
+    use:not(:target) {
+      display: none;
+    }
+    use {
+      fill: GrayText;
+    }
+    use[id$="-inverted"] {
+      fill: highlighttext;
+    }
+  </style>
+  <defs>
+    <path id="glyphShape-gear" d="M28,16c0-1.7,0.9-3.1,2-3.3c-0.4-1.5-0.9-2.9-1.7-4.2c-0.9,0.7-2.6,0.3-3.8-0.9c-1.2-1.2-1.6-2.8-0.9-3.8 c-1.3-0.8-2.7-1.4-4.2-1.7c-0.2,1.1-1.6,2-3.3,2S13,3.1,12.8,2c-1.5,0.4-2.9,0.9-4.2,1.7c0.7,0.9,0.3,2.6-0.9,3.8 c-1.4,1.1-3,1.5-4,0.9C2.9,9.7,2.4,11.2,2,12.7c1.1,0.2,2,1.6,2,3.3s-0.9,3.1-2,3.3c0.4,1.5,0.9,2.9,1.7,4.2 c0.9-0.7,2.6-0.3,3.8,0.9c1.2,1.2,1.6,2.8,0.9,3.8c1.3,0.8,2.7,1.4,4.2,1.7c0.2-1.1,1.6-2,3.3-2s3.1,0.9,3.3,2 c1.5-0.4,2.9-0.9,4.2-1.7c-0.7-0.9-0.3-2.6,0.9-3.8c1.3-1.2,2.8-1.6,3.8-0.9c0.8-1.3,1.4-2.7,1.7-4.2C28.9,19.1,28,17.7,28,16z M16,24c-4.4,0-8-3.6-8-8s3.6-8,8-8s8,3.6,8,8S20.4,24,16,24z"/>
+  </defs>
+  <use id="gear" xlink:href="#glyphShape-gear"/>
+  <use id="gear-inverted" xlink:href="#glyphShape-gear"/>
+</svg>
new file mode 100755
--- /dev/null
+++ b/experiments/unified-urlbar/code/content/style.css
@@ -0,0 +1,80 @@
+.urlbar-header {
+  font-size: 10px;
+  font-weight: normal;
+  margin: 0;
+  padding: 3px 6px;
+  color: #666;
+}
+
+.urlbar-header > label {
+  margin-top: 2px !important;
+  margin-bottom: 2px !important;
+}
+
+.urlbar-current-input > label {
+  margin: 2px 0 !important;
+}
+
+.urlbar-input-value {
+  color: black;
+}
+
+.urlbar-one-offs {
+  margin: 0 0 !important;
+}
+
+.urlbar-engine-one-off-item {
+  -moz-appearance: none;
+  display: inline-block;
+  border: none;
+  min-width: 48px;
+  height: 32px;
+  margin: 0 0;
+  padding: 0 0;
+}
+
+.urlbar-engine-one-off-item:not(.dummy) {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAWCAYAAAABxvaqAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gofECQNNVW2/AAAABBJREFUGFdjOHPmzH8GehEA/KpKg9YTf4AAAAAASUVORK5CYII=');
+  background-repeat: no-repeat;
+  background-position: right center;
+}
+
+.urlbar-engine-one-off-item:not(.last-row) {
+  box-sizing: content-box;
+  border-bottom: 1px solid #ccc;
+}
+
+.urlbar-engine-one-off-item.last-of-row {
+  background-image: none;
+}
+
+.urlbar-engine-one-off-item[selected] {
+  background-color: Highlight;
+  background-image: none;
+}
+
+.urlbar-engine-one-off-item > .button-box > .button-text {
+  display: none;
+}
+
+.urlbar-engine-one-off-item > .button-box > .button-icon {
+  -moz-margin-start: 0;
+  width: 16px;
+  height: 16px;
+}
+
+#urlbar-search-settings2 {
+  list-style-image: url("chrome://unified-urlbar/content/gear.svg#gear");
+  background-position: left center;
+  border-bottom: none;
+}
+
+#urlbar-search-settings2:hover {
+  list-style-image: url("chrome://unified-urlbar/content/gear.svg#gear-inverted");
+  background-color: Highlight;
+}
+
+#urlbar-search-footer2 {
+  border-top: 1px solid hsla(210, 4%, 10%, 0.14);
+  background-color: hsla(210, 4%, 10%, 0.07);
+}
new file mode 100755
--- /dev/null
+++ b/experiments/unified-urlbar/code/install.rdf
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>unified-urlbar@experiments.mozilla.org</em:id>
+    <em:version>1.0.0</em:version>
+    <em:type>128</em:type>
+    <em:bootstrap>true</em:bootstrap>
+    <em:unpack>false</em:unpack>
+
+    <!-- Firefox -->
+    <em:targetApplication>
+      <Description>
+        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+        <em:minVersion>43.0</em:minVersion>
+        <em:maxVersion>47.0</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+    <em:name>Unified Urlbar Experiment</em:name>
+    <em:description>
+      An experimental add-on testing the unified urlbar experience.
+    </em:description>
+    <em:aboutURL>https://bugzilla.mozilla.org/show_bug.cgi?id=1219505</em:aboutURL>
+  </Description>
+</RDF>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..57302bd2cf7ea08b4086624b7b6603c31c98d548
GIT binary patch
literal 15954
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
Lejz)~KUe<?0E1CH
new file mode 100755
--- /dev/null
+++ b/experiments/unified-urlbar/filter.js
@@ -0,0 +1,3 @@
+function filter(c) {
+  return c.telemetryEnvironment.settings.telemetryEnabled;
+}
new file mode 100755
--- /dev/null
+++ b/experiments/unified-urlbar/manifest.json
@@ -0,0 +1,18 @@
+{
+  "publish" : true,
+  "priority" : 2,
+  "name" : "Unified Search in Urlbar",
+  "description" : "Adds search features to the urlbar and moves the search bar to the customization panel",
+  "info" : "<p><a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1219505\">Related bug</a></p>",
+  "manifest" : {
+    "id" : "unified-urlbar@experiments.mozilla.org",
+    "startTime" : 1452470400,
+    "endTime" : 1456272000,
+    "maxActiveSeconds" : 1209600,
+    "appName" : ["Firefox"],
+    "channel" : ["beta"],
+    "minVersion" : "44.0",
+    "maxVersion" : "45.*",
+    "sample" : 0.1
+  }
+}