Bug 622578 - form helper navigation bar should not obscure web content when only the NEXT / PREV buttons are shown [r=mfinkle]

This commit is contained in:
Vivien Nicolas 2011-02-24 02:07:51 +01:00
parent bf65597199
commit d990330b8a
11 changed files with 366 additions and 408 deletions

View File

@ -45,7 +45,7 @@ var MenuListHelperUI = {
// Add selected as a class name instead of an attribute to not being overidden
// by the richlistbox behavior (it sets the "current" and "selected" attribute
item.setAttribute("class", "menulist-command prompt-button" + (child.selected ? " selected" : ""));
item.setAttribute("class", "option-command prompt-button" + (child.selected ? " selected" : ""));
let image = document.createElement("image");
image.setAttribute("src", child.image || "");

View File

@ -3,20 +3,36 @@
* Supports simultaneous selection of choices and group headers.
*/
var SelectHelperUI = {
_list: null,
_selectedIndexes: null,
_list: null,
get _panel() {
delete this._panel;
return this._panel = document.getElementById("select-container");
get _container() {
delete this._container;
return this._container = document.getElementById("select-container");
},
show: function selectHelperShow(aList) {
get _listbox() {
delete this._listbox;
return this._listbox = document.getElementById("select-commands");
},
get _title() {
delete this._title;
return this._title = document.getElementById("select-title");
},
show: function selectHelperShow(aList, aTitle) {
if (this._list)
this.reset();
this._list = aList;
this._container = document.getElementById("select-list");
// The element label is used as a title to give more context
this._title.value = aTitle || "";
this._container.setAttribute("multiple", aList.multiple ? "true" : "false");
// Save already selected indexes to detect what has changed when a a new
// element is selected
this._selectedIndexes = this._getSelectedIndexes();
let firstSelected = null;
@ -25,10 +41,18 @@ var SelectHelperUI = {
let choices = aList.choices;
for (let i = 0; i < choices.length; i++) {
let choice = choices[i];
let item = document.createElement("option");
item.className = "chrome-select-option";
let item = document.createElement("listitem");
item.setAttribute("class", "option-command listitem-iconic prompt-button");
item.setAttribute("image", "");
item.setAttribute("flex", "1");
item.setAttribute("crop", "center");
item.setAttribute("label", choice.text);
choice.disabled ? item.setAttribute("disabled", choice.disabled)
choice.selected ? item.setAttribute("selected", "true")
: item.removeAttribute("selected");
choice.disabled ? item.setAttribute("disabled", "true")
: item.removeAttribute("disabled");
fragment.appendChild(item);
@ -48,62 +72,46 @@ var SelectHelperUI = {
firstSelected = firstSelected || item;
}
}
this._container.appendChild(fragment);
this._panel.hidden = false;
this._panel.height = this._panel.getBoundingClientRect().height;
if (!this._docked)
BrowserUI.pushPopup(this, this._panel);
this._listbox.appendChild(fragment);
this._container.hidden = false;
BrowserUI.pushPopup(this, this._container);
this._scrollElementIntoView(firstSelected);
this._container.addEventListener("click", this, false);
this._panel.addEventListener("overflow", this, true);
},
window.addEventListener("resize", this, true);
this.sizeToContent();
dock: function selectHelperDock(aContainer) {
aContainer.insertBefore(this._panel, aContainer.lastChild);
this.resize();
this._docked = true;
},
undock: function selectHelperUndock() {
let rootNode = Elements.stack;
rootNode.insertBefore(this._panel, rootNode.lastChild);
this._panel.style.maxHeight = "";
this._docked = false;
let evt = document.createEvent("UIEvents");
evt.initUIEvent("SelectUI", true, false, window, true);
window.dispatchEvent(evt);
},
reset: function selectHelperReset() {
this._updateControl();
let empty = this._container.cloneNode(false);
this._container.parentNode.replaceChild(empty, this._container);
this._container = empty;
while (this._listbox.hasChildNodes())
this._listbox.removeChild(this._listbox.lastChild);
this._list = null;
this._title.value = "";
this._selectedIndexes = null;
this._panel.height = "";
BrowserUI.popPopup(this);
},
resize: function selectHelperResize() {
this._panel.style.maxHeight = (window.innerHeight / 1.8) + "px";
sizeToContent: function selectHelperSizeToContent() {
this._container.firstChild.maxHeight = window.innerHeight * 0.75;
},
hide: function selectHelperResize() {
hide: function selectHelperHide() {
if (!this._list)
return;
window.removeEventListener("resize", this, true);
this._container.removeEventListener("click", this, false);
this._panel.removeEventListener("overflow", this, true);
this._panel.hidden = true;
if (this._docked)
this.undock();
else
BrowserUI.popPopup(this);
this._container.hidden = true;
this.reset();
let evt = document.createEvent("UIEvents");
evt.initUIEvent("SelectUI", true, false, window, true);
window.dispatchEvent(evt);
},
unselectAll: function selectHelperUnselectAll() {
@ -122,12 +130,19 @@ var SelectHelperUI = {
return;
let choices = this._list.choices;
for (let i = 0; i < this._container.childNodes.length; i++) {
let option = this._container.childNodes[i];
let children = this._listbox.childNodes;
for (let i = 0; i < children.length; i++) {
let option = children[i];
if (option.optionIndex == aIndex) {
option.selected = true;
this._choices[i].selected = true;
this._scrollElementIntoView(option);
let choice = choices[i];
if (this._list.multiple) {
choice.selected = !choice.selected;
option.setAttribute("selected", choice.selected);
} else {
option.setAttribute("selected", "true");
choice.selected = true;
this._scrollElementIntoView(option);
}
break;
}
}
@ -155,7 +170,7 @@ var SelectHelperUI = {
let index = -1;
this._forEachOption(
function(aItem, aIndex) {
if (aElement.optionIndex == aItem.optionIndex)
if (aItem.optionIndex == aElement.optionIndex)
index = aIndex;
}
);
@ -163,20 +178,19 @@ var SelectHelperUI = {
if (index == -1)
return;
let scrollBoxObject = this._container.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
let scrollBoxObject = this._listbox.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
let itemHeight = aElement.getBoundingClientRect().height;
let visibleItemsCount = this._container.boxObject.height / itemHeight;
let visibleItemsCount = this._listbox.boxObject.height / itemHeight;
if ((index + 1) > visibleItemsCount) {
let delta = Math.ceil(visibleItemsCount / 2);
scrollBoxObject.scrollTo(0, ((index + 1) - delta) * itemHeight);
}
else {
} else {
scrollBoxObject.scrollTo(0, 0);
}
},
_forEachOption: function _selectHelperForEachOption(aCallback) {
let children = this._container.children;
let children = this._listbox.childNodes;
for (let i = 0; i < children.length; i++) {
let item = children[i];
if (!item.hasOwnProperty("optionIndex"))
@ -210,18 +224,24 @@ var SelectHelperUI = {
let item = aEvent.target;
if (item && item.hasOwnProperty("optionIndex")) {
if (this._list.multiple) {
// Toggle the item state
item.selected = !item.selected;
}
else {
item.classList.toggle("selected");
} else {
this.unselectAll();
// Select the new one and update the control
item.selected = true;
item.classList.add("selected");
}
this.onSelect(item.optionIndex, item.selected, !this._list.multiple);
this.onSelect(item.optionIndex, item.classList.contains("selected"), !this._list.multiple);
} else if (item == this._container) {
// The click is outside the listbox area, so we need to hide the list
// This is used instead of the popup mechanism otherwise the click
// will be dispatched while we want to inhibit it (I think)
this.hide();
}
break;
case "resize":
this.sizeToContent();
break;
}
},
@ -234,12 +254,11 @@ var SelectHelperUI = {
Browser.selectedBrowser.messageManager.sendAsyncMessage("FormAssist:ChoiceSelect", json);
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-button-element.html#the-select-element
// The list will be closed as soon as the user click if it is not multiple,
// while list with multiple choices have a button to close it
if (!this._list.multiple) {
this._updateControl();
// Update the selectedIndex so the field will fire a new change event if
// needed
this._selectedIndexes = [aIndex];
this.hide();
}
}
};

View File

@ -1477,13 +1477,10 @@
</binding>
<binding id="content-navigator">
<content pack="end">
<children includes="vbox"/>
<xul:hbox class="content-navigator-box panel-dark" pack="end">
<children includes="textbox|arrowscrollbox"/>
<xul:toolbarbutton anonid="previous-button" class="content-navigator-item previous-button" xbl:inherits="command=previous"/>
<xul:toolbarbutton anonid="next-button" class="content-navigator-item next-button" xbl:inherits="command=next"/>
</xul:hbox>
<content class="content-navigator-box" orient="horizontal" pack="end">
<children includes="textbox"/>
<xul:button anonid="previous-button" class="content-navigator-item previous-button" xbl:inherits="command=previous"/>
<xul:button anonid="next-button" class="content-navigator-item next-button" xbl:inherits="command=next"/>
</content>
<implementation>
@ -1495,26 +1492,23 @@
document.getAnonymousElementByAttribute(this, "anonid", "next-button");
</field>
<field name="_spacer">
document.getElementById(this.getAttribute("spacer"));
</field>
<method name="contentHasChanged">
<body><![CDATA[
let height = Math.floor(this.getBoundingClientRect().height);
let oldHeight = parseInt(this._spacer.getAttribute("height"));
this.top = window.innerHeight - height;
this._spacer.setAttribute("height", height);
if (!this.isActive)
return;
if (height != oldHeight) {
let event = document.createEvent("Events");
event.initEvent("SizeChanged", true, false);
this.dispatchEvent(event);
}
// There is a race condition with getBoundingClientRect and when the
// box is displayed, the padding is ignored in the size calculation
this.getBoundingClientRect();
setTimeout(function(self) {
let height = Math.floor(self.getBoundingClientRect().height);
self.top = window.innerHeight - height;
}, 0, this);
]]></body>
</method>
<property name="isActive" onget="return !this._spacer.hidden;"/>
<property name="isActive" onget="return !!this.model;"/>
<field name="model">null</field>
<method name="show">
@ -1537,7 +1531,6 @@
this.model = aModel;
this.contentHasChanged();
this._spacer.hidden = false;
]]></body>
</method>
@ -1549,9 +1542,8 @@
this.removeAttribute("close");
this.removeAttribute("type");
this.model = null;
this.contentHasChanged();
this._spacer.hidden = true;
this.model = null;
]]></body>
</method>
</implementation>

View File

@ -429,8 +429,7 @@ var BrowserUI = {
popup.width = windowW;
// content navigator helper
let contentHelper = document.getElementById("content-navigator");
contentHelper.top = windowH - contentHelper.getBoundingClientRect().height;
document.getElementById("content-navigator").contentHasChanged();
},
init: function() {

View File

@ -172,7 +172,7 @@ var Browser = {
/* handles web progress management for open browsers */
Elements.browsers.webProgress = new Browser.WebProgress();
let keySender = new ContentCustomKeySender(Elements.browsers);
this.keySender = new ContentCustomKeySender(Elements.browsers);
let mouseModule = new MouseModule();
let gestureModule = new GestureModule(Elements.browsers);
let scrollWheelModule = new ScrollwheelModule(Elements.browsers);
@ -318,9 +318,6 @@ var Browser = {
#if MOZ_PLATFORM_MAEMO == 6
os.addObserver(ViewableAreaObserver, "softkb-change", false);
#endif
Elements.contentNavigator.addEventListener("SizeChanged", function() {
ViewableAreaObserver.update();
}, true);
window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess();
@ -1113,7 +1110,6 @@ Browser.MainDragger = function MainDragger() {
Elements.browsers.addEventListener("PanBegin", this, false);
Elements.browsers.addEventListener("PanFinished", this, false);
Elements.contentNavigator.addEventListener("SizeChanged", this, false);
};
Browser.MainDragger.prototype = {
@ -1171,9 +1167,9 @@ Browser.MainDragger.prototype = {
},
handleEvent: function handleEvent(aEvent) {
let browser = getBrowser();
switch (aEvent.type) {
case "PanBegin": {
let browser = Browser.selectedBrowser;
let width = ViewableAreaObserver.width, height = ViewableAreaObserver.height;
let contentWidth = browser.contentDocumentWidth * browser.scale;
let contentHeight = browser.contentDocumentHeight * browser.scale;
@ -1189,18 +1185,13 @@ Browser.MainDragger.prototype = {
this._showScrollbars();
break;
}
case "PanFinished": {
case "PanFinished":
this._hideScrollbars();
// Update the scroll position of the content
let browser = getBrowser();
browser._updateCSSViewport();
break;
}
case "SizeChanged":
let height = Elements.contentNavigator.getBoundingClientRect().height;
this._horizontalScrollbar.setAttribute("bottom", 2 + height);
break;
}
},
@ -2737,7 +2728,7 @@ var ViewableAreaObserver = {
},
get height() {
return (this._height || window.innerHeight) - Elements.contentNavigator.getBoundingClientRect().height;
return (this._height || window.innerHeight);
},
observe: function va_observe(aSubject, aTopic, aData) {

View File

@ -41,6 +41,7 @@
<?xml-stylesheet href="chrome://browser/skin/platform.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/forms.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
<!DOCTYPE window [
@ -274,16 +275,13 @@
<!-- vertical scrollbar -->
<box id="vertical-scroller" class="scroller" orient="vertical" right="2" top="0"/>
</stack>
<box id="content-navigator-spacer" hidden="true"/>
</vbox>
</vbox>
</scrollbox>
<!-- popup for content navigator helper -->
<vbox id="content-navigator" class="window-width" top="0" spacer="content-navigator-spacer">
<arrowscrollbox id="form-helper-autofill" collapsed="true" align="center" flex="1" orient="horizontal"
onclick="FormHelperUI.doAutoComplete(event.target);"/>
<textbox id="find-helper-textbox" class="search-bar content-navigator-item" oncommand="FindHelperUI.search(this.value)" oninput="FindHelperUI.updateCommands(this.value);" type="search" flex="1"/>
<vbox id="content-navigator" class="window-width" top="0">
<textbox id="find-helper-textbox" class="search-bar content-navigator-item" oncommand="FindHelperUI.search(this.value)" oninput="FindHelperUI.updateCommands(this.value);" type="search"/>
</vbox>
<!-- horizontal scrollbar -->
@ -349,6 +347,11 @@
</hbox>
</arrowbox>
<!-- Form Helper suggestions helper popup -->
<arrowbox id="form-helper-suggestions-container" flex="1" hidden="true" offset="0" top="0" left="0">
<arrowscrollbox id="form-helper-suggestions" align="center" flex="1" orient="horizontal" onclick="FormHelperUI.doAutoComplete(event.target);"/>
</arrowbox>
<arrowbox id="bookmark-popup" hidden="true" align="center" offset="12">
<label value="&bookmarkPopup.label;"/>
<separator class="thin"/>
@ -579,15 +582,16 @@
</vbox>
<!-- options dialog for select form field -->
<vbox id="select-container" hidden="true" pack="center">
<spacer id="select-spacer" flex="1000"/>
<vbox id="select-container-inner" class="dialog-dark" flex="1">
<scrollbox id="select-list" flex="1" orient="vertical"/>
<hbox id="select-buttons" pack="center">
<button id="select-buttons-done" label="&selectHelper.done;" oncommand="SelectHelperUI.hide();"/>
<vbox id="select-container" class="window-width window-height context-block" top="0" left="0" hidden="true" flex="1">
<vbox id="select-popup" class="dialog-dark">
<hbox pack="center">
<label id="select-title" class="options-title" align="center" crop="center" flex="1"/>
</hbox>
<scrollbox id="select-commands" onclick="if (event.target != this) SelectHelperUI.selectByIndex(event.target.optionIndex);" orient="vertical" flex="1"/>
<hbox pack="center">
<button label="&selectHelper.done;" oncommand="SelectHelperUI.hide();"/>
</hbox>
</vbox>
<spacer flex="1000"/>
</vbox>
<hbox id="context-container" class="window-width window-height context-block" top="0" left="0" hidden="true">
@ -656,6 +660,7 @@
<hbox id="menulist-container" class="window-width window-height context-block" top="0" left="0" hidden="true" flex="1">
<vbox id="menulist-popup" class="dialog-dark">
<label id="menulist-title" crop="center" flex="1"/>
<label id="menulist-title" class="options-title" crop="center" flex="0"/>
<richlistbox id="menulist-commands" class="prompt-buttons" onclick="if (event.target != this) MenuListHelperUI.selectByIndex(this.selectedIndex);" flex="1"/>
</vbox>
</hbox>

View File

@ -475,10 +475,14 @@ var FindHelperUI = {
this._cmdPrevious = document.getElementById(this.commands.previous);
this._cmdNext = document.getElementById(this.commands.next);
// Listen for form assistant messages from content
// Listen for find assistant messages from content
messageManager.addMessageListener("FindAssist:Show", this);
messageManager.addMessageListener("FindAssist:Hide", this);
// Listen for pan events happening on the browsers
Elements.browsers.addEventListener("PanBegin", this, false);
Elements.browsers.addEventListener("PanFinished", this, false);
// Listen for events where form assistant should be closed
document.getElementById("tabs").addEventListener("TabSelect", this, true);
Elements.browsers.addEventListener("URLChanged", this, true);
@ -510,6 +514,16 @@ var FindHelperUI = {
if (aEvent.detail && aEvent.target == getBrowser())
this.hide();
break;
case "PanBegin":
this._container.style.visibility = "hidden";
this._textbox.collapsed = true;
break;
case "PanFinished":
this._container.style.visibility = "visible";
this._textbox.collapsed = false;
break;
}
},
@ -607,7 +621,7 @@ var FormHelperUI = {
init: function formHelperInit() {
this._container = document.getElementById("content-navigator");
this._autofillContainer = document.getElementById("form-helper-autofill");
this._suggestionsContainer = document.getElementById("form-helper-suggestions-container");
this._cmdPrevious = document.getElementById(this.commands.previous);
this._cmdNext = document.getElementById(this.commands.next);
@ -628,21 +642,35 @@ var FormHelperUI = {
// Listen for modal dialog to show/hide the UI
messageManager.addMessageListener("DOMWillOpenModalDialog", this);
messageManager.addMessageListener("DOMModalDialogClosed", this);
// Listen key events for fields that are non-editable
window.addEventListener("keydown", this, true);
window.addEventListener("keyup", this, true);
window.addEventListener("keypress", this, true);
// Listen some events to show/hide the autocomplete box
Elements.browsers.addEventListener("PanBegin", this, false);
Elements.browsers.addEventListener("PanFinished", this, false);
window.addEventListener("AnimatedZoomBegin", this, false);
window.addEventListener("AnimatedZoomEnd", this, false);
},
_currentBrowser: null,
show: function formHelperShow(aElement, aHasPrevious, aHasNext) {
this._currentBrowser = Browser.selectedBrowser;
this._currentCaretRect = null;
// Update the next/previous commands
this._cmdPrevious.setAttribute("disabled", !aHasPrevious);
this._cmdNext.setAttribute("disabled", !aHasNext);
this._hasSuggestions = false;
this._open = true;
let lastElement = this._currentElement || null;
this._currentElement = {
id: aElement.id,
name: aElement.name,
title: aElement.title,
value: aElement.value,
maxLength: aElement.maxLength,
type: aElement.type,
@ -650,8 +678,9 @@ var FormHelperUI = {
list: aElement.choices
}
this._updateContainer(lastElement, this._currentElement);
this._updateContainerForSelect(lastElement, this._currentElement);
this._zoom(Rect.fromRect(aElement.rect), Rect.fromRect(aElement.caretRect));
this._updateSuggestionsFor(this._currentElement);
// Prevent the view to scroll automatically while typing
this._currentBrowser.scrollSync = false;
@ -669,6 +698,7 @@ var FormHelperUI = {
this._currentCaretRect = null;
this._updateContainerForSelect(this._currentElement, null);
this._resetSuggestions();
this._currentBrowser.messageManager.sendAsyncMessage("FormAssist:Closed", { });
this._open = false;
@ -687,16 +717,44 @@ var FormHelperUI = {
this.hide();
break;
case "PanBegin":
// The previous/next buttons should be hidden during a manual panning
// operation but not doing a zoom operation since this happen on both
// manual dblClick and navigation between the fields by clicking the
// buttons
this._container.style.visibility = "hidden";
case "AnimatedZoomBegin":
// Changing the hidden attribute here create bugs with the scrollbox
// arrows because the binding will miss some underflow events
if (this._hasSuggestions)
this._suggestionsContainer.style.visibility = "hidden";
break;
case "PanFinished":
this._container.style.visibility = "visible";
case "AnimatedZoomEnd":
if (this._hasSuggestions) {
this._suggestionsContainer.style.visibility = "visible";
this._ensureSuggestionsVisible();
}
break;
case "URLChanged":
if (aEvent.detail && aEvent.target == getBrowser())
this.hide();
break;
case "keydown":
case "keypress":
case "keyup":
Browser.keySender.handleEvent(aEvent);
break;
case "SizeChanged":
setTimeout(function(self) {
SelectHelperUI.resize();
self._container.contentHasChanged();
SelectHelperUI.sizeToContent();
self._zoom(self._currentElementRect, self._currentCaretRect);
}, 0, this);
break;
@ -714,7 +772,7 @@ var FormHelperUI = {
// want to show a UI for <select /> element but not managed by
// FormHelperUI
this.enabled ? this.show(json.current, json.hasPrevious, json.hasNext)
: SelectHelperUI.show(json.current.choices);
: SelectHelperUI.show(json.current.choices, json.current.title);
break;
case "FormAssist:Hide":
@ -728,8 +786,7 @@ var FormHelperUI = {
break;
case "FormAssist:AutoComplete":
this._updateAutocompleteFor(json.current);
this._container.contentHasChanged();
this._updateSuggestionsFor(json.current);
break;
case "FormAssist:Update":
@ -739,17 +796,13 @@ var FormHelperUI = {
break;
case "DOMWillOpenModalDialog":
if (aMessage.target == Browser.selectedBrowser) {
if (aMessage.target == Browser.selectedBrowser && this._container.isActive)
this._container.style.display = "none";
this._container._spacer.hidden = true;
}
break;
case "DOMModalDialogClosed":
if (aMessage.target == Browser.selectedBrowser) {
if (aMessage.target == Browser.selectedBrowser && this._container.isActive)
this._container.style.display = "-moz-box";
this._container._spacer.hidden = false;
}
break;
}
},
@ -784,6 +837,10 @@ var FormHelperUI = {
this._zoomFinish();
this._currentElement = null;
this._container.hide(this);
// Since the style is overrided when a popup is shown, it needs to be
// resetted here to let the default CSS works
this._container.style.display = "";
}
let evt = document.createEvent("UIEvents");
@ -791,26 +848,45 @@ var FormHelperUI = {
this._container.dispatchEvent(evt);
},
_updateAutocompleteFor: function _formHelperUpdateAutocompleteFor(aElement) {
_hasSuggestions: false,
_updateSuggestionsFor: function _formHelperUpdateAutocompleteFor(aElement) {
let suggestions = this._getAutocompleteSuggestions(aElement);
this._displaySuggestions(suggestions);
},
if (!suggestions.length) {
this._resetSuggestions();
return;
}
// Keeps the suggestions element hidden while is it not positionned to the
// correct place
this._suggestionsContainer.style.visibility = "hidden";
this._suggestionsContainer.hidden = false;
_displaySuggestions: function _formHelperDisplaySuggestions(aSuggestions) {
let autofill = this._autofillContainer;
while (autofill.hasChildNodes())
autofill.removeChild(autofill.lastChild);
// the scrollX/scrollY position can change because of the animated zoom so
// delay the suggestions positioning
if (AnimatedZoom.isZooming()) {
let self = this;
window.addEventListener("AnimatedZoomEnd", function() {
window.removeEventListener("AnimatedZoomEnd", arguments.callee, true);
self._updateSuggestionsFor(aElement);
}, true);
return;
}
let container = this._suggestionsContainer.firstChild;
while (container.hasChildNodes())
container.removeChild(container.lastChild);
let fragment = document.createDocumentFragment();
for (let i = 0; i < aSuggestions.length; i++) {
let value = aSuggestions[i];
for (let i = 0; i < suggestions.length; i++) {
let value = suggestions[i];
let button = document.createElement("label");
button.setAttribute("value", value);
button.className = "form-helper-autofill-label";
button.className = "form-helper-suggestions-label";
fragment.appendChild(button);
}
autofill.appendChild(fragment);
autofill.collapsed = !aSuggestions.length;
container.appendChild(fragment);
this._hasSuggestions = true;
this._ensureSuggestionsVisible();
},
/** Retrieve the autocomplete list from the autocomplete service for an element */
@ -832,12 +908,85 @@ var FormHelperUI = {
return suggestions;
},
_resetSuggestions: function _formHelperResetAutocomplete() {
this._suggestionsContainer.hidden = true;
this._hasSuggestions = false;
},
/**
* This method positionned the list of suggestions on the screen using
* a 'virtual' element as referrer that match the real content element
* This method called element.getBoundingClientRect() many times and can be
* expensive, do not called it too many times.
*/
_ensureSuggestionsVisible: function _formHelperEnsureSuggestionsVisible() {
let container = this._suggestionsContainer;
container.firstChild.style.maxWidth = (window.innerWidth * 0.75) + "px";
let browser = getBrowser();
let rect = this._currentElementRect.clone().scale(browser.scale, browser.scale);
let scroll = browser.getRootView().getPosition();
// The sidebars scroll needs to be taken into account, otherwise the arrows
// can be misplaced if the sidebars are open
let [leftVis, rightVis, leftW, rightW] = Browser.computeSidebarVisibility();
let leftOffset = leftVis * leftW;
let rightOffset = rightVis * rightW;
let topOffset = (BrowserUI.toolbarH - Browser.getScrollboxPosition(Browser.pageScrollboxScroller).y);
let virtualContentRect = {
width: rect.width,
height: rect.height,
left: Math.ceil(rect.left - scroll.x + leftOffset - rightOffset),
right: Math.floor(rect.left + rect.width - scroll.x + leftOffset - rightOffset),
top: Math.ceil(rect.top - scroll.y + topOffset),
bottom: Math.floor(rect.top + rect.height - scroll.y + topOffset)
};
// If the suggestions are out of view there is no need to display it
let browserRect = Rect.fromRect(browser.getBoundingClientRect());
if (BrowserUI.isToolbarLocked()) {
// If the toolbar is locked, it can appear over the field in such a way
// that the field is hidden
browserRect = new Rect(browserRect.left + leftOffset - rightOffset, browserRect.top + BrowserUI.toolbarH,
browserRect.width + leftOffset - rightOffset, browserRect.height - BrowserUI.toolbarH);
}
if (browserRect.intersect(Rect.fromRect(virtualContentRect)).isEmpty()) {
container.style.visibility = "hidden";
return;
}
// Adding rect.height to the top moves the arrowbox below the virtual field
let left = rect.left - scroll.x + leftOffset - rightOffset;
let top = rect.top - scroll.y + topOffset + (rect.height);
// Ensure parts of the arrowbox are not outside the window
// XXX do we want to correct when it is out of view on the left side too?
let arrowboxRect = Rect.fromRect(container.getBoundingClientRect());
if (left + arrowboxRect.width > window.innerWidth)
left -= (left + arrowboxRect.width - window.innerWidth);
container.left = left;
// Do not position the suggestions over the navigation buttons
let buttonsHeight = this._container.getBoundingClientRect().height;
if (top + arrowboxRect.height >= window.innerHeight - buttonsHeight)
top -= (rect.height + arrowboxRect.height);
container.top = top;
// Create a virtual element to point to
let virtualContentElement = {
getBoundingClientRect: function() {
return virtualContentRect;
}
};
container.anchorTo(virtualContentElement);
container.style.visibility = "visible";
},
/** Update the form helper container to reflect new element user is editing. */
_updateContainer: function _formHelperUpdateContainer(aLastElement, aCurrentElement) {
this._updateContainerForSelect(aLastElement, aCurrentElement);
// Setup autofill UI
this._updateAutocompleteFor(aCurrentElement);
this._container.contentHasChanged();
},
@ -846,15 +995,10 @@ var FormHelperUI = {
let lastHasChoices = aLastElement && (aLastElement.list != null);
let currentHasChoices = aCurrentElement && (aCurrentElement.list != null);
if (!lastHasChoices && currentHasChoices) {
SelectHelperUI.dock(this._container);
SelectHelperUI.show(aCurrentElement.list);
} else if (lastHasChoices && currentHasChoices) {
SelectHelperUI.reset();
SelectHelperUI.show(aCurrentElement.list);
} else if (lastHasChoices && !currentHasChoices) {
if (currentHasChoices)
SelectHelperUI.show(aCurrentElement.list, aCurrentElement.title);
else if (lastHasChoices)
SelectHelperUI.hide();
}
},
/** Zoom and move viewport so that element is legible and touchable. */

View File

@ -69,6 +69,7 @@ function FormAssistant() {
addMessageListener("FormAssist:AutoComplete", this);
addMessageListener("Content:SetWindowSize", this);
addEventListener("keypress", this, true);
addEventListener("keyup", this, false);
addEventListener("focus", this, true);
addEventListener("pageshow", this, false);
@ -282,15 +283,17 @@ FormAssistant.prototype = {
if ((!this._open && aEvent.type != "focus") || shouldIgnoreFocus)
return;
let currentElement = this.currentElement;
switch (aEvent.type) {
case "pagehide":
case "pageshow":
// When reacting to a page show/hide, if the focus is different this
// could mean the web page has dramatically changed because of
// an Ajax change based on fragment identifier
if (gFocusManager.focusedElement != this.currentElement)
if (gFocusManager.focusedElement != currentElement)
this.close();
break;
case "focus":
let focusedElement = gFocusManager.getFocusedElementForWindow(content, true, {}) || aEvent.target;
@ -309,7 +312,7 @@ FormAssistant.prototype = {
// if an element is focused while we're closed but the element can be handle
// by the assistant, try to activate it (only during mouseup)
if (!this.currentElement) {
if (!currentElement) {
if (focusedElement && this._isValidElement(focusedElement)) {
this._executeDelayed(function(self) {
self.open(focusedElement);
@ -323,19 +326,46 @@ FormAssistant.prototype = {
this.currentIndex = focusedIndex;
break;
// key processing inside a select element are done during the keypress
// handler, preventing this one to be fired cancel the selection change
case "keypress":
let formExceptions = { button: true, checkbox: true, file: true, image: true, radio: true, reset: true, submit: true };
if (this._isSelectElement(currentElement) || formExceptions[currentElement.type] ||
currentElement instanceof HTMLButtonElement || (currentElement.getAttribute("role") == "button" && currentElement.hasAttribute("tabindex"))) {
switch (aEvent.keyCode) {
case aEvent.DOM_VK_RIGHT:
this._executeDelayed(function(self) {
self.currentIndex++;
});
aEvent.stopPropagation();
aEvent.preventDefault();
break;
case aEvent.DOM_VK_LEFT:
this._executeDelayed(function(self) {
self.currentIndex--;
});
aEvent.stopPropagation();
aEvent.preventDefault();
break;
}
}
break;
case "keyup":
let currentElement = this.currentElement;
switch (aEvent.keyCode) {
case aEvent.DOM_VK_DOWN:
if (currentElement instanceof HTMLInputElement && !this._isAutocomplete(currentElement)) {
if (this._hasKeyListener(currentElement))
return;
}
else if (currentElement instanceof HTMLTextAreaElement) {
} else if (currentElement instanceof HTMLTextAreaElement) {
let existSelection = currentElement.selectionEnd - currentElement.selectionStart;
let isEnd = (currentElement.textLength == currentElement.selectionEnd);
if (!isEnd || existSelection)
return;
} else if (getListForElement(currentElement)) {
this.currentIndex = this.currentIndex;
return;
}
this.currentIndex++;
@ -345,23 +375,28 @@ FormAssistant.prototype = {
if (currentElement instanceof HTMLInputElement && !this._isAutocomplete(currentElement)) {
if (this._hasKeyListener(currentElement))
return;
}
else if (currentElement instanceof HTMLTextAreaElement) {
} else if (currentElement instanceof HTMLTextAreaElement) {
let existSelection = currentElement.selectionEnd - currentElement.selectionStart;
let isStart = (currentElement.selectionEnd == 0);
if (!isStart || existSelection)
return;
} else if (this._isSelectElement(currentElement)) {
this.currentIndex = this.currentIndex;
return;
}
this.currentIndex--;
break;
case aEvent.DOM_VK_RETURN:
case aEvent.DOM_VK_ESCAPE:
break;
default:
if (this._isAutocomplete(aEvent.target))
sendAsyncMessage("FormAssist:AutoComplete", this._getJSON());
else if (this._isSelectElement(currentElement))
this.currentIndex = this.currentIndex;
break;
}
@ -494,9 +529,8 @@ FormAssistant.prototype = {
_getCaretRect: function _formHelperGetCaretRect() {
let element = this.currentElement;
let focusedElement = gFocusManager.getFocusedElementForWindow(content, true, {});
if ((element instanceof HTMLTextAreaElement ||
(element instanceof HTMLInputElement && element.type == "text")) &&
focusedElement == element) {
if ((element.mozIsTextField && element.mozIsTextField(false) ||
element instanceof HTMLTextAreaElement) && focusedElement == element) {
let utils = Util.getWindowUtils(element.ownerDocument.defaultView);
let rect = utils.sendQueryContentEvent(utils.QUERY_CARET_RECT, element.selectionEnd, 0, 0, 0);
if (rect) {
@ -516,7 +550,7 @@ FormAssistant.prototype = {
let labels = this._getLabels();
for (let i=0; i<labels.length; i++) {
let labelRect = getBoundingContentRect(labels[i]);
let labelRect = labels[i].rect;
if (labelRect.left < elRect.left) {
let isClose = Math.abs(labelRect.left - elRect.left) - labelRect.width < kDistanceMax &&
Math.abs(labelRect.top - elRect.top) - labelRect.height < kDistanceMax;
@ -535,11 +569,16 @@ FormAssistant.prototype = {
let element = this.currentElement;
let labels = element.ownerDocument.getElementsByTagName("label");
for (let i=0; i<labels.length; i++) {
if (labels[i].control == element)
associatedLabels.push(labels[i]);
let label = labels[i];
if ((label.control == element || label.getAttribute("for") == element.id) && this._isVisibleElement(label)) {
associatedLabels.push({
rect: getBoundingContentRect(label),
title: label.textContent
});
}
}
return associatedLabels.filter(this._isVisibleElement);
return associatedLabels;
},
_getAllElements: function getAllElements(aElement) {
@ -596,10 +635,12 @@ FormAssistant.prototype = {
_getJSON: function() {
let element = this.currentElement;
let list = getListForElement(element);
let labels = this._getLabels();
return {
current: {
id: element.id,
name: element.name,
title: labels.length ? labels[0].title : "",
value: element.value,
maxLength: element.maxLength,
type: (element.getAttribute("type") || "").toLowerCase(),

View File

@ -1309,241 +1309,6 @@ pageaction:not([image]) > hbox >.pageaction-image {
font-size: @font_small@ !important;
}
/* navigator popup -------------------------------------------------------------- */
#content-navigator {
padding: 0;
background-color: #5e6166;
}
#content-navigator,
#content-navigator > #select-container > #select-spacer,
#content-navigator > #select-container > #select-container-inner > #select-buttons {
display: none;
}
#find-helper-textbox[status="1"] { /* Ci.nsITypeAheadFind.FIND_NOTFOUND */
background: rgb(255,200,200);
}
#content-navigator[type="find"],
#content-navigator[type="form"] {
display: -moz-box;
}
#content-navigator:not([type="form"]) > #form-helper-autofill {
visibility: collapse;
}
#content-navigator:not([type="form"]) > #select-container,
#content-navigator:not([type="find"]) > #find-helper-textbox {
display: none;
}
#content-navigator > #select-container > #select-container-inner {
border-width: 0;
border-radius: @border_radius_normal@ @border_radius_normal@ 0 0;
padding: @padding_normal@ @padding_small@ @padding_normal@ @padding_small@;
-moz-box-flex: 0;
}
#content-navigator > #select-container > #select-container-inner > #select-list {
min-height: @touch_row@;
}
#content-navigator > #select-container > spacer {
display: none;
}
#select-buttons {
padding: @padding_small@ @padding_normal@; /* row size & core spacing */
}
#select-buttons-done {
-moz-user-focus: ignore;
-moz-user-select: none;
}
.content-navigator-box {
padding: 0;
}
#content-navigator > hbox > .content-navigator-item {
margin: 0;
}
/* XXX this should go with the final Android theme */
#content-navigator > hbox > toolbarbutton {
border-left: @border_width_tiny@ solid rgba(255,255,255,0.2);
border-right: @border_width_tiny@ solid rgba(0,0,0,0.2);
background-color: transparent;
-moz-box-align: center;
-moz-box-pack: center;
}
#content-navigator > hbox > .previous-button {
-moz-margin-end: 0;
list-style-image: url("chrome://browser/skin/images/previous-hdpi.png");
}
#content-navigator > hbox > .next-button {
-moz-margin-start: 0;
list-style-image: url("chrome://browser/skin/images/next-hdpi.png");
}
#content-navigator > hbox > toolbarbutton:not([disabled="true"]):hover:active {
background-color: #3d3f42;
}
#content-navigator > hbox > toolbarbutton[disabled="true"] {
opacity: 0.5;
}
#form-helper-autofill,
#find-helper-textbox {
border: none;
margin: @margin_small@;
border-radius: @border_radius_normal@;
}
#form-helper-autofill {
padding: 0; /* half core spacing & none (autorepeat arrows compensate) */
color: black;
background-color: rgb(235,235,235);
background-image: url("chrome://browser/skin/images/button-bg.png");
background-size: auto 100%;
background-repeat: repeat-x;
}
#form-helper-autofill > .autorepeatbutton-down,
#form-helper-autofill > .autorepeatbutton-up {
border: none;
}
#form-helper-autofill > .autorepeatbutton-down {
list-style-image: url("chrome://browser/skin/images/arrowright-16.png");
}
#form-helper-autofill > .autorepeatbutton-down:-moz-locale-dir(rtl) {
list-style-image: url("chrome://browser/skin/images/arrowleft-16.png");
}
#form-helper-autofill > .autorepeatbutton-up {
list-style-image: url("chrome://browser/skin/images/arrowleft-16.png");
}
#form-helper-autofill > .autorepeatbutton-up:-moz-locale-dir(rtl) {
list-style-image: url("chrome://browser/skin/images/arrowright-16.png");
}
/* force the autorepeat buttons to create a 'padding' when collapsed */
#form-helper-autofill > autorepeatbutton[collapsed="true"],
#form-helper-autofill > autorepeatbutton[disabled="true"] {
visibility: hidden;
}
.form-helper-autofill-label {
padding: @padding_xxnormal@ @padding_normal@; /* 12px helps get row size for the labels */
margin: 0;
border-color: transparent rgb(215,215,215) transparent rgb(255,255,255);
border-style: solid;
border-width: @border_width_tiny@;
}
.form-helper-autofill-label:first-child {
-moz-padding-start: -moz-initial; /* the arrowscrollbox creates enough left padding */
-moz-border-start: none;
}
.form-helper-autofill-label:last-child {
-moz-border-end: none;
}
.form-helper-autofill-label:hover:active {
background-color: #8db8d8;
}
#select-container:not([hidden=true]) + #form-buttons {
border-top: 0;
}
/* select popup ------------------------------------------------------------ */
#stack > #select-container {
padding: 32px;
}
#select-list {
border: @border_width_tiny@ solid gray;
background-color: #fff;
}
.chrome-select-option {
color: #000;
background-color: #fff;
padding: @padding_small@;
border-bottom: @border_width_tiny@ solid rgb(207,207,207);
min-height: @touch_row@; /* row size */
max-height: @touch_row@; /* row size */
-moz-box-align: center;
}
.chrome-select-option[selected="true"] {
background-color: #8db8d8;
}
.chrome-select-option[disabled="true"] {
pointer-events: none;
color: #aaa !important;
}
.chrome-select-option.optgroup {
font-weight: bold;
font-style: italic;
}
.chrome-select-option.in-optgroup {
-moz-padding-start: -moz-calc(30px + @padding_large@);
}
.chrome-select-option-image {
min-width: 30px;
}
.chrome-select-option[selected="true"] {
list-style-image: url("chrome://browser/skin/images/check-30.png");
}
/* menulist popup ---------------------------------------------------------- */
#menulist-commands {
display: -moz-box;
}
#menulist-title {
padding: @padding_xxnormal@ @padding_small@ @padding_small@ @padding_small@;
font-size: @font_small@;
}
#menulist-title[value=""] {
display: none;
}
.menulist-command > image {
width: 32px;
height: 32px;
}
.menulist-command.selected > image {
width: 30px;
height: 30px;
list-style-image: url("chrome://browser/skin/images/check-30.png");
margin-right: @margin_tiny@;
margin-top: @margin_tiny@;
}
.menulist-command:not(.selected) > image[src=""] {
visibility: hidden;
}
/* full-screen video ------------------------------------------------------- */
.full-screen {
position: absolute;

View File

@ -6,12 +6,13 @@ chrome.jar:
skin/aboutPage.css (aboutPage.css)
skin/about.css (about.css)
skin/aboutHome.css (aboutHome.css)
* skin/browser.css (browser.css)
skin/config.css (config.css)
skin/firstRun.css (firstRun.css)
* skin/forms.css (forms.css)
skin/header.css (header.css)
* skin/platform.css (platform.css)
* skin/browser.css (browser.css)
* skin/notification.css (notification.css)
* skin/platform.css (platform.css)
skin/touchcontrols.css (touchcontrols.css)
% override chrome://global/skin/about.css chrome://browser/skin/about.css
% override chrome://global/skin/media/videocontrols.css chrome://browser/skin/touchcontrols.css

View File

@ -91,6 +91,7 @@ textbox.search-bar {
border: @border_width_small@ solid rgba(0,0,0,0.4);
background-color: #f9f9f9;
background: url("chrome://browser/skin/images/textbox-bg.png") top left repeat-x;
background-size: 100% 100%;
}
textbox[disabled="true"] {