Bug 327977 ? Password manager overwrites page data for password-only forms. r=gavin
[wine-gecko.git] / toolkit / spatial-navigation / SpatialNavigation.js
blob55275543e1cc04d19c0571f0381302177d0a12a9
1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
14 * The Original Code is Spatial Navigation.
16 * The Initial Developer of the Original Code is Mozilla Corporation
17 * Portions created by the Initial Developer are Copyright (C) 2008
18 * the Initial Developer. All Rights Reserved.
20 * Contributor(s):
21 * Doug Turner <dougt@meer.net> (Original Author)
23 * Alternatively, the contents of this file may be used under the terms of
24 * either the GNU General Public License Version 2 or later (the "GPL"), or
25 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26 * in which case the provisions of the GPL or the LGPL are applicable instead
27 * of those above. If you wish to allow use of your version of this file only
28 * under the terms of either the GPL or the LGPL, and not to allow others to
29 * use your version of this file under the terms of the MPL, indicate your
30 * decision by deleting the provisions above and replace them with the notice
31 * and other provisions required by the GPL or the LGPL. If you do not delete
32 * the provisions above, a recipient may use your version of this file under
33 * the terms of any one of the MPL, the GPL or the LGPL.
35 * ***** END LICENSE BLOCK ***** */
37 /**
39 * Import this module through
41 * Components.utils.import("resource://gre/modules/SpatialNavigation.js");
43 * Usage:
46 * var snav = new SpatialNavigation(browser_element, optional_callback);
48 * optional_callback will be called when a new element is focused.
50 * function optional_callback(element) {}
55 var EXPORTED_SYMBOLS = ["SpatialNavigation"];
57 function SpatialNavigation (browser, callback)
59 browser.addEventListener("keypress", function (event) { _onInputKeyPress(event, callback) }, true);
62 SpatialNavigation.prototype = {
66 // Private stuff
68 const Cc = Components.classes;
69 const Ci = Components.interfaces;
71 function dump(msg)
73 var console = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
74 console.logStringMessage("*** SNAV: " + msg);
77 var gDirectionalBias = 10;
78 var gRectFudge = 1;
80 function _onInputKeyPress (event, callback) {
82 // If it isn't an arrow key, bail.
83 if (event.keyCode != event.DOM_VK_LEFT &&
84 event.keyCode != event.DOM_VK_RIGHT &&
85 event.keyCode != event.DOM_VK_UP &&
86 event.keyCode != event.DOM_VK_DOWN )
87 return;
89 function snavfilter(node) {
91 if (node instanceof Ci.nsIDOMHTMLLinkElement ||
92 node instanceof Ci.nsIDOMHTMLAnchorElement) {
93 // if a anchor doesn't have a href, don't target it.
94 if (node.href == "")
95 return Ci.nsIDOMNodeFilter.FILTER_SKIP;
96 return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
99 if (node instanceof Ci.nsIDOMHTMLInputElement ||
100 node instanceof Ci.nsIDOMHTMLSelectElement ||
101 node instanceof Ci.nsIDOMHTMLOptionElement)
102 return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
103 return Ci.nsIDOMNodeFilter.FILTER_SKIP;
105 var bestElementToFocus = null;
106 var distanceToBestElement = Infinity;
107 var focusedRect = _inflateRect(event.target.getBoundingClientRect(),
108 - gRectFudge);
109 var doc = event.target.ownerDocument;
111 var treeWalker = doc.createTreeWalker(doc, Ci.nsIDOMNodeFilter.SHOW_ELEMENT, snavfilter, false);
112 var nextNode;
114 while ((nextNode = treeWalker.nextNode())) {
116 if (nextNode == event.target)
117 continue;
119 var nextRect = _inflateRect(nextNode.getBoundingClientRect(),
120 - gRectFudge);
122 if (! _isRectInDirection(event, focusedRect, nextRect))
123 continue;
125 var distance = _spatialDistance(event, focusedRect, nextRect);
127 if (distance <= distanceToBestElement && distance > 0) {
128 distanceToBestElement = distance;
129 bestElementToFocus = nextNode;
133 if (bestElementToFocus != null) {
134 // dump("focusing element " + bestElementToFocus.nodeName + " " + bestElementToFocus);
135 // Wishing we could do element.focus()
136 doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).focus(bestElementToFocus);
138 if (callback != undefined)
139 callback(bestElementToFocus);
141 } else {
142 // couldn't find anything. just advance and hope.
143 // dump("advancing focus");
144 var windowMediator = Cc['@mozilla.org/appshell/window-mediator;1'].getService(Ci.nsIWindowMediator);
145 var window = windowMediator.getMostRecentWindow("navigator:browser");
146 window.document.commandDispatcher.advanceFocus();
148 if (callback != undefined)
149 callback(null);
152 event.preventDefault();
153 event.stopPropagation();
156 function _isRectInDirection(event, focusedRect, anotherRect)
158 if (event.keyCode == event.DOM_VK_LEFT) {
159 return (anotherRect.left < focusedRect.left);
162 if (event.keyCode == event.DOM_VK_RIGHT) {
163 return (anotherRect.right > focusedRect.right);
166 if (event.keyCode == event.DOM_VK_UP) {
167 return (anotherRect.top < focusedRect.top);
170 if (event.keyCode == event.DOM_VK_DOWN) {
171 return (anotherRect.bottom > focusedRect.bottom);
173 return false;
176 function _inflateRect(rect, value)
178 var newRect = new Object();
180 newRect.left = rect.left - value;
181 newRect.top = rect.top - value;
182 newRect.right = rect.right + value;
183 newRect.bottom = rect.bottom + value;
184 return newRect;
187 function _containsRect(a, b)
189 return ( (b.left <= a.right) &&
190 (b.right >= a.left) &&
191 (b.top <= a.bottom) &&
192 (b.bottom >= a.top) );
195 function _spatialDistance(event, a, b)
197 var inlineNavigation = false;
198 var mx, my, nx, ny;
200 if (event.keyCode == event.DOM_VK_LEFT) {
202 // |---|
203 // |---|
205 // |---| |---|
206 // |---| |---|
208 // |---|
209 // |---|
212 if (a.top > b.bottom) {
213 // the b rect is above a.
214 mx = a.left;
215 my = a.top;
216 nx = b.right;
217 ny = b.bottom;
219 else if (a.bottom < b.top) {
220 // the b rect is below a.
221 mx = a.left;
222 my = a.bottom;
223 nx = b.right;
224 ny = b.top;
226 else {
227 mx = a.left;
228 my = 0;
229 nx = b.right;
230 ny = 0;
232 } else if (event.keyCode == event.DOM_VK_RIGHT) {
234 // |---|
235 // |---|
237 // |---| |---|
238 // |---| |---|
240 // |---|
241 // |---|
244 if (a.top > b.bottom) {
245 // the b rect is above a.
246 mx = a.right;
247 my = a.top;
248 nx = b.left;
249 ny = b.bottom;
251 else if (a.bottom < b.top) {
252 // the b rect is below a.
253 mx = a.right;
254 my = a.bottom;
255 nx = b.left;
256 ny = b.top;
257 } else {
258 mx = a.right;
259 my = 0;
260 nx = b.left;
261 ny = 0;
263 } else if (event.keyCode == event.DOM_VK_UP) {
265 // |---| |---| |---|
266 // |---| |---| |---|
268 // |---|
269 // |---|
272 if (a.left > b.right) {
273 // the b rect is to the left of a.
274 mx = a.left;
275 my = a.top;
276 nx = b.right;
277 ny = b.bottom;
278 } else if (a.right < b.left) {
279 // the b rect is to the right of a
280 mx = a.right;
281 my = a.top;
282 nx = b.left;
283 ny = b.bottom;
284 } else {
285 // both b and a share some common x's.
286 mx = 0;
287 my = a.top;
288 nx = 0;
289 ny = b.bottom;
291 } else if (event.keyCode == event.DOM_VK_DOWN) {
293 // |---|
294 // |---|
296 // |---| |---| |---|
297 // |---| |---| |---|
300 if (a.left > b.right) {
301 // the b rect is to the left of a.
302 mx = a.left;
303 my = a.bottom;
304 nx = b.right;
305 ny = b.top;
306 } else if (a.right < b.left) {
307 // the b rect is to the right of a
308 mx = a.right;
309 my = a.bottom;
310 nx = b.left;
311 ny = b.top;
312 } else {
313 // both b and a share some common x's.
314 mx = 0;
315 my = a.bottom;
316 nx = 0;
317 ny = b.top;
321 var scopedRect = _inflateRect(a, gRectFudge);
323 if (event.keyCode == event.DOM_VK_LEFT ||
324 event.keyCode == event.DOM_VK_RIGHT) {
325 scopedRect.left = 0;
326 scopedRect.right = Infinity;
327 inlineNavigation = _containsRect(scopedRect, b);
329 else if (event.keyCode == event.DOM_VK_UP ||
330 event.keyCode == event.DOM_VK_DOWN) {
331 scopedRect.top = 0;
332 scopedRect.bottom = Infinity;
333 inlineNavigation = _containsRect(scopedRect, b);
336 var d = Math.pow((mx-nx), 2) + Math.pow((my-ny), 2);
338 // prefer elements directly aligned with the focused element
339 if (inlineNavigation)
340 d /= gDirectionalBias;
342 return d;