From 7dee39df6fe657abfae82f91308a2561a04fd224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A3o=20Gottwald?= Date: Thu, 13 Sep 2018 18:38:07 +0200 Subject: [PATCH] Bug 1477985 - Implement basic UrlbarInput and UrlbarView classes and a hidden pref for using them. r=standard8 --- browser/app/profile/firefox.js | 2 + browser/base/content/browser.css | 4 + browser/base/content/browser.js | 57 +++++--- browser/base/content/browser.xul | 16 +++ .../browser_urlbar_keyed_search.js | 26 ++-- .../test/performance/browser_urlbar_search.js | 19 ++- browser/base/content/urlbarBindings.xml | 38 ++++- browser/components/urlbar/.eslintrc.js | 8 +- browser/components/urlbar/UrlbarInput.jsm | 101 ++++++++++++++ browser/components/urlbar/UrlbarView.jsm | 131 ++++++++++++++++++ browser/components/urlbar/moz.build | 2 + browser/themes/linux/browser.css | 1 - browser/themes/osx/browser.css | 1 - .../themes/shared/urlbar-autocomplete.inc.css | 88 ++++++++++++ browser/themes/windows/browser.css | 1 - 15 files changed, 448 insertions(+), 47 deletions(-) create mode 100644 browser/components/urlbar/UrlbarInput.jsm create mode 100644 browser/components/urlbar/UrlbarView.jsm diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 2f8df362de18..1d254d408103 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -335,6 +335,8 @@ pref("browser.urlbar.switchTabs.adoptIntoActiveWindow", false); // should be opened in new tabs by default. pref("browser.urlbar.openintab", false); +pref("browser.urlbar.quantumbar", false); + pref("browser.altClickSave", false); // Enable logging downloads operations to the Console. diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index c86f91650ad9..098547bf5388 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -536,6 +536,10 @@ toolbar:not(#TabsToolbar) > #personal-bookmarks { } #urlbar { + -moz-binding: url(chrome://browser/content/urlbarBindings.xml#legacy-urlbar); +} + +#urlbar[quantumbar] { -moz-binding: url(chrome://browser/content/urlbarBindings.xml#urlbar); } diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 9bd839d1f20b..95a32a916cc9 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -67,6 +67,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { Translation: "resource:///modules/translation/Translation.jsm", UITour: "resource:///modules/UITour.jsm", UpdateUtils: "resource://gre/modules/UpdateUtils.jsm", + UrlbarInput: "resource:///modules/UrlbarInput.jsm", Utils: "resource://gre/modules/sessionstore/Utils.jsm", Weave: "resource://services-sync/main.js", WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm", @@ -251,27 +252,41 @@ if (AppConstants.platform != "macosx") { var gEditUIVisible = true; } -/* globals gNavToolbox, gURLBar:true */ -[ - ["gNavToolbox", "navigator-toolbox"], - ["gURLBar", "urlbar"], -].forEach(function(elementGlobal) { - var [name, id] = elementGlobal; - Object.defineProperty(window, name, { - configurable: true, - enumerable: true, - get() { - var element = document.getElementById(id); - if (!element) - return null; - delete window[name]; - return window[name] = element; - }, - set(val) { - delete window[name]; - return window[name] = val; - }, - }); +Object.defineProperty(this, "gURLBar", { + configurable: true, + enumerable: true, + get() { + delete this.gURLBar; + + let element = document.getElementById("urlbar"); + + // For now, always use the legacy implementation in the first window to + // have a usable address bar e.g. for accessing about:config. + if (BrowserWindowTracker.windowCount <= 1 || + !Services.prefs.getBoolPref("browser.urlbar.quantumbar", false)) { + return this.gURLBar = element; + } + + // Disable the legacy XBL binding. + element.setAttribute("quantumbar", "true"); + + // Re-focus the input field if it was focused before switching bindings. + if (element.hasAttribute("focused")) { + element.inputField.focus(); + } + + return this.gURLBar = + new UrlbarInput(element, document.getElementById("urlbar-results")); + }, +}); + +Object.defineProperty(this, "gNavToolbox", { + configurable: true, + enumerable: true, + get() { + delete this.gNavToolbox; + return this.gNavToolbox = document.getElementById("navigator-toolbox"); + }, }); // Smart getter for the findbar. If you don't wish to force the creation of diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index f2e3b4d8f930..e292bf3c6503 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -213,6 +213,22 @@ xmlns="http://www.w3.org/1999/xhtml" level="parent" overflowpadding="15" /> + + + - + + + + + + + + + + + + + + + + + + + diff --git a/browser/components/urlbar/.eslintrc.js b/browser/components/urlbar/.eslintrc.js index 6af380d75599..ad8c1e83a839 100644 --- a/browser/components/urlbar/.eslintrc.js +++ b/browser/components/urlbar/.eslintrc.js @@ -5,11 +5,11 @@ module.exports = { "mozilla/var-only-at-top-level": "error", "require-jsdoc": ["error", { "require": { - "FunctionDeclaration": true, - "MethodDefinition": true, + "FunctionDeclaration": false, + "MethodDefinition": false, "ClassDeclaration": true, - "ArrowFunctionExpression": true, - "FunctionExpression": true + "ArrowFunctionExpression": false, + "FunctionExpression": false } }], "valid-jsdoc": ["error", { diff --git a/browser/components/urlbar/UrlbarInput.jsm b/browser/components/urlbar/UrlbarInput.jsm new file mode 100644 index 000000000000..ed53a67fa5ad --- /dev/null +++ b/browser/components/urlbar/UrlbarInput.jsm @@ -0,0 +1,101 @@ +/* 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/. */ + +"use strict"; + +var EXPORTED_SYMBOLS = ["UrlbarInput"]; + +ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetters(this, { + UrlbarView: "resource:///modules/UrlbarView.jsm", +}); + +/** + * Represents the urlbar . + * Also forwards important textbox properties and methods. + */ +class UrlbarInput { + /** + * @param {object} textbox + * The element. + * @param {object} panel + * The element. + */ + constructor(textbox, panel) { + this.textbox = textbox; + this.panel = panel; + this.view = new UrlbarView(this); + this.valueIsTyped = false; + this.userInitiatedFocus = false; + + const METHODS = ["addEventListener", "removeEventListener", + "setAttribute", "hasAttribute", "removeAttribute", "getAttribute", + "focus", "blur", "select"]; + const READ_ONLY_PROPERTIES = ["focused", "inputField", "editor"]; + const READ_WRITE_PROPERTIES = ["value", "placeholder", "readOnly", + "selectionStart", "selectionEnd"]; + + for (let method of METHODS) { + this[method] = (...args) => { + this.textbox[method](...args); + }; + } + + for (let property of READ_ONLY_PROPERTIES) { + Object.defineProperty(this, property, { + enumerable: true, + get() { + return this.textbox[property]; + }, + }); + } + + for (let property of READ_WRITE_PROPERTIES) { + Object.defineProperty(this, property, { + enumerable: true, + get() { + return this.textbox[property]; + }, + set(val) { + return this.textbox[property] = val; + }, + }); + } + + this.addEventListener("input", this); + } + + formatValue() { + } + + closePopup() { + this.view.close(); + } + + openResults() { + this.view.open(); + } + + /** + * Passes DOM events for the textbox to the _on methods. + * @param {Event} event + * DOM event from the . + */ + handleEvent(event) { + let methodName = "_on" + event.type; + if (methodName in this) { + this[methodName](event); + } else { + throw "Unrecognized urlbar event: " + event.type; + } + } + + // Private methods below. + /* eslint-disable require-jsdoc */ + + _oninput(event) { + this.openResults(); + } +} diff --git a/browser/components/urlbar/UrlbarView.jsm b/browser/components/urlbar/UrlbarView.jsm new file mode 100644 index 000000000000..3a4cbb659f03 --- /dev/null +++ b/browser/components/urlbar/UrlbarView.jsm @@ -0,0 +1,131 @@ +/* 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/. */ + +"use strict"; + +var EXPORTED_SYMBOLS = ["UrlbarView"]; + +/** + * Receives and displays address bar autocomplete results. + */ +class UrlbarView { + /** + * @param {UrlbarInput} urlbar + * The UrlbarInput instance belonging to this UrlbarView instance. + */ + constructor(urlbar) { + this.urlbar = urlbar; + this.panel = urlbar.panel; + this.document = urlbar.panel.ownerDocument; + this.window = this.document.defaultView; + + this._mainContainer = this.panel.querySelector(".urlbarView-body-inner"); + this._rows = this.panel.querySelector(".urlbarView-results"); + + // For the horizontal fade-out effect, set the overflow attribute on result + // rows when they overflow. + this._rows.addEventListener("overflow", event => { + if (event.target.classList.contains("urlbarView-row-inner")) { + event.target.toggleAttribute("overflow", true); + } + }); + this._rows.addEventListener("underflow", event => { + if (event.target.classList.contains("urlbarView-row-inner")) { + event.target.toggleAttribute("overflow", false); + } + }); + } + + /** + * Opens the autocomplete results popup. + */ + open() { + this.panel.removeAttribute("hidden"); + + let panelDirection = this.panel.style.direction; + if (!panelDirection) { + panelDirection = this.panel.style.direction = + this.window.getComputedStyle(this.urlbar.textbox).direction; + } + + // Make the panel span the width of the window. + let documentRect = + this._getBoundsWithoutFlushing(this.document.documentElement); + let width = documentRect.right - documentRect.left; + this.panel.setAttribute("width", width); + + // Subtract two pixels for left and right borders on the panel. + this._mainContainer.style.maxWidth = (width - 2) + "px"; + + this.panel.openPopup(this.urlbar.textbox.closest("toolbar"), "after_end", 0, -1); + + this._rows.textContent = ""; + for (let i = 0; i < 12; i++) { + this._addRow(); + } + this._rows.firstElementChild.toggleAttribute("selected", true); + } + + /** + * Closes the autocomplete results popup. + */ + close() { + } + + // Private methods below. + /* eslint-disable require-jsdoc */ + + _getBoundsWithoutFlushing(element) { + return this.window.windowUtils.getBoundsWithoutFlushing(element); + } + + _createElement(name) { + return this.document.createElementNS("http://www.w3.org/1999/xhtml", name); + } + + _addRow() { + const SWITCH_TO_TAB = Math.random() < .3; + + let item = this._createElement("div"); + item.className = "urlbarView-row"; + if (SWITCH_TO_TAB) { + item.setAttribute("action", "switch-to-tab"); + } + + let content = this._createElement("span"); + content.className = "urlbarView-row-inner"; + item.appendChild(content); + + let actionIcon = this._createElement("span"); + actionIcon.className = "urlbarView-action-icon"; + content.appendChild(actionIcon); + + let favicon = this._createElement("span"); + favicon.className = "urlbarView-favicon"; + content.appendChild(favicon); + + let title = this._createElement("span"); + title.className = "urlbarView-title"; + do { + title.textContent += "foo bar "; + } while (Math.random() < .5); + content.appendChild(title); + + let secondary = this._createElement("span"); + secondary.className = "urlbarView-secondary"; + if (SWITCH_TO_TAB) { + secondary.classList.add("urlbarView-action"); + secondary.textContent = "Switch to Tab"; + } else { + secondary.classList.add("urlbarView-url"); + secondary.textContent = "http://www"; + while (Math.random() < .95) { + secondary.textContent += ".xyz"; + } + } + content.appendChild(secondary); + + this._rows.appendChild(item); + } +} diff --git a/browser/components/urlbar/moz.build b/browser/components/urlbar/moz.build index 95bbfc145b92..c5f9d3b168e8 100644 --- a/browser/components/urlbar/moz.build +++ b/browser/components/urlbar/moz.build @@ -7,7 +7,9 @@ with Files("**"): EXTRA_JS_MODULES += [ 'UrlbarController.jsm', + 'UrlbarInput.jsm', 'UrlbarTokenizer.jsm', + 'UrlbarView.jsm', ] XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini'] diff --git a/browser/themes/linux/browser.css b/browser/themes/linux/browser.css index a8378defaa07..11289a820413 100644 --- a/browser/themes/linux/browser.css +++ b/browser/themes/linux/browser.css @@ -4,7 +4,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ %endif -@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); @namespace html url("http://www.w3.org/1999/xhtml"); %include ../shared/browser.inc.css diff --git a/browser/themes/osx/browser.css b/browser/themes/osx/browser.css index c37d65a35f6d..71f297c5d094 100644 --- a/browser/themes/osx/browser.css +++ b/browser/themes/osx/browser.css @@ -5,7 +5,6 @@ %include shared.inc %define toolbarButtonPressed :hover:active:not([disabled="true"]):not([cui-areatype="menu-panel"]) -@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); @namespace html url("http://www.w3.org/1999/xhtml"); %include ../shared/browser.inc.css diff --git a/browser/themes/shared/urlbar-autocomplete.inc.css b/browser/themes/shared/urlbar-autocomplete.inc.css index 86e9970c7e28..7c256d427fd0 100644 --- a/browser/themes/shared/urlbar-autocomplete.inc.css +++ b/browser/themes/shared/urlbar-autocomplete.inc.css @@ -22,6 +22,94 @@ --urlbar-popup-action-color: #30e60b; } +#urlbar-results { + -moz-appearance: none; + background: var(--autocomplete-popup-background); + color: var(--autocomplete-popup-color); + border: 1px solid var(--autocomplete-popup-border-color); +} + +.urlbarView-body-inner { + box-sizing: border-box; +} + +.urlbarView-results { + padding: 5px; + white-space: nowrap; +} + +.urlbarView-row { + padding: 7px 50px; + border-radius: 2px; +} + +.urlbarView-row-inner { + overflow: hidden; + display: block; +} + +.urlbarView-row-inner[overflow] { + mask-image: linear-gradient(to left, transparent, black 2em); +} + +.urlbarView-row:hover { + background: var(--arrowpanel-dimmed); +} + +.urlbarView-row[selected] { + background: var(--autocomplete-popup-highlight-background); + color: var(--autocomplete-popup-highlight-color); +} + +.urlbarView-action-icon, +.urlbarView-favicon { + display: inline-block; + vertical-align: middle; + width: 16px; + height: 16px; + margin-inline-end: .6em; +} + +.urlbarView-favicon { + border-radius: 8px; + background: currentcolor; + opacity: 0.6; +} + +.urlbarView-row[action=switch-to-tab] > .row-inner > .action-icon { + height: 8px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + background: currentcolor; + opacity: 0.5; +} + +.urlbarView-title { + font-size: 1.05em; +} + +.urlbarView-title::after { + content: "\2014"; + color: var(--panel-disabled-color); + margin: 0 .4em; +} + +.urlbarView-secondary { + color: var(--urlbar-popup-action-color); + font-size: 0.9em; +} + +.urlbarView-url { + color: var(--urlbar-popup-url-color); +} + +.urlbarView-row[selected] > .urlbarView-row-inner > .urlbarView-title::after, +.urlbarView-row[selected] > .urlbarView-row-inner > .urlbarView-secondary { + color: inherit; +} + +/* legacy URL bar styles below */ + #PopupAutoCompleteRichResult, #PopupSearchAutoComplete { background: var(--autocomplete-popup-background); diff --git a/browser/themes/windows/browser.css b/browser/themes/windows/browser.css index 81accc94252c..e79e700a0f3a 100644 --- a/browser/themes/windows/browser.css +++ b/browser/themes/windows/browser.css @@ -2,7 +2,6 @@ * 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/. */ -@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); @namespace html url("http://www.w3.org/1999/xhtml"); %include ../shared/browser.inc.css