selenium-webdriver 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ }