selenium-webdriver 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. data/chrome/prebuilt/Win32/Release/npchromedriver.dll +0 -0
  2. data/chrome/prebuilt/x64/Release/npchromedriver.dll +0 -0
  3. data/chrome/src/extension/background.html +9 -0
  4. data/chrome/src/extension/background.js +933 -0
  5. data/chrome/src/extension/content_script.js +1286 -0
  6. data/chrome/src/extension/manifest-nonwin.json +15 -0
  7. data/chrome/src/extension/manifest-win.json +16 -0
  8. data/chrome/src/extension/toolstrip.html +28 -0
  9. data/chrome/src/extension/utils.js +196 -0
  10. data/chrome/src/rb/lib/selenium/webdriver/chrome.rb +8 -0
  11. data/chrome/src/rb/lib/selenium/webdriver/chrome/bridge.rb +324 -0
  12. data/chrome/src/rb/lib/selenium/webdriver/chrome/command_executor.rb +70 -0
  13. data/chrome/src/rb/lib/selenium/webdriver/chrome/launcher.rb +119 -0
  14. data/common/src/js/abstractcommandprocessor.js +161 -0
  15. data/common/src/js/asserts.js +296 -0
  16. data/common/src/js/by.js +147 -0
  17. data/common/src/js/command.js +274 -0
  18. data/common/src/js/context.js +58 -0
  19. data/common/src/js/extension/README +2 -0
  20. data/common/src/js/extension/dommessenger.js +152 -0
  21. data/common/src/js/factory.js +55 -0
  22. data/common/src/js/future.js +118 -0
  23. data/common/src/js/key.js +117 -0
  24. data/common/src/js/localcommandprocessor.js +181 -0
  25. data/common/src/js/logging.js +249 -0
  26. data/common/src/js/testrunner.js +605 -0
  27. data/common/src/js/timing.js +89 -0
  28. data/common/src/js/wait.js +199 -0
  29. data/common/src/js/webdriver.js +853 -0
  30. data/common/src/js/webelement.js +683 -0
  31. data/common/src/rb/lib/selenium-webdriver.rb +1 -0
  32. data/common/src/rb/lib/selenium/webdriver.rb +52 -0
  33. data/common/src/rb/lib/selenium/webdriver/bridge_helper.rb +88 -0
  34. data/common/src/rb/lib/selenium/webdriver/child_process.rb +85 -0
  35. data/common/src/rb/lib/selenium/webdriver/core_ext/dir.rb +41 -0
  36. data/common/src/rb/lib/selenium/webdriver/driver.rb +128 -0
  37. data/common/src/rb/lib/selenium/webdriver/element.rb +126 -0
  38. data/common/src/rb/lib/selenium/webdriver/error.rb +68 -0
  39. data/common/src/rb/lib/selenium/webdriver/find.rb +69 -0
  40. data/common/src/rb/lib/selenium/webdriver/navigation.rb +23 -0
  41. data/common/src/rb/lib/selenium/webdriver/options.rb +50 -0
  42. data/common/src/rb/lib/selenium/webdriver/platform.rb +82 -0
  43. data/common/src/rb/lib/selenium/webdriver/target_locator.rb +23 -0
  44. data/firefox/prebuilt/nsICommandProcessor.xpt +0 -0
  45. data/firefox/prebuilt/nsINativeEvents.xpt +0 -0
  46. data/firefox/prebuilt/nsIResponseHandler.xpt +0 -0
  47. data/firefox/src/extension/chrome.manifest +3 -0
  48. data/firefox/src/extension/components/context.js +37 -0
  49. data/firefox/src/extension/components/driver-component.js +127 -0
  50. data/firefox/src/extension/components/firefoxDriver.js +706 -0
  51. data/firefox/src/extension/components/json2.js +273 -0
  52. data/firefox/src/extension/components/keytest.html +554 -0
  53. data/firefox/src/extension/components/nsCommandProcessor.js +586 -0
  54. data/firefox/src/extension/components/screenshooter.js +70 -0
  55. data/firefox/src/extension/components/socketListener.js +185 -0
  56. data/firefox/src/extension/components/utils.js +1200 -0
  57. data/firefox/src/extension/components/webLoadingListener.js +57 -0
  58. data/firefox/src/extension/components/webdriverserver.js +101 -0
  59. data/firefox/src/extension/components/wrappedElement.js +609 -0
  60. data/firefox/src/extension/content/fxdriver.xul +30 -0
  61. data/firefox/src/extension/content/server.js +95 -0
  62. data/firefox/src/extension/idl/nsICommandProcessor.idl +38 -0
  63. data/firefox/src/extension/idl/nsIResponseHandler.idl +34 -0
  64. data/firefox/src/extension/install.rdf +29 -0
  65. data/firefox/src/rb/lib/selenium/webdriver/firefox.rb +21 -0
  66. data/firefox/src/rb/lib/selenium/webdriver/firefox/binary.rb +86 -0
  67. data/firefox/src/rb/lib/selenium/webdriver/firefox/bridge.rb +426 -0
  68. data/firefox/src/rb/lib/selenium/webdriver/firefox/extension_connection.rb +82 -0
  69. data/firefox/src/rb/lib/selenium/webdriver/firefox/launcher.rb +132 -0
  70. data/firefox/src/rb/lib/selenium/webdriver/firefox/profile.rb +174 -0
  71. data/firefox/src/rb/lib/selenium/webdriver/firefox/profiles_ini.rb +60 -0
  72. data/firefox/src/rb/lib/selenium/webdriver/firefox/util.rb +23 -0
  73. data/jobbie/prebuilt/Win32/Release/InternetExplorerDriver.dll +0 -0
  74. data/jobbie/prebuilt/x64/Release/InternetExplorerDriver.dll +0 -0
  75. data/jobbie/src/rb/lib/selenium/webdriver/ie.rb +14 -0
  76. data/jobbie/src/rb/lib/selenium/webdriver/ie/bridge.rb +552 -0
  77. data/jobbie/src/rb/lib/selenium/webdriver/ie/lib.rb +94 -0
  78. data/jobbie/src/rb/lib/selenium/webdriver/ie/util.rb +147 -0
  79. data/remote/client/src/rb/lib/selenium/webdriver/remote.rb +16 -0
  80. data/remote/client/src/rb/lib/selenium/webdriver/remote/bridge.rb +374 -0
  81. data/remote/client/src/rb/lib/selenium/webdriver/remote/capabilities.rb +105 -0
  82. data/remote/client/src/rb/lib/selenium/webdriver/remote/commands.rb +53 -0
  83. data/remote/client/src/rb/lib/selenium/webdriver/remote/default_http_client.rb +71 -0
  84. data/remote/client/src/rb/lib/selenium/webdriver/remote/response.rb +43 -0
  85. data/remote/client/src/rb/lib/selenium/webdriver/remote/server_error.rb +32 -0
  86. metadata +182 -0
@@ -0,0 +1,70 @@
1
+ /*
2
+ Copyright 2007-2009 WebDriver committers
3
+ Copyright 2007-2009 Google Inc.
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ */
17
+
18
+
19
+ function Screenshooter() {
20
+ }
21
+
22
+
23
+ Screenshooter.grab = function(window) {
24
+ var document = window.document;
25
+ var documentElement = document.documentElement;
26
+ var canvas = document.getElementById('fxdriver-screenshot-canvas');
27
+ if (canvas == null) {
28
+ canvas = document.createElement('canvas');
29
+ canvas.id = 'fxdriver-screenshot-canvas';
30
+ canvas.style.display = 'none';
31
+ documentElement.appendChild(canvas);
32
+ }
33
+ var width =
34
+ Math.max(documentElement.scrollWidth, document.body.scrollWidth);
35
+ var height =
36
+ Math.max(documentElement.scrollHeight, document.body.scrollHeight);
37
+ canvas.width = width;
38
+ canvas.height = height;
39
+ var context = canvas.getContext('2d');
40
+ context.drawWindow(window, 0, 0, width, height, 'rgb(0,0,0)');
41
+ return canvas;
42
+ };
43
+
44
+
45
+ Screenshooter.save = function(canvas, filepath) {
46
+ var cc = Components.classes;
47
+ var ci = Components.interfaces;
48
+ var dataUrl = canvas.toDataURL('image/png');
49
+ var ioService = cc['@mozilla.org/network/io-service;1'].
50
+ getService(ci.nsIIOService);
51
+ var dataUri = ioService.newURI(dataUrl, 'UTF-8', null);
52
+ var channel = ioService.newChannelFromURI(dataUri);
53
+ var file = cc['@mozilla.org/file/local;1'].createInstance(ci.nsILocalFile);
54
+ file.initWithPath(filepath);
55
+ var inputStream = channel.open();
56
+ var binaryInputStream = cc['@mozilla.org/binaryinputstream;1'].
57
+ createInstance(ci.nsIBinaryInputStream);
58
+ binaryInputStream.setInputStream(inputStream);
59
+ var fileOutputStream = cc['@mozilla.org/network/safe-file-output-stream;1'].
60
+ createInstance(ci.nsIFileOutputStream);
61
+ fileOutputStream.init(file, -1, -1, null);
62
+ var n = binaryInputStream.available();
63
+ var bytes = binaryInputStream.readBytes(n);
64
+ fileOutputStream.write(bytes, n);
65
+ if (fileOutputStream instanceof ci.nsISafeOutputStream) {
66
+ fileOutputStream.finish();
67
+ } else {
68
+ fileOutputStream.close();
69
+ }
70
+ };
@@ -0,0 +1,185 @@
1
+ /*
2
+ Copyright 2007-2009 WebDriver committers
3
+ Copyright 2007-2009 Google Inc.
4
+ Portions copyright 2007 ThoughtWorks, Inc
5
+
6
+ Licensed under the Apache License, Version 2.0 (the "License");
7
+ you may not use this file except in compliance with the License.
8
+ You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing, software
13
+ distributed under the License is distributed on an "AS IS" BASIS,
14
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ See the License for the specific language governing permissions and
16
+ limitations under the License.
17
+ */
18
+
19
+ /**
20
+ * @fileoverview Defines a class that reads commands from a socket and
21
+ * dispatches them to the nsICommandProcessor. When the response is ready, it is
22
+ * serialized and sent back to the client through the socket.
23
+ */
24
+
25
+
26
+ /**
27
+ * Communicates with a client by reading and writing from a socket.
28
+ * @param {nsISocketTransport} transport The connected socket transport.
29
+ * @constructor
30
+ * @extends {nsIStreamListener}
31
+ */
32
+ function SocketListener(transport) {
33
+ this.outstream = transport.
34
+ openOutputStream(Components.interfaces.nsITransport.OPEN_BLOCKING, 0, 0);
35
+
36
+ this.stream = transport.openInputStream(0, 0, 0);
37
+ var cin = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
38
+ createInstance(Components.interfaces.nsIConverterInputStream);
39
+ cin.init(this.stream, SocketListener.CHARSET, 0, 0x0000);
40
+
41
+ this.inputStream = cin;
42
+
43
+ var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"].
44
+ createInstance(Components.interfaces.nsIInputStreamPump);
45
+ pump.init(this.stream, -1, -1, 0, 0, false);
46
+ pump.asyncRead(this, null);
47
+
48
+ this.linesLeft = "";
49
+ this.data = "";
50
+ this.command = "";
51
+ this.step = 0;
52
+ this.readLength = false;
53
+
54
+ /**
55
+ * A reference to the command processor service. We grab the reference here
56
+ * instead of on the prototype since the component may not be loaded yet.
57
+ * @type {nsICommandProcessor}
58
+ * @private
59
+ */
60
+ this.commandProcessor_ = Components.
61
+ classes['@googlecode.com/webdriver/command-processor;1'].
62
+ getService(Components.interfaces.nsICommandProcessor);
63
+
64
+ /**
65
+ * The converter used when writing data back to the socket.
66
+ * @type {nsIScriptableUnicodeConverter}
67
+ * @private
68
+ */
69
+ this.converter_ = Components.
70
+ classes['@mozilla.org/intl/scriptableunicodeconverter'].
71
+ createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
72
+
73
+ this.converter_.charset = SocketListener.CHARSET;
74
+ }
75
+
76
+
77
+ /**
78
+ * Charset used for socket I/O.
79
+ * @type {string}
80
+ */
81
+ SocketListener.CHARSET = 'UTF-8';
82
+
83
+
84
+ /**
85
+ * Signals the start of a request. Each request lasts for the life of the
86
+ * underlying socket connection and represents a session with a FirefoxDriver
87
+ * client.
88
+ * @see {nsIRequestObserver#onStartRequest}
89
+ */
90
+ SocketListener.prototype.onStartRequest = function(request, context) {
91
+ };
92
+
93
+
94
+ /**
95
+ * Signals the end of a request (e.g. the underlying socket connection was
96
+ * closed).
97
+ * @see {nsIRequestObserver#onStopRequest}
98
+ */
99
+ SocketListener.prototype.onStopRequest = function(request, context, status) {
100
+ };
101
+
102
+
103
+ /**
104
+ * Called whenever another chunk of data is ready to be read from the socket.
105
+ * @param {nsIRequest} request The data's origin.
106
+ * @param {nsISupports} context User defined context.
107
+ * @param {nsIInputStream} inputStream The input stream containing the data
108
+ * chunk.
109
+ * @param {number} offset The total number of bytes read by previous calls to
110
+ * {@code #onDataAvailable}.
111
+ * @param {number} count The number of bytes available in the stream.
112
+ * @see {nsIStreamListener#onDataAvailable}
113
+ */
114
+ SocketListener.prototype.onDataAvailable = function(request, context,
115
+ inputStream, offset,
116
+ count) {
117
+ var incoming = {};
118
+ var read = this.inputStream.readString(count, incoming);
119
+
120
+ var lines = incoming.value.split('\n');
121
+ for (var j = 0; j < lines.length; j++) {
122
+ if (0 == this.step) {
123
+ var head = lines[j].split(": ", 2);
124
+ if (head[0] == "Content-Length") {
125
+ this.linesLeft = Number(head[1]);
126
+ this.readLength = true;
127
+ } else if (lines[j].length == 0 && this.readLength) {
128
+ this.step++;
129
+ }
130
+ } else {
131
+ this.data += lines[j];
132
+ this.linesLeft -= read;
133
+
134
+ if (this.linesLeft <= 0) {
135
+ this.executeCommand_();
136
+ j++; // Consume the empty line
137
+ }
138
+ }
139
+ }
140
+
141
+ if (this.linesLeft <= 0 && this.data) {
142
+ this.executeCommand_();
143
+ }
144
+ };
145
+
146
+
147
+ /**
148
+ * Parses the command data read from the socket into a JSON object and
149
+ * dispatches it to the command processesor.
150
+ * @private
151
+ */
152
+ SocketListener.prototype.executeCommand_ = function() {
153
+ var self = this;
154
+ var command = this.data;
155
+ var callback = function(response) {
156
+ //Utils.dumpn('writing to socket:\n' + response);
157
+ var data = self.converter_.convertToByteArray(response, {});
158
+ var header = "Length: " + data.length + "\n\n";
159
+ self.outstream.write(header, header.length);
160
+ self.outstream.flush();
161
+
162
+ var stream = self.converter_.convertToInputStream(response);
163
+ self.outstream.writeFrom(stream, data.length);
164
+ self.outstream.flush();
165
+ stream.close();
166
+ };
167
+
168
+ // Clear data for the next read.
169
+ this.data = '';
170
+ this.linesLeft = 0;
171
+ this.step = 0;
172
+ this.readLength = 0;
173
+
174
+ try {
175
+ this.commandProcessor_.execute(command, callback);
176
+ } catch (e) {
177
+ Utils.dump(e);
178
+ Utils.dumpn(command);
179
+
180
+ // Something has gone seriously wrong. Quit the browser.
181
+ this.commandProcessor_.execute(
182
+ JSON.stringify({'commandName': 'quit'}),
183
+ function() {});
184
+ }
185
+ };
@@ -0,0 +1,1200 @@
1
+ /*
2
+ Copyright 2007-2009 WebDriver committers
3
+ Copyright 2007-2009 Google Inc.
4
+ Portions copyright 2007 ThoughtWorks, Inc
5
+
6
+ Licensed under the Apache License, Version 2.0 (the "License");
7
+ you may not use this file except in compliance with the License.
8
+ You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing, software
13
+ distributed under the License is distributed on an "AS IS" BASIS,
14
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ See the License for the specific language governing permissions and
16
+ limitations under the License.
17
+ */
18
+
19
+ function StaleElementError() {
20
+ this.isStaleElementError = true;
21
+ }
22
+
23
+ function createSwitchFile(file_content) {
24
+ var filename = "/tmp/switch_window_started";
25
+ var cc = Components.classes;
26
+ var ci = Components.interfaces;
27
+
28
+ try {
29
+ // TODO(eran): Look at the OS and only do this where it makes sense.
30
+ var tmpdir = cc['@mozilla.org/file/local;1'].createInstance(ci.nsILocalFile);
31
+ tmpdir.initWithPath("/tmp");
32
+
33
+ // Do not create a switch file on systems that do not have a /tmp directory
34
+ // - this serves to prevent creation of a switch file on Windows systems.
35
+ if (!tmpdir.exists()) {
36
+ return;
37
+ }
38
+
39
+ var file = cc['@mozilla.org/file/local;1'].createInstance(ci.nsILocalFile);
40
+ file.initWithPath(filename);
41
+ var fileOutputStream = cc['@mozilla.org/network/safe-file-output-stream;1'].createInstance(ci.nsIFileOutputStream);
42
+ fileOutputStream.init(file, -1, -1, null);
43
+
44
+ fileOutputStream.write(file_content, file_content.length);
45
+ if (fileOutputStream instanceof ci.nsISafeOutputStream) {
46
+ fileOutputStream.finish();
47
+ } else {
48
+ fileOutputStream.close();
49
+ }
50
+ } catch (e) {
51
+ // Fine. Log it and continue
52
+ Utils.dumpn(e);
53
+ }
54
+ }
55
+
56
+ function Utils() {
57
+ }
58
+
59
+
60
+ Utils.getUniqueId = function() {
61
+ if (!Utils._generator) {
62
+ Utils._generator =
63
+ Utils.getService("@mozilla.org/uuid-generator;1", "nsIUUIDGenerator");
64
+ }
65
+ return Utils._generator.generateUUID().toString();
66
+ };
67
+
68
+
69
+ Utils.newInstance = function(className, interfaceName) {
70
+ var clazz = Components.classes[className];
71
+
72
+ if (!clazz)
73
+ return undefined;
74
+
75
+ var iface = Components.interfaces[interfaceName];
76
+ return clazz.createInstance(iface);
77
+ };
78
+
79
+
80
+ Utils.getService = function(className, serviceName) {
81
+ var clazz = Components.classes[className];
82
+ if (clazz == undefined) {
83
+ throw new Exception();
84
+ }
85
+
86
+ return clazz.getService(Components.interfaces[serviceName]);
87
+ };
88
+
89
+
90
+ Utils.getServer = function() {
91
+ var handle =
92
+ Utils.newInstance("@googlecode.com/webdriver/fxdriver;1", "nsISupports");
93
+ return handle.wrappedJSObject;
94
+ };
95
+
96
+
97
+ Utils.getBrowser = function(context) {
98
+ return context.fxbrowser;
99
+ };
100
+
101
+
102
+ Utils.getDocument = function(context) {
103
+ if (context.frame) {
104
+ return context.frame.document;
105
+ }
106
+ return context.fxbrowser.contentDocument;
107
+ };
108
+
109
+
110
+ Utils.getActiveElement = function(context) {
111
+ var doc = Utils.getDocument(context);
112
+
113
+ var element;
114
+ if (doc["activeElement"]) {
115
+ element = doc.activeElement;
116
+ } else {
117
+ var commandDispatcher = Utils.getBrowser(context).ownerDocument.
118
+ commandDispatcher;
119
+
120
+ doc = Utils.getDocument(context);
121
+ element = commandDispatcher.focusedElement;
122
+
123
+ if (element && Utils.getDocument(context) != element.ownerDocument)
124
+ element = null;
125
+ }
126
+
127
+ // Default to the body
128
+ if (!element) {
129
+ element = Utils.getDocument(context).body;
130
+ }
131
+
132
+ return element;
133
+ };
134
+
135
+
136
+ function getTextFromNode(node, toReturn, textSoFar) {
137
+ if (node['tagName'] && node.tagName == "SCRIPT") {
138
+ return [toReturn, textSoFar];
139
+ }
140
+ var children = node.childNodes;
141
+
142
+ var bits;
143
+ for (var i = 0; i < children.length; i++) {
144
+ var child = children[i];
145
+
146
+ // Do we need to collapse the text so far?
147
+ if (child["tagName"] && child.tagName == "PRE") {
148
+ toReturn += collapseWhitespace(textSoFar);
149
+ textSoFar = "";
150
+ bits = getTextFromNode(child, toReturn, "", true);
151
+ toReturn += bits[1];
152
+ continue;
153
+ }
154
+
155
+ // Or is this just plain text?
156
+ if (child.nodeName == "#text") {
157
+ if (Utils.isDisplayed(child)) {
158
+ var textToAdd = child.nodeValue;
159
+ textToAdd =
160
+ textToAdd.replace(new RegExp(String.fromCharCode(160), "gm"), " ");
161
+ textSoFar += textToAdd;
162
+ }
163
+ continue;
164
+ }
165
+
166
+ // Treat as another child node.
167
+ bits = getTextFromNode(child, toReturn, textSoFar, false);
168
+ toReturn = bits[0];
169
+ textSoFar = bits[1];
170
+ }
171
+
172
+ if (isBlockLevel(node)) {
173
+ if (node["tagName"] && node.tagName != "PRE") {
174
+ toReturn += collapseWhitespace(textSoFar) + "\n";
175
+ textSoFar = "";
176
+ } else {
177
+ toReturn += "\n";
178
+ }
179
+ }
180
+ return [toReturn, textSoFar];
181
+ }
182
+
183
+
184
+ function isBlockLevel(node) {
185
+ if (node["tagName"] && node.tagName == "BR")
186
+ return true;
187
+
188
+ try {
189
+ // Should we think about getting hold of the current document?
190
+ return "block" == Utils.getStyleProperty(node, "display");
191
+ } catch (e) {
192
+ return false;
193
+ }
194
+ }
195
+
196
+
197
+ Utils.isInHead = function(element) {
198
+ while (element) {
199
+ if (element.tagName && element.tagName.toLowerCase() == "head") {
200
+ return true;
201
+ }
202
+ try {
203
+ element = element.parentNode;
204
+ } catch (e) {
205
+ // Fine. the DOM has dispeared from underneath us
206
+ return false;
207
+ }
208
+ }
209
+
210
+ return false;
211
+ };
212
+
213
+
214
+ Utils.isDisplayed = function(element) {
215
+ // Ensure that we're dealing with an element.
216
+ var el = element;
217
+ while (el.nodeType != 1 && !(el.nodeType >= 9 && el.nodeType <= 11)) {
218
+ el = el.parentNode;
219
+ }
220
+
221
+ if (!el) {
222
+ return false;
223
+ }
224
+
225
+ // Hidden input elements are, by definition, never displayed
226
+ if (el.tagName == "input" && el.type == "hidden") {
227
+ return false;
228
+ }
229
+
230
+ var box = Utils.getLocationOnceScrolledIntoView(el);
231
+ // Elements with zero width or height are never displayed
232
+ if (box.width == 0 || box.height == 0) {
233
+ return false;
234
+ }
235
+
236
+ var visibility = Utils.getStyleProperty(el, "visibility");
237
+
238
+ var _isDisplayed = function(e) {
239
+ var display = e.ownerDocument.defaultView.getComputedStyle(e, null).
240
+ getPropertyValue("display");
241
+ if (display == "none") return display;
242
+ if (e && e.parentNode && e.parentNode.style) {
243
+ return _isDisplayed(e.parentNode);
244
+ }
245
+ return undefined;
246
+ };
247
+
248
+ var displayed = _isDisplayed(el);
249
+
250
+ return displayed != "none" && visibility != "hidden";
251
+ };
252
+
253
+
254
+ /**
255
+ * Gets the computed style of a DOM {@code element}. If the computed style is
256
+ * inherited from the element's parent, the parent will be queried for its
257
+ * style value. If the style value is an RGB color string, it will be converted
258
+ * to hex ("#rrggbb").
259
+ * @param {Element} element The DOM element whose computed style to retrieve.
260
+ * @param {string} propertyName The name of the CSS style proeprty to get.
261
+ * @return {string} The computed style as a string.
262
+ */
263
+ Utils.getStyleProperty = function(element, propertyName) {
264
+ if (!element) {
265
+ return undefined;
266
+ }
267
+
268
+ var value = element.ownerDocument.defaultView.getComputedStyle(element, null).
269
+ getPropertyValue(propertyName);
270
+
271
+ if ('inherit' == value && element.parentNode.style) {
272
+ value = Utils.getStyleProperty(element.parentNode, propertyName);
273
+ }
274
+
275
+ // Convert colours to hex if possible
276
+ var raw = /rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)/.exec(value);
277
+ if (raw) {
278
+ var hex = (Number(raw[1]) << 16) +
279
+ (Number(raw[2]) << 8) +
280
+ (Number(raw[3]));
281
+ hex = (hex & 0x00ffffff) | 0x1000000;
282
+ value = '#' + hex.toString(16).substring(1);
283
+ }
284
+
285
+ return value;
286
+ };
287
+
288
+
289
+ function collapseWhitespace(textSoFar) {
290
+ return textSoFar.replace(/\s+/g, " ");
291
+ }
292
+
293
+
294
+ function getPreformattedText(node) {
295
+ var textToAdd = "";
296
+ return getTextFromNode(node, "", textToAdd, true)[1];
297
+ }
298
+
299
+
300
+ function isWhiteSpace(character) {
301
+ return character == '\n' || character == ' ' || character == '\t' || character
302
+ == '\r';
303
+ }
304
+
305
+
306
+ Utils.getText = function(element) {
307
+ var bits = getTextFromNode(element, "", "", element.tagName == "PRE");
308
+ var text = bits[0] + collapseWhitespace(bits[1]);
309
+ var start = 0;
310
+ while (start < text.length && isWhiteSpace(text[start])) {
311
+ ++start;
312
+ }
313
+ var end = text.length;
314
+ while (end > start && isWhiteSpace(text[end - 1])) {
315
+ --end;
316
+ }
317
+ return text.slice(start, end);
318
+ };
319
+
320
+
321
+ Utils.addToKnownElements = function(element, context) {
322
+ var doc = Utils.getDocument(context);
323
+ if (!doc.fxdriver_elements) {
324
+ doc.fxdriver_elements = {};
325
+ }
326
+
327
+ for (var e in doc.fxdriver_elements) {
328
+ if (doc.fxdriver_elements[e] == element) {
329
+ return e;
330
+ }
331
+ }
332
+
333
+ var id = Utils.getUniqueId();
334
+ doc.fxdriver_elements[id] = element;
335
+
336
+ return id;
337
+ };
338
+
339
+
340
+ Utils.getElementAt = function(index, context) {
341
+ var doc = Utils.getDocument(context);
342
+ var e = doc.fxdriver_elements ? doc.fxdriver_elements[index] : undefined;
343
+ if (e) {
344
+ // Is this a stale reference?
345
+ var parent = e;
346
+ while (parent && parent != e.ownerDocument.documentElement) {
347
+ parent = parent.parentNode;
348
+ }
349
+
350
+ if (parent !== e.ownerDocument.documentElement) {
351
+ // Remove from the cache
352
+ delete doc.fxdriver_elements[index];
353
+
354
+ throw new StaleElementError();
355
+ }
356
+ } else {
357
+ throw new StaleElementError();
358
+ }
359
+
360
+ return e;
361
+ };
362
+
363
+
364
+ Utils.currentDocument = function(context) {
365
+ if (context) {
366
+ return Utils.getDocument(context);
367
+ } else {
368
+ return document;
369
+ }
370
+ };
371
+
372
+
373
+ Utils.platform = function(context) {
374
+ if (!this.userAgentPlatformLowercase) {
375
+ var currentWindow = Utils.currentDocument(context).defaultView;
376
+ this.userAgentPlatformLowercase =
377
+ currentWindow.navigator.platform.toLowerCase();
378
+ }
379
+
380
+ return this.userAgentPlatformLowercase;
381
+ };
382
+
383
+
384
+ Utils.shiftCount = 0;
385
+
386
+
387
+ Utils.getNativeEvents = function() {
388
+ try {
389
+ const cid = "@openqa.org/nativeevents;1";
390
+ var obj = Components.classes[cid].createInstance();
391
+ return obj.QueryInterface(Components.interfaces.nsINativeEvents);
392
+ } catch(e) {
393
+ // Unable to retrieve native events. No biggie, because we fall back to
394
+ // synthesis later
395
+ return undefined;
396
+ }
397
+ };
398
+
399
+
400
+ Utils.getNodeForNativeEvents = function(element) {
401
+ try {
402
+ // This stuff changes between releases.
403
+ // Do as much up-front work in JS as possible
404
+ var retrieval = Utils.newInstance(
405
+ "@mozilla.org/accessibleRetrieval;1", "nsIAccessibleRetrieval");
406
+ var accessible = retrieval.getAccessibleFor(element.ownerDocument);
407
+ var accessibleDoc =
408
+ accessible.QueryInterface(Components.interfaces.nsIAccessibleDocument);
409
+ return accessibleDoc.QueryInterface(Components.interfaces.nsISupports);
410
+ } catch(e) {
411
+ // Unable to retrieve the accessible doc
412
+ return undefined;
413
+ }
414
+ };
415
+
416
+
417
+ Utils.type = function(context, element, text, opt_useNativeEvents) {
418
+
419
+ // For consistency between native and synthesized events, convert common
420
+ // escape sequences to their Key enum aliases.
421
+ text = text.replace(new RegExp('\b', 'g'), '\uE003'). // DOM_VK_BACK_SPACE
422
+ replace(/\t/g, '\uE004'). // DOM_VK_TAB
423
+ replace(/(\r\n|\n|\r)/g, '\uE006'); // DOM_VK_RETURN
424
+
425
+ // Special-case file input elements. This is ugly, but should be okay
426
+ if (element.tagName == "INPUT") {
427
+ var inputtype = element.getAttribute("type");
428
+ if (inputtype && inputtype.toLowerCase() == "file") {
429
+ element.value = text;
430
+ return;
431
+ }
432
+ }
433
+
434
+ var obj = Utils.getNativeEvents();
435
+ var node = Utils.getNodeForNativeEvents(element);
436
+ const thmgr_cls = Components.classes["@mozilla.org/thread-manager;1"];
437
+
438
+ if (opt_useNativeEvents && obj && node && thmgr_cls) {
439
+
440
+ // This indicates that a the page has been unloaded
441
+ var pageHasBeenUnloaded = false;
442
+
443
+ // This is the standard indicator that a page has been unloaded, but
444
+ // due to Firefox's caching policy, will occur only when Firefox works
445
+ // *without* caching at all.
446
+ var unloadFunction = function() { pageHasBeenUnloaded = true; };
447
+
448
+ element.ownerDocument.body.addEventListener("unload",
449
+ unloadFunction, false);
450
+
451
+ // This is a Firefox specific event - See:
452
+ // https://developer.mozilla.org/En/Using_Firefox_1.5_caching
453
+ element.ownerDocument.defaultView.addEventListener("pagehide",
454
+ unloadFunction, false);
455
+
456
+ // Now do the native thing.
457
+ obj.sendKeys(node, text);
458
+
459
+ var hasEvents = {};
460
+ var threadmgr =
461
+ thmgr_cls.getService(Components.interfaces.nsIThreadManager);
462
+ var thread = threadmgr.currentThread;
463
+
464
+ do {
465
+
466
+ // This sleep is needed so that Firefox on Linux will manage to process
467
+ // all of the keyboard events before returning control to the caller
468
+ // code (otherwise the caller may not find all of the keystrokes it
469
+ // has entered).
470
+ var the_window = element.ownerDocument.defaultView;
471
+
472
+ var doneNativeEventWait = false;
473
+
474
+ if (the_window) {
475
+ the_window.setTimeout(function() {
476
+ doneNativeEventWait = true; }, 100);
477
+ }
478
+
479
+ // Do it as long as the timeout function has not been called and the
480
+ // page has not been unloaded. If the page has been unloaded, there is no
481
+ // point in waiting for other native events to be processed in this page
482
+ // as they "belong" to the next page.
483
+ while ((!doneNativeEventWait) && (!pageHasBeenUnloaded)) {
484
+ thread.processNextEvent(true);
485
+ }
486
+
487
+ obj.hasUnhandledEvents(node, hasEvents);
488
+
489
+ } while ((hasEvents.value == true) && (!pageHasBeenUnloaded));
490
+
491
+ if (pageHasBeenUnloaded) {
492
+ Utils.dumpn("Page has been reloaded while waiting for native events to "
493
+ + "be processed. Remaining events? " + hasEvents.value);
494
+ } else {
495
+ // Remove event listeners...
496
+ element.ownerDocument.body.removeEventListener("unload",
497
+ unloadFunction, false);
498
+ element.ownerDocument.defaultView.removeEventListener("pagehide",
499
+ unloadFunction, false);
500
+ }
501
+
502
+ // It is possible that, even though the native code reports all of the
503
+ // keyboard events are out of the GDK event queue, the process is not done.
504
+ // These keyboard events are converted into Javascript events - and not all
505
+ // of them may have been processed. In fact, this is the common case when
506
+ // the sleep timeout above is less than 500 msec.
507
+ // The appropriate thing to do is process all the remaining JS events.
508
+ // Only existing events in the queue should be processed - hence the call
509
+ // to processNextEvent with false.
510
+
511
+ var numExtraEventsProcessed = 0;
512
+ var hasMoreEvents = thread.processNextEvent(false);
513
+ // A safety net to prevent the code from endlessly staying in this loop,
514
+ // in case there is some source of events that's constantly generating them.
515
+ var MAX_EXTRA_EVENTS_TO_PROCESS = 150;
516
+
517
+ while ((hasMoreEvents) &&
518
+ (numExtraEventsProcessed < MAX_EXTRA_EVENTS_TO_PROCESS)) {
519
+ hasMoreEvents = thread.processNextEvent(false);
520
+ numExtraEventsProcessed += 1;
521
+ }
522
+
523
+ return;
524
+ }
525
+
526
+ Utils.dumpn("Doing sendKeys in a non-native way...")
527
+ var controlKey = false;
528
+ var shiftKey = false;
529
+ var altKey = false;
530
+ var metaKey = false;
531
+
532
+ Utils.shiftCount = 0;
533
+
534
+ var upper = text.toUpperCase();
535
+
536
+ for (var i = 0; i < text.length; i++) {
537
+ var c = text.charAt(i);
538
+
539
+ // NULL key: reset modifier key states, and continue
540
+
541
+ if (c == '\uE000') {
542
+ if (controlKey) {
543
+ var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CONTROL;
544
+ Utils.keyEvent(context, element, "keyup", kCode, 0,
545
+ controlKey = false, shiftKey, altKey, metaKey);
546
+ }
547
+
548
+ if (shiftKey) {
549
+ var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SHIFT;
550
+ Utils.keyEvent(context, element, "keyup", kCode, 0,
551
+ controlKey, shiftKey = false, altKey, metaKey);
552
+ }
553
+
554
+ if (altKey) {
555
+ var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ALT;
556
+ Utils.keyEvent(context, element, "keyup", kCode, 0,
557
+ controlKey, shiftKey, altKey = false, metaKey);
558
+ }
559
+
560
+ if (metaKey) {
561
+ var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_META;
562
+ Utils.keyEvent(context, element, "keyup", kCode, 0,
563
+ controlKey, shiftKey, altKey, metaKey = false);
564
+ }
565
+
566
+ continue;
567
+ }
568
+
569
+ // otherwise decode keyCode, charCode, modifiers ...
570
+
571
+ var modifierEvent = "";
572
+ var charCode = 0;
573
+ var keyCode = 0;
574
+
575
+ if (c == '\uE001') {
576
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CANCEL;
577
+ } else if (c == '\uE002') {
578
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_HELP;
579
+ } else if (c == '\uE003') {
580
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_BACK_SPACE;
581
+ } else if (c == '\uE004') {
582
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_TAB;
583
+ } else if (c == '\uE005') {
584
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CLEAR;
585
+ } else if (c == '\uE006') {
586
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_RETURN;
587
+ } else if (c == '\uE007') {
588
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ENTER;
589
+ } else if (c == '\uE008') {
590
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SHIFT;
591
+ shiftKey = !shiftKey;
592
+ modifierEvent = shiftKey ? "keydown" : "keyup";
593
+ } else if (c == '\uE009') {
594
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CONTROL;
595
+ controlKey = !controlKey;
596
+ modifierEvent = controlKey ? "keydown" : "keyup";
597
+ } else if (c == '\uE00A') {
598
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ALT;
599
+ altKey = !altKey;
600
+ modifierEvent = altKey ? "keydown" : "keyup";
601
+ } else if (c == '\uE03D') {
602
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_META;
603
+ metaKey = !metaKey;
604
+ modifierEvent = metaKey ? "keydown" : "keyup";
605
+ } else if (c == '\uE00B') {
606
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_PAUSE;
607
+ } else if (c == '\uE00C') {
608
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ESCAPE;
609
+ } else if (c == '\uE00D') {
610
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SPACE;
611
+ keyCode = charCode = ' '.charCodeAt(0); // printable
612
+ } else if (c == '\uE00E') {
613
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_PAGE_UP;
614
+ } else if (c == '\uE00F') {
615
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN;
616
+ } else if (c == '\uE010') {
617
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_END;
618
+ } else if (c == '\uE011') {
619
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_HOME;
620
+ } else if (c == '\uE012') {
621
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_LEFT;
622
+ } else if (c == '\uE013') {
623
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_UP;
624
+ } else if (c == '\uE014') {
625
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_RIGHT;
626
+ } else if (c == '\uE015') {
627
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_DOWN;
628
+ } else if (c == '\uE016') {
629
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_INSERT;
630
+ } else if (c == '\uE017') {
631
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_DELETE;
632
+ } else if (c == '\uE018') {
633
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SEMICOLON;
634
+ charCode = ';'.charCodeAt(0);
635
+ } else if (c == '\uE019') {
636
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_EQUALS;
637
+ charCode = '='.charCodeAt(0);
638
+ } else if (c == '\uE01A') {
639
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD0;
640
+ charCode = '0'.charCodeAt(0);
641
+ } else if (c == '\uE01B') {
642
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD1;
643
+ charCode = '1'.charCodeAt(0);
644
+ } else if (c == '\uE01C') {
645
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD2;
646
+ charCode = '2'.charCodeAt(0);
647
+ } else if (c == '\uE01D') {
648
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD3;
649
+ charCode = '3'.charCodeAt(0);
650
+ } else if (c == '\uE01E') {
651
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD4;
652
+ charCode = '4'.charCodeAt(0);
653
+ } else if (c == '\uE01F') {
654
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD5;
655
+ charCode = '5'.charCodeAt(0);
656
+ } else if (c == '\uE020') {
657
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD6;
658
+ charCode = '6'.charCodeAt(0);
659
+ } else if (c == '\uE021') {
660
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD7;
661
+ charCode = '7'.charCodeAt(0);
662
+ } else if (c == '\uE022') {
663
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD8;
664
+ charCode = '8'.charCodeAt(0);
665
+ } else if (c == '\uE023') {
666
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD9;
667
+ charCode = '9'.charCodeAt(0);
668
+ } else if (c == '\uE024') {
669
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_MULTIPLY;
670
+ charCode = '*'.charCodeAt(0);
671
+ } else if (c == '\uE025') {
672
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ADD;
673
+ charCode = '+'.charCodeAt(0);
674
+ } else if (c == '\uE026') {
675
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SEPARATOR;
676
+ charCode = ','.charCodeAt(0);
677
+ } else if (c == '\uE027') {
678
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SUBTRACT;
679
+ charCode = '-'.charCodeAt(0);
680
+ } else if (c == '\uE028') {
681
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_DECIMAL;
682
+ charCode = '.'.charCodeAt(0);
683
+ } else if (c == '\uE029') {
684
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_DIVIDE;
685
+ charCode = '/'.charCodeAt(0);
686
+ } else if (c == '\uE031') {
687
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F1;
688
+ } else if (c == '\uE032') {
689
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F2;
690
+ } else if (c == '\uE033') {
691
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F3;
692
+ } else if (c == '\uE034') {
693
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F4;
694
+ } else if (c == '\uE035') {
695
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F5;
696
+ } else if (c == '\uE036') {
697
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F6;
698
+ } else if (c == '\uE037') {
699
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F7;
700
+ } else if (c == '\uE038') {
701
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F8;
702
+ } else if (c == '\uE039') {
703
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F9;
704
+ } else if (c == '\uE03A') {
705
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F10;
706
+ } else if (c == '\uE03B') {
707
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F11;
708
+ } else if (c == '\uE03C') {
709
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F12;
710
+ } else if (c == ',' || c == '<') {
711
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_COMMA;
712
+ charCode = c.charCodeAt(0);
713
+ } else if (c == '.' || c == '>') {
714
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_PERIOD;
715
+ charCode = c.charCodeAt(0);
716
+ } else if (c == '/' || c == '?') {
717
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SLASH;
718
+ charCode = text.charCodeAt(i);
719
+ } else if (c == '`' || c == '~') {
720
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_BACK_QUOTE;
721
+ charCode = c.charCodeAt(0);
722
+ } else if (c == '{' || c == '[') {
723
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET;
724
+ charCode = c.charCodeAt(0);
725
+ } else if (c == '\\' || c == '|') {
726
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_BACK_SLASH;
727
+ charCode = c.charCodeAt(0);
728
+ } else if (c == '}' || c == ']') {
729
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET;
730
+ charCode = c.charCodeAt(0);
731
+ } else if (c == '\'' || c == '"') {
732
+ keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_QUOTE;
733
+ charCode = c.charCodeAt(0);
734
+ } else {
735
+ keyCode = upper.charCodeAt(i);
736
+ charCode = text.charCodeAt(i);
737
+ }
738
+
739
+ // generate modifier key event if needed, and continue
740
+
741
+ if (modifierEvent) {
742
+ Utils.keyEvent(context, element, modifierEvent, keyCode, 0,
743
+ controlKey, shiftKey, altKey, metaKey);
744
+ continue;
745
+ }
746
+
747
+ // otherwise, shift down if needed
748
+
749
+ var needsShift = false;
750
+ if (charCode) {
751
+ needsShift = /[A-Z\!\$\^\*\(\)\+\{\}\:\?\|~@#%&_"<>]/.test(c);
752
+ }
753
+
754
+ if (needsShift && !shiftKey) {
755
+ var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SHIFT;
756
+ Utils.keyEvent(context, element, "keydown", kCode, 0,
757
+ controlKey, true, altKey, metaKey);
758
+ Utils.shiftCount += 1;
759
+ }
760
+
761
+ // generate key[down/press/up] for key
762
+
763
+ var pressCode = keyCode;
764
+ if (charCode >= 32 && charCode < 127) {
765
+ pressCode = 0;
766
+ if (!needsShift && shiftKey && charCode > 32) {
767
+ // If typing a lowercase character key and the shiftKey is down, the
768
+ // charCode should be mapped to the shifted key value. This assumes
769
+ // a default 104 international keyboard layout.
770
+ if (charCode >= 97 && charCode <= 122) {
771
+ charCode = charCode + 65 - 97; // [a-z] -> [A-Z]
772
+ } else {
773
+ var mapFrom = '`1234567890-=[]\\;\',./';
774
+ var mapTo = '~!@#$%^&*()_+{}|:"<>?';
775
+
776
+ var value = String.fromCharCode(charCode).
777
+ replace(/([\[\\\.])/g, '\\$1');
778
+ var index = mapFrom.search(value);
779
+ if (index >= 0) {
780
+ charCode = mapTo.charCodeAt(index);
781
+ }
782
+ }
783
+ }
784
+ }
785
+
786
+ var accepted =
787
+ Utils.keyEvent(context, element, "keydown", keyCode, 0,
788
+ controlKey, needsShift || shiftKey, altKey, metaKey);
789
+
790
+ Utils.keyEvent(context, element, "keypress", pressCode, charCode,
791
+ controlKey, needsShift || shiftKey, altKey, metaKey, !accepted);
792
+
793
+ Utils.keyEvent(context, element, "keyup", keyCode, 0,
794
+ controlKey, needsShift || shiftKey, altKey, metaKey);
795
+
796
+ // shift up if needed
797
+
798
+ if (needsShift && !shiftKey) {
799
+ var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SHIFT;
800
+ Utils.keyEvent(context, element, "keyup", kCode, 0,
801
+ controlKey, false, altKey, metaKey);
802
+ }
803
+ }
804
+
805
+ // exit cleanup: keyup active modifier keys
806
+
807
+ if (controlKey) {
808
+ var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CONTROL;
809
+ Utils.keyEvent(context, element, "keyup", kCode, 0,
810
+ controlKey = false, shiftKey, altKey, metaKey);
811
+ }
812
+
813
+ if (shiftKey) {
814
+ var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SHIFT;
815
+ Utils.keyEvent(context, element, "keyup", kCode, 0,
816
+ controlKey, shiftKey = false, altKey, metaKey);
817
+ }
818
+
819
+ if (altKey) {
820
+ var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ALT;
821
+ Utils.keyEvent(context, element, "keyup", kCode, 0,
822
+ controlKey, shiftKey, altKey = false, metaKey);
823
+ }
824
+
825
+ if (metaKey) {
826
+ var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_META;
827
+ Utils.keyEvent(context, element, "keyup", kCode, 0,
828
+ controlKey, shiftKey, altKey, metaKey = false);
829
+ }
830
+ };
831
+
832
+
833
+ Utils.keyEvent = function(context, element, type, keyCode, charCode,
834
+ controlState, shiftState, altState, metaState,
835
+ shouldPreventDefault) {
836
+ var preventDefault = shouldPreventDefault == undefined ? false
837
+ : shouldPreventDefault;
838
+
839
+ var keyboardEvent =
840
+ Utils.currentDocument(context).createEvent("KeyEvents");
841
+ var currentView =
842
+ Utils.currentDocument(context).defaultView;
843
+
844
+ keyboardEvent.initKeyEvent(
845
+ type, // in DOMString typeArg,
846
+ true, // in boolean canBubbleArg
847
+ true, // in boolean cancelableArg
848
+ currentView, // in nsIDOMAbstractView viewArg
849
+ controlState, // in boolean ctrlKeyArg
850
+ altState, // in boolean altKeyArg
851
+ shiftState, // in boolean shiftKeyArg
852
+ metaState, // in boolean metaKeyArg
853
+ keyCode, // in unsigned long keyCodeArg
854
+ charCode); // in unsigned long charCodeArg
855
+
856
+ if (preventDefault) {
857
+ keyboardEvent.preventDefault();
858
+ }
859
+
860
+ return element.dispatchEvent(keyboardEvent);
861
+ };
862
+
863
+
864
+ Utils.fireHtmlEvent = function(context, element, eventName) {
865
+ var doc = element.ownerDocument;
866
+ var e = doc.createEvent("HTMLEvents");
867
+ e.initEvent(eventName, true, true);
868
+ element.dispatchEvent(e);
869
+ };
870
+
871
+
872
+ Utils.findForm = function(element) {
873
+ // Are we already on an element that can be used to submit the form?
874
+ try {
875
+ element.QueryInterface(Components.interfaces.nsIDOMHTMLButtonElement);
876
+ return element;
877
+ } catch(e) {
878
+ }
879
+
880
+ try {
881
+ var input =
882
+ element.QueryInterface(Components.interfaces.nsIDOMHTMLInputElement);
883
+ if (input.type == "image" || input.type == "submit")
884
+ return input;
885
+ } catch(e) {
886
+ }
887
+
888
+ var form = element;
889
+ while (form) {
890
+ if (form["submit"])
891
+ return form;
892
+ form = form.parentNode;
893
+ }
894
+ return undefined;
895
+ };
896
+
897
+
898
+ Utils.fireMouseEventOn = function(context, element, eventName) {
899
+ Utils.triggerMouseEvent(element, eventName, 0, 0);
900
+ };
901
+
902
+
903
+ Utils.triggerMouseEvent = function(element, eventType, clientX, clientY) {
904
+ var event = element.ownerDocument.createEvent("MouseEvents");
905
+ var view = element.ownerDocument.defaultView;
906
+
907
+ event.initMouseEvent(eventType, true, true, view, 1, 0, 0, clientX, clientY,
908
+ false, false, false, false, 0, element);
909
+ element.dispatchEvent(event);
910
+ };
911
+
912
+
913
+ Utils.findDocumentInFrame = function(browser, frameId) {
914
+ var frame = Utils.findFrame(browser, frameId);
915
+ return frame ? frame.document : null;
916
+ };
917
+
918
+
919
+ Utils.findFrame = function(browser, frameId) {
920
+ var stringId = "" + frameId;
921
+ var names = stringId.split(".");
922
+ var frame = browser.contentWindow;
923
+ for (var i = 0; i < names.length; i++) {
924
+ // Try a numerical index first
925
+ var index = names[i] - 0;
926
+ if (!isNaN(index)) {
927
+ frame = frame.frames[index];
928
+ if (frame) {
929
+ return frame;
930
+ }
931
+ } else {
932
+ // Fine. Use the name and loop
933
+ var found = false;
934
+ for (var j = 0; j < frame.frames.length; j++) {
935
+ var f = frame.frames[j];
936
+ if (f.name == names[i] || f.frameElement.id == names[i]) {
937
+ frame = f;
938
+ found = true;
939
+ break;
940
+ }
941
+ }
942
+
943
+ if (!found) {
944
+ return null;
945
+ }
946
+ }
947
+ }
948
+
949
+ return frame;
950
+ };
951
+
952
+
953
+ Utils.dumpText = function(text) {
954
+ var consoleService = Utils.getService(
955
+ "@mozilla.org/consoleservice;1", "nsIConsoleService");
956
+ if (consoleService)
957
+ consoleService.logStringMessage(text);
958
+ else
959
+ dump(text);
960
+ };
961
+
962
+
963
+ Utils.dumpn = function(text) {
964
+ Utils.dumpText(text + "\n");
965
+ };
966
+
967
+
968
+ Utils.dump = function(element) {
969
+ var dump = "=============\n";
970
+
971
+ var rows = [];
972
+
973
+ dump += "Supported interfaces: ";
974
+ for (var i in Components.interfaces) {
975
+ try {
976
+ var view = element.QueryInterface(Components.interfaces[i]);
977
+ dump += i + ", ";
978
+ } catch (e) {
979
+ // Doesn't support the interface
980
+ }
981
+ }
982
+ dump += "\n------------\n";
983
+
984
+ try {
985
+ Utils.dumpProperties(element, rows);
986
+ } catch (e) {
987
+ Utils.dumpText("caught an exception: " + e);
988
+ }
989
+
990
+ rows.sort();
991
+ for (var i in rows) {
992
+ dump += rows[i] + "\n";
993
+ }
994
+
995
+ dump += "=============\n\n\n";
996
+ Utils.dumpText(dump);
997
+ };
998
+
999
+
1000
+ Utils.dumpProperties = function(view, rows) {
1001
+ for (var i in view) {
1002
+ var value = "\t" + i + ": ";
1003
+ try {
1004
+ if (typeof(view[i]) == typeof(Function)) {
1005
+ value += " function()";
1006
+ } else {
1007
+ value += String(view[i]);
1008
+ }
1009
+ } catch (e) {
1010
+ value += " Cannot obtain value";
1011
+ }
1012
+
1013
+ rows.push(value);
1014
+ }
1015
+ };
1016
+
1017
+
1018
+ Utils.stackTrace = function() {
1019
+ var stack = Components.stack;
1020
+ var i = 5;
1021
+ var dump = "";
1022
+ while (i && stack.caller) {
1023
+ stack = stack.caller;
1024
+ dump += stack + "\n";
1025
+ }
1026
+
1027
+ Utils.dumpText(dump);
1028
+ };
1029
+
1030
+
1031
+ Utils.getElementLocation = function(element, context) {
1032
+ var x = element.offsetLeft;
1033
+ var y = element.offsetTop;
1034
+ var elementParent = element.offsetParent;
1035
+ while (elementParent != null) {
1036
+ if (elementParent.tagName == "TABLE") {
1037
+ var parentBorder = parseInt(elementParent.border);
1038
+ if (isNaN(parentBorder)) {
1039
+ var parentFrame = elementParent.getAttribute('frame');
1040
+ if (parentFrame != null) {
1041
+ x += 1;
1042
+ y += 1;
1043
+ }
1044
+ } else if (parentBorder > 0) {
1045
+ x += parentBorder;
1046
+ y += parentBorder;
1047
+ }
1048
+ }
1049
+ x += elementParent.offsetLeft;
1050
+ y += elementParent.offsetTop;
1051
+ elementParent = elementParent.offsetParent;
1052
+ }
1053
+
1054
+ // Netscape can get confused in some cases, such that the height of the parent
1055
+ // is smaller than that of the element (which it shouldn't really be). If this
1056
+ // is the case, we need to exclude this element, since it will result in too
1057
+ // large a 'top' return value.
1058
+ if (element.offsetParent && element.offsetParent.offsetHeight
1059
+ && element.offsetParent.offsetHeight < element.offsetHeight) {
1060
+ // skip the parent that's too small
1061
+ element = element.offsetParent.offsetParent;
1062
+ } else {
1063
+ // Next up...
1064
+ element = element.offsetParent;
1065
+ }
1066
+ var location = new Object();
1067
+ location.x = x;
1068
+ location.y = y;
1069
+ return location;
1070
+ };
1071
+
1072
+
1073
+ Utils.findElementsByXPath = function (xpath, contextNode, context) {
1074
+ var doc = Utils.getDocument(context);
1075
+ var result = doc.evaluate(xpath, contextNode, null,
1076
+ Components.interfaces.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
1077
+ var indices = [];
1078
+ var element = result.iterateNext();
1079
+ while (element) {
1080
+ var index = Utils.addToKnownElements(element, context);
1081
+ indices.push(index);
1082
+ element = result.iterateNext();
1083
+ }
1084
+ return indices;
1085
+ };
1086
+
1087
+
1088
+ Utils.getLocationOnceScrolledIntoView = function(element) {
1089
+ element.scrollIntoView(true);
1090
+
1091
+ var retrieval = Utils.newInstance(
1092
+ "@mozilla.org/accessibleRetrieval;1", "nsIAccessibleRetrieval");
1093
+
1094
+ try {
1095
+ element = element.wrappedJSObject ? element.wrappedJSObject : element;
1096
+
1097
+ var clientRect = element.getBoundingClientRect();
1098
+
1099
+ // Firefox 3.5
1100
+ if (clientRect['width']) {
1101
+ return {
1102
+ x : clientRect.left + 3,
1103
+ y : clientRect.top,
1104
+ width: clientRect.width,
1105
+ height: clientRect.height
1106
+ };
1107
+ }
1108
+
1109
+ // Firefox 3.0
1110
+ Utils.dumpn("Falling back to firefox3 mechanism");
1111
+ var accessible = retrieval.getAccessibleFor(element);
1112
+ var x = {}, y = {}, width = {}, height = {};
1113
+ accessible.getBounds(x, y, width, height);
1114
+
1115
+ return {
1116
+ x : clientRect.left + 3,
1117
+ y : clientRect.top,
1118
+ width: width.value,
1119
+ height: height.value
1120
+ };
1121
+ } catch(e) {
1122
+ Utils.dumpn(e);
1123
+ // Element doesn't have an accessibility node
1124
+ }
1125
+
1126
+ // Firefox 2.0
1127
+
1128
+ // Fallback. Use the (deprecated) method to find out where the element is in
1129
+ // the viewport. This should be fine to use because we only fall down this
1130
+ // code path on older versions of Firefox (I think!)
1131
+ var theDoc = element.ownerDocument;
1132
+ var box = theDoc.getBoxObjectFor(element);
1133
+
1134
+ // We've seen cases where width is 0, despite the element actually having
1135
+ // children with width.
1136
+ // This happens particularly with GWT.
1137
+ if (box.width == 0 || box.height == 0) {
1138
+ // Check the child, and hope the user doesn't nest this stuff. Walk the
1139
+ // children til we find an element. At this point, we know that width and
1140
+ // height are a polite fiction
1141
+ for (var i = 0; i < element.childNodes.length; i++) {
1142
+ var c = element.childNodes[i];
1143
+ if (c.nodeType == 1) {
1144
+ Utils.dumpn(
1145
+ "Width and height are ficticious values, based on child node");
1146
+ box = theDoc.getBoxObjectFor(c);
1147
+ break;
1148
+ }
1149
+ }
1150
+ }
1151
+
1152
+ return {
1153
+ x : box.x + 3,
1154
+ y : box.y,
1155
+ width: box.width,
1156
+ height: box.height
1157
+ };
1158
+ };
1159
+
1160
+
1161
+ Utils.unwrapParameters = function(wrappedParameters, resultArray, context) {
1162
+ while (wrappedParameters && wrappedParameters.length > 0) {
1163
+ var t = wrappedParameters.shift();
1164
+
1165
+ if (t != null && t.length !== undefined && t.length != null && (t['type']
1166
+ === undefined || t['type'] == null)) {
1167
+ var innerArray = [];
1168
+ Utils.unwrapParameters(t, innerArray);
1169
+ resultArray.push(innerArray);
1170
+ return;
1171
+ }
1172
+
1173
+ if (t['type'] == "ELEMENT") {
1174
+ var element = Utils.getElementAt(t['value'], context);
1175
+ t['value'] = element.wrappedJSObject ? element.wrappedJSObject : element;
1176
+ }
1177
+
1178
+ resultArray.push(t['value']);
1179
+ }
1180
+ };
1181
+
1182
+
1183
+ Utils.wrapResult = function(result, context) {
1184
+ // Sophisticated.
1185
+ if (null === result || undefined === result) {
1186
+ return {resultType: "NULL"};
1187
+ } else if (result['tagName']) {
1188
+ return {resultType: "ELEMENT",
1189
+ response: Utils.addToKnownElements(result, context)};
1190
+ } else if (result !== undefined &&
1191
+ result.constructor.toString().indexOf("Array") != -1) {
1192
+ var array = [];
1193
+ for (var i = 0; i < result.length; i++) {
1194
+ array.push(Utils.wrapResult(result[i], context));
1195
+ }
1196
+ return {resultType: "ARRAY", response: array};
1197
+ } else {
1198
+ return {resultType: "OTHER", response: result};
1199
+ }
1200
+ }