Bug 486184: make filling in forms easier [r=webapps r=mark.finkle]

This commit is contained in:
Vivien Nicolas 2009-10-24 00:04:51 -04:00
Родитель 25d6b19b7a
Коммит e77ab2b928
9 изменённых файлов: 430 добавлений и 81 удалений

Просмотреть файл

@ -42,6 +42,8 @@ let Ci = Components.interfaces;
const kBrowserViewZoomLevelMin = 0.2;
const kBrowserViewZoomLevelMax = 4.0;
const kBrowserFormZoomLevelMin = 1.0;
const kBrowserFormZoomLevelMax = 2.0;
const kBrowserViewZoomLevelPrecision = 10000;
const kBrowserViewPrefetchBeginIdleWait = 1; // seconds
const kBrowserViewCacheSize = 15;

Просмотреть файл

@ -930,6 +930,21 @@
</handlers>
</binding>
<binding id="chrome-input">
<content>
<children />
</content>
<handlers>
<handler event="click" button="0">
<![CDATA[
var showEvent = document.createEvent("Events");
showEvent.initEvent("UIShowForm", true, false);
this.dispatchEvent(showEvent);
]]>
</handler>
</handlers>
</binding>
<binding id="chrome-select">
<content>
<children />
@ -955,10 +970,8 @@
if (options.length == 0)
return;
this.focus();
var showEvent = document.createEvent("Events");
showEvent.initEvent("UIShowSelect", true, false);
showEvent.initEvent("UIShowForm", true, false);
this.dispatchEvent(showEvent);
]]>
</handler>

Просмотреть файл

@ -342,6 +342,10 @@ var BrowserUI = {
const popupMargin = 10;
bookmarkPopup.top = Math.round(starRect.top) + popupMargin;
bookmarkPopup.left = windowW - this.sidebarW - bookmarkPopupW - popupMargin;
// form helper
let formHelper = document.getElementById("form-helper-container");
formHelper.top = windowH - formHelper.getBoundingClientRect().height;
},
init : function() {
@ -362,7 +366,7 @@ var BrowserUI = {
let browsers = document.getElementById("browsers");
browsers.addEventListener("DOMWindowClose", this, true);
browsers.addEventListener("UIShowSelect", this, false, true);
browsers.addEventListener("UIShowForm", this, false, true);
// XXX these really want to listen to only the current browser
browsers.addEventListener("DOMTitleChanged", this, true);
@ -446,6 +450,9 @@ var BrowserUI = {
// Update the navigation buttons
this._updateButtons(browser);
// Close the forms assistant
FormHelper.close();
// Check for a bookmarked page
this.updateStar();
@ -590,8 +597,8 @@ var BrowserUI = {
case "DOMWindowClose":
this._domWindowClose(aEvent);
break;
case "UIShowSelect":
SelectHelper.show(aEvent.target);
case "UIShowForm":
FormHelper.open(aEvent.target);
break;
case "TabSelect":
this._tabSelect(aEvent);
@ -953,6 +960,225 @@ var BookmarkList = {
}
};
var FormHelper = {
_nodes: null,
get _container() {
delete this._container;
return this._container = document.getElementById("form-helper-container");
},
get _helperSpacer() {
delete this._helperSpacer;
return this._helperSpacer = document.getElementById("form-helper-spacer");
},
get _selectContainer() {
delete this._selectContainer;
return this._selectContainer = document.getElementById("select-container");
},
_getRectForElement: function formHelper_getRectForElement(aElement) {
let elRect = Browser.getBoundingContentRect(aElement);
let bv = Browser._browserView;
let labels = this.getLabelsFor(aElement);
for (let i=0; i<labels.length; i++) {
let labelRect = Browser.getBoundingContentRect(labels[i]);
if (labelRect.left < elRect.left) {
let width = labelRect.width + elRect.width + (elRect.x - labelRect.x - labelRect.width);
return new Rect(labelRect.x, labelRect.y, width, elRect.height).expandToIntegers();
}
}
return elRect;
},
_update: function(aPreviousElement, aNewElement) {
this._updateSelect(aPreviousElement, aNewElement);
let height = Math.floor(this._container.getBoundingClientRect().height);
this._container.top = window.innerHeight - height;
document.getElementById("form-helper-previous").disabled = this._getPrevious() ? false : true;
document.getElementById("form-helper-next").disabled = this._getNext() ? false : true;
},
_updateSelect: function(aPreviousElement, aNewElement) {
let previousIsSelect = this._isValidSelectElement(aPreviousElement);
let currentIsSelect = this._isValidSelectElement(aNewElement);
if (currentIsSelect && !previousIsSelect) {
this._selectContainer.height = window.innerHeight / 1.8;
let rootNode = this._container;
rootNode.insertBefore(this._selectContainer, rootNode.lastChild);
SelectHelper.show(aNewElement);
}
else if (currentIsSelect && previousIsSelect) {
SelectHelper.reset();
SelectHelper.show(aNewElement);
}
else if (!currentIsSelect && previousIsSelect) {
let rootNode = this._container.parentNode;
rootNode.insertBefore(this._selectContainer, rootNode.lastChild);
SelectHelper.close();
}
},
_isValidElement: function(aElement) {
if (aElement.disabled)
return false;
if (aElement instanceof HTMLSelectElement || aElement instanceof HTMLTextAreaElement) {
let rect = aElement.getBoundingClientRect();
let isVisible = (rect.height != 0 || rect.width != 0);
return isVisible;
}
if (aElement instanceof HTMLInputElement) {
let ignoreInputElements = ["checkbox", "radio", "hidden", "reset", "button"];
let isValidElement = (ignoreInputElements.indexOf(aElement.type) == -1);
if (!isValidElement)
return false;
let rect = aElement.getBoundingClientRect();
let isVisible = (rect.height != 0 || rect.width != 0);
return isVisible;
}
return false;
},
_isValidSelectElement: function(aElement) {
return (aElement instanceof HTMLSelectElement) || (aElement instanceof Ci.nsIDOMXULMenuListElement);
},
_getAll: function() {
let doc = getBrowser().contentDocument;
let nodes = doc.evaluate("//input|//select",
doc,
null,
XPathResult.ORDERED_NODE_ITERATOR_TYPE,
null);
let elements = [];
let node = nodes.iterateNext();
while (node) {
if (this._isValidElement(node))
elements.push(node);
node = nodes.iterateNext();
}
function orderByTabIndex(a, b) {
return a.tabIndex - b.tabIndex;
}
return elements.sort(orderByTabIndex);
},
_getPrevious: function() {
let elements = this._nodes;
for (let i = elements.length; i>0; --i) {
if (elements[i] == this._currentElement)
return elements[--i];
}
return null;
},
_getNext: function() {
let elements = this._nodes;
for (let i = 0; i<elements.length; i++) {
if (elements[i] == this._currentElement)
return elements[++i];
}
return null;
},
getLabelsFor: function(aElement) {
let associatedLabels = [];
if (this._isValidElement(aElement)) {
let labels = aElement.ownerDocument.getElementsByTagName("label");
for (let i=0; i<labels.length; i++) {
if (labels[i].getAttribute("for") == aElement.id)
associatedLabels.push(labels[i]);
}
}
if (aElement.parentNode instanceof HTMLLabelElement)
associatedLabels.push(aElement.parentNode);
return associatedLabels;
},
_currentElement: null,
getCurrentElement: function() {
return this._currentElement;
},
setCurrentElement: function(aElement) {
if (!aElement)
return;
let previousElement = this._currentElement;
this._currentElement = aElement;
this._update(previousElement, aElement);
let containerHeight = this._container.getBoundingClientRect().height;
this._helperSpacer.setAttribute("height", containerHeight);
this.zoom(aElement);
gFocusManager.setFocus(aElement, Ci.nsIFocusManager.FLAG_NOSCROLL);
},
goToPrevious: function formHelperGoToPrevious() {
let previous = this._getPrevious();
this.setCurrentElement(previous);
},
goToNext: function formHelperGoToNext() {
let next = this._getNext();
this.setCurrentElement(next);
},
open: function formHelperOpen(aElement) {
this._open = true;
this._container.hidden = false;
this._helperSpacer.hidden = false;
this._nodes = this._getAll();
this.setCurrentElement(aElement);
},
close: function formHelperHide() {
if (!this._open)
return;
this._updateSelect(this._currentElement, null);
this._helperSpacer.hidden = true;
// give the form spacer area back to the content
let bv = Browser._browserView;
bv.onBeforeVisibleMove(0, 0);
Browser.contentScrollboxScroller.scrollBy(0, 0);
bv.onAfterVisibleMove();
this._container.hidden = true;
this._currentElement = null;
this._open = false;
},
zoom: function formHelperZoom(aElement) {
let zoomLevel = Browser._getZoomLevelForElement(aElement);
zoomLevel = Math.min(Math.max(kBrowserFormZoomLevelMin, zoomLevel), kBrowserFormZoomLevelMax);
let elRect = this._getRectForElement(aElement);
let zoomRect = Browser._getZoomRectForPoint(elRect.center().x, elRect.y, zoomLevel);
Browser.setVisibleRect(zoomRect);
}
};
function SelectWrapper(aControl) {
this._control = aControl;
}
@ -1089,7 +1315,6 @@ var SelectHelper = {
this._scrollElementIntoView(firstSelected);
this._list.focus();
this._list.addEventListener("click", this, false);
},
@ -1144,18 +1369,19 @@ var SelectHelper = {
this._control.fireOnChange();
},
reset: function() {
let empty = this._list.cloneNode(false);
this._list.parentNode.replaceChild(empty, this._list);
this._list = empty;
},
close: function() {
this._updateControl();
this._list.removeEventListener("click", this, false);
this._panel.hidden = true;
// Clear out the list for the next show
let empty = this._list.cloneNode(false);
this._list.parentNode.replaceChild(empty, this._list);
this._list = empty;
this._control.focus();
this.reset();
},
handleEvent: function(aEvent) {

Просмотреть файл

@ -983,39 +983,21 @@ var Browser = {
},
/**
* Returns a good zoom rectangle for given element.
* @param y Where the user clicked in browser coordinates. For long elements
* (like news columns), this keeps the clicked spot in the viewport.
* @return Rectangle in current viewport coordinates, null if nothing works.
* Find the needed zoom level for zooming on an element
*/
_getZoomRectForElement: function _getZoomRectForElement(element, y) {
if (element == null)
return null;
_getZoomLevelForElement: function _getZoomLevelForElement(element) {
const margin = 15;
let bv = Browser._browserView;
let vis = bv.getVisibleRect();
let bv = this._browserView;
let elRect = bv.browserToViewportRect(Browser.getBoundingContentRect(element));
y = bv.browserToViewport(y);
let zoomLevel = BrowserView.Util.clampZoomLevel(bv.getZoomLevel() * vis.width / (elRect.width + margin * 2));
let zoomRatio = bv.getZoomLevel() / zoomLevel;
// Don't zoom in a marginal amount
// > 2/3 means operation increases the zoom level by less than 1.5
if (zoomRatio >= .6666)
return null;
let newVisW = vis.width * zoomRatio, newVisH = vis.height * zoomRatio;
let result = new Rect(elRect.center().x - newVisW / 2, y - newVisH / 2, newVisW, newVisH).expandToIntegers();
// Make sure rectangle doesn't poke out of viewport
return result.translateInside(bv._browserViewportState.viewportRect);
let vis = bv.getVisibleRect();
return BrowserView.Util.clampZoomLevel(bv.getZoomLevel() * vis.width / (elRect.width + margin * 2));
},
/**
* Find a good zoom rectangle for point specified in browser coordinates.
* @return Point in viewport coordinates, null if the zoom is too big.
* @return Point in viewport coordinates
*/
_getZoomRectForPoint: function _getZoomRectForPoint(x, y, zoomLevel) {
let bv = Browser._browserView;
@ -1023,12 +1005,10 @@ var Browser = {
x = bv.browserToViewport(x);
y = bv.browserToViewport(y);
if (zoomLevel >= 4)
return null;
zoomLevel = Math.min(kBrowserViewZoomLevelMax, zoomLevel);
let zoomRatio = zoomLevel / bv.getZoomLevel();
let newVisW = vis.width / zoomRatio, newVisH = vis.height / zoomRatio;
let result = new Rect(x - newVisW / 2, y - newVisH / 2, newVisW, newVisH);
let result = new Rect(x - newVisW / 2, y - newVisH / 2, newVisW, newVisH).expandToIntegers();
// Make sure rectangle doesn't poke out of viewport
return result.translateInside(bv._browserViewportState.viewportRect);
@ -1055,15 +1035,23 @@ var Browser = {
},
zoomToPoint: function zoomToPoint(cX, cY) {
const margin = 15;
let [elementX, elementY] = Browser.transformClientToBrowser(cX, cY);
let element = Browser.elementFromPoint(elementX, elementY);
let zoomRect = this._getZoomRectForElement(element, elementY);
if (zoomRect == null)
let element = Browser.elementFromPoint(elementX, elementY);
if (!element)
return false;
let zoomLevel = this._getZoomLevelForElement(element);
// Don't zoom in a marginal amount
// > 2/3 means operation increases the zoom level by less than 1.5
let zoomRatio = this._browserView.getZoomLevel() / zoomLevel;
if (zoomRatio >= .6666)
return false;
let elRect = Browser.getBoundingContentRect(element);
let zoomRect = this._getZoomRectForPoint(elRect.center().x, elementY, zoomLevel);
this.setVisibleRect(zoomRect);
return true;
},
@ -1172,13 +1160,14 @@ var Browser = {
* the tile container, i.e. BrowserView coordinates.
*/
getVisibleRect: function getVisibleRect() {
let stack = document.getElementById("tile-stack");
let container = document.getElementById("tile-container");
let containerBCR = container.getBoundingClientRect();
let x = Math.round(-containerBCR.left);
let y = Math.round(-containerBCR.top);
let w = window.innerWidth;
let h = window.innerHeight;
let h = stack.getBoundingClientRect().height;
return new Rect(x, y, w, h);
},
@ -1277,9 +1266,10 @@ Browser.MainDragger.prototype = {
if (doffset.x > 0 && rect.left > 0)
x = Math.min(doffset.x, rect.left);
let height = document.getElementById("tile-stack").getBoundingClientRect().height;
rect = Rect.fromRect(Browser.contentScrollbox.getBoundingClientRect()).map(Math.round);
if (doffset.y < 0 && rect.bottom < window.innerHeight)
y = Math.max(doffset.y, rect.bottom - window.innerHeight);
if (doffset.y < 0 && rect.bottom < height)
y = Math.max(doffset.y, rect.bottom - height);
if (doffset.y > 0 && rect.top > 0)
y = Math.min(doffset.y, rect.top);

Просмотреть файл

@ -127,6 +127,11 @@
<command id="cmd_paste" label="&paste.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
<command id="cmd_delete" label="&delete.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
<command id="cmd_selectAll" label="&selectAll.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
<!-- forms navigation -->
<command id="cmd_formPrevious" oncommand="FormHelper.goToPrevious();"/>
<command id="cmd_formNext" oncommand="FormHelper.goToNext();"/>
<command id="cmd_formClose" oncommand="FormHelper.close();"/>
</commandset>
<keyset id="mainKeyset">
@ -228,8 +233,9 @@
<notificationbox id="notifications"/>
<!-- Content viewport -->
<stack id="tile-stack" class="window-width window-height">
<scrollbox id="content-scrollbox" style="overflow: hidden;" class="window-width window-height">
<vbox class="window-width window-height">
<stack id="tile-stack" class="window-width" flex="1">
<scrollbox id="content-scrollbox" style="overflow: hidden;" class="window-width" flex="1">
<!-- Content viewport -->
<html:div id="tile-container" style="overflow: hidden;"/>
</scrollbox>
@ -237,6 +243,9 @@
<html:canvas id="view-buffer" style="display: none;" moz-opaque="true">
</html:canvas>
</stack>
<box id="form-helper-spacer" hidden="true"/>
</vbox>
</vbox>
</scrollbox>
@ -254,6 +263,16 @@
</vbox>
</scrollbox>
<!-- popup for form helper -->
<vbox id="form-helper-container" class="window-width" hidden="true" top="0" pack="end">
<hbox id="form-buttons" class="panel-dark" pack="center">
<button id="form-helper-previous" class="button-dark" label="&formHelper.previous;" command="cmd_formPrevious"/>
<button id="form-helper-next" class="button-dark" label="&formHelper.next;" command="cmd_formNext"/>
<spacer flex="1"/>
<button id="form-helper-close" class="button-dark" label="&formHelper.done;" command="cmd_formClose"/>
</hbox>
</vbox>
<!-- popup for site identity information -->
<vbox id="identity-container" hidden="true" class="panel-dark window-width" mode="unknownIdentity">
<hbox id="identity-popup-container" flex="1" align="top">
@ -425,8 +444,8 @@
<vbox id="select-container" hidden="true">
<vbox id="select-container-inner" class="panel-dark" flex="1">
<scrollbox id="select-list" flex="1" orient="vertical"/>
<hbox id="select-buttons">
<button id="select-buttons-done" class="button-dark" label="&selectListDone.label;" oncommand="SelectHelper.close();"/>
<hbox id="select-buttons" pack="center">
<button id="select-buttons-done" class="button-dark" label="&formHelper.done;" oncommand="SelectHelper.close();"/>
</hbox>
</vbox>
</vbox>

Просмотреть файл

@ -54,6 +54,15 @@ thumb {
-moz-border-radius: 4px !important;
}
textarea,
input:not(type),
input[type=""],
input[type="file"],
input[type="password"],
input[type="text"] {
-moz-binding: url("chrome://browser/content/bindings.xml#chrome-input");
}
select {
-moz-binding: url("chrome://browser/content/bindings.xml#chrome-select");
}

Просмотреть файл

@ -31,6 +31,10 @@
<!ENTITY helperApp.save "Save">
<!ENTITY helperApp.nothing "Nothing">
<!ENTITY formHelper.previous "Previous">
<!ENTITY formHelper.next "Next">
<!ENTITY formHelper.done "Done">
<!ENTITY addonsHeader.label "Add-ons">
<!ENTITY addonsLocal.label "Your Add-ons">
<!ENTITY addonsUpdate.label "Update">
@ -89,5 +93,3 @@
<!ENTITY consoleErrFile.label "Source File:">
<!ENTITY consoleErrLine.label "Line:">
<!ENTITY consoleErrColumn.label "Column:">
<!ENTITY selectListDone.label "Done">

Просмотреть файл

@ -867,12 +867,44 @@ settings .settings-title {
font-size: 2.4mm !important;
}
/* select popup ------------------------------------------------------------ */
#select-container {
padding: 3mm; /* half row size */
/* form popup -------------------------------------------------------------- */
#form-helper-container > #select-container > #select-container-inner {
-moz-border-radius-topleft: 1mm;
-moz-border-radius-topright: 1mm;
padding: 1mm 0.5mm 1mm 0.5mm;
}
#select-container-inner {
#form-helper-container > #select-container > #select-container-inner,
#form-buttons {
border: 0.1mm solid gray;
border-bottom: 0;
}
#form-buttons,
#select-buttons {
padding: 0.5mm 1mm; /* row size & core spacing */
}
#form-buttons > button,
#select-buttons > button {
-moz-user-focus: ignore;
-moz-user-select: none;
}
#form-helper-container #select-buttons {
display: none;
}
#select-container:not([hidden=true]) + #form-buttons {
border-top: 0;
}
/* select popup ------------------------------------------------------------ */
#stack > #select-container {
padding: 3mm;
}
#stack > #select-container > #select-container-inner {
border: 0.5mm solid #36373b;
-moz-border-top-colors: #fff #36373b;
-moz-border-right-colors: #fff #36373b;
@ -882,6 +914,10 @@ settings .settings-title {
padding-top: 1mm; /* core spacing */
}
#select-list {
border: 0.1mm solid gray;
}
#select-list > option {
color: #000;
background-color: #fff;
@ -909,11 +945,6 @@ settings .settings-title {
-moz-padding-start: 2.2mm;
}
#select-buttons {
padding: 0.5mm 1mm; /* row size & core spacing */
-moz-box-pack: center;
}
#select-list > option > image {
min-width: 30px;
}

Просмотреть файл

@ -569,11 +569,73 @@ settings .settings-title {
font-size: 8pt !important;
}
/* forms popup ------------------------------------------------------------- */
#forms-buttons {
border: 0.25mm solid black;
border-bottom: 0;
padding: 0.55mm 1.1mm; /* row size & core spacing */
-moz-box-pack: center;
}
#select-container:not([hidden=true]) + #forms-buttons {
border-top: 0;
}
#forms-buttons > button {
-moz-user-focus: ignore;
-moz-user-select: none;
}
/* form popup -------------------------------------------------------------- */
#form-helper-container > #select-container > #select-container-inner {
-moz-border-radius-topleft: 1mm;
-moz-border-radius-topright: 1mm;
padding: 0.75mm 0.25mm 0.75mm 0.25mm;
border-bottom: 0;
}
#form-helper-container > #select-container > #select-container-inner,
#form-buttons {
border: 0.25mm solid gray;
border-bottom: 0;
}
#form-buttons,
#select-buttons {
padding: 0.25mm 0.5mm; /* row size & core spacing */
}
#form-buttons > button,
#select-buttons > button {
-moz-user-focus: ignore;
-moz-user-select: none;
}
#form-helper-container #select-buttons {
display: none;
}
#select-container:not([hidden=true]) + #form-buttons {
border-top: 0;
}
/* select popup ------------------------------------------------------------ */
#select-container {
border: 0.5mm solid #36373b;
-moz-border-radius: 1.0mm;
padding-top: 1.1mm; /* core spacing */
#stack > #select-container {
padding: 1.5mm;
}
#stack > #select-container > #select-container-inner {
border: 0.25mm solid #36373b;
-moz-border-top-colors: #fff #36373b;
-moz-border-right-colors: #fff #36373b;
-moz-border-left-colors: #fff #36373b;
-moz-border-bottom-colors: #fff #36373b;
-moz-border-radius: 0.5mm;
padding-top: 0.5mm; /* core spacing */
}
#select-list {
border: 0.1mm solid gray;
}
#select-list > option {
@ -603,11 +665,6 @@ settings .settings-title {
-moz-padding-start: 4.4mm;
}
#select-buttons {
padding: 0.55mm 1.1mm; /* row size & core spacing */
-moz-box-pack: center;
}
#select-list > option > image {
min-width: 30px;
}