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,1286 @@
1
+ /**
2
+ * All functions which take elements assume that they are not null,
3
+ * and are present as passed on the DOM.
4
+ */
5
+
6
+ ChromeDriverContentScript = {};
7
+
8
+ ChromeDriverContentScript.internalElementArray = [];
9
+ ChromeDriverContentScript.port = null;
10
+ ChromeDriverContentScript.currentDocument = window.document;
11
+ ChromeDriverContentScript.injectedEmbedElement = null;
12
+ //Record this for async calls (execute), so returner knows what to return
13
+ //(Also so that we can not re-start commands we have already started executing)
14
+ ChromeDriverContentScript.currentSequenceNumber = -1;
15
+
16
+ if (ChromeDriverContentScript.currentDocument.location != "about:blank") {
17
+ //If loading windows using window.open, the port is opened
18
+ //while we are on about:blank (which always reports window.name as ''),
19
+ //and we use port-per-tab semantics, so don't open the port if
20
+ //we're on about:blank
21
+ ChromeDriverContentScript.port = chrome.extension.connect({name: window.name});
22
+ ChromeDriverContentScript.port.onMessage.addListener(parsePortMessage);
23
+ var isFrameset = (ChromeDriverContentScript.currentDocument.getElementsByTagName("frameset").length > 0);
24
+ ChromeDriverContentScript.port.postMessage({response: {response: "newTabInformation",
25
+ value: {statusCode: "no-op", isFrameset: isFrameset, frameCount: window.frames.length,
26
+ portName: ChromeDriverContentScript.port.name}}, sequenceNumber: -1});
27
+ }
28
+
29
+ /**
30
+ * Parse messages coming in on the port.
31
+ * Sends relevant response back down the port
32
+ * @param message JSON message of format:
33
+ * {request: {request: "some command"
34
+ * [, optional params]},
35
+ * sequenceNumber: some sequence number
36
+ * [, followup: message']}
37
+ * where message' is a message to parse after this one
38
+ */
39
+ function parsePortMessage(message) {
40
+ if (message == null || message.request == null) {
41
+ console.log("Received bad request: " + JSON.stringify(message));
42
+ return;
43
+ }
44
+ if (message.sequenceNumber <= ChromeDriverContentScript.currentSequenceNumber) {
45
+ console.log("Already process{ed,ing} request with sequence number: " + message.sequenceNumber + ", ignoring request: " + message);
46
+ return;
47
+ }
48
+
49
+ ChromeDriverContentScript.currentSequenceNumber = message.sequenceNumber;
50
+
51
+ console.log("Received request: " + JSON.stringify(message) + " (" + window.name + ")");
52
+ //wait indicates whether this is a potentially page-changing change (see background.js's sendResponseByXHR)
53
+ var response = {response: message.request.request, value: null, wait: true};
54
+ if (message.request.elementId !== undefined && message.request.elementId != null) {
55
+ //If it seems an elementId has been passed, try to resolve that to an element
56
+ try {
57
+ var element = internalGetElement(message.request.elementId);
58
+ } catch(e) {
59
+ response.value = e;
60
+ ChromeDriverContentScript.port.postMessage({response: response, sequenceNumber: message.sequenceNumber});
61
+ return;
62
+ }
63
+ }
64
+ switch (message.request.request) {
65
+ case "addCookie":
66
+ response.value = setCookie(message.request.cookie);
67
+ response.wait = false;
68
+ break;
69
+ case "clearElement":
70
+ response.value = clearElement(element);
71
+ break;
72
+ case "clickElement":
73
+ response.value = clickElement(element, message.request.elementId);
74
+ break;
75
+ case "nonNativeClickElement":
76
+ //TODO(danielwh): Focus/blur events for non-native clicking
77
+ element.scrollIntoView(true);
78
+ //TODO: Work out a way of firing events,
79
+ //now that synthesising them gives appendMessage errors
80
+ Utils.fireMouseEventOn(element, "mousedown");
81
+ Utils.fireMouseEventOn(element, "mouseup");
82
+ Utils.fireMouseEventOn(element, "click");
83
+ if (element.click) {
84
+ element.click();
85
+ }
86
+ response.value = {statusCode: 0};
87
+ break;
88
+ case "deleteAllCookies":
89
+ response.value = deleteAllCookies();
90
+ response.wait = false;
91
+ break;
92
+ case "deleteCookie":
93
+ response.value = deleteCookie(message.request.name);
94
+ response.wait = false;
95
+ break;
96
+ case "executeScript":
97
+ execute(message.request.script, message.request.args);
98
+ //Sends port message back to background page from its own callback
99
+ break;
100
+ case "getCookies":
101
+ response.value = getCookies();
102
+ response.wait = false;
103
+ break;
104
+ case "getCookie":
105
+ response.value = getCookieNamed(message.request.name);
106
+ response.wait = false;
107
+ break;
108
+ case "findChildElement":
109
+ response.value = getElement(false, message.request.using, message.request.value, message.request.id);
110
+ response.wait = false;
111
+ break;
112
+ case "findChildElements":
113
+ response.value = getElement(true, message.request.using, message.request.value, message.request.id);
114
+ response.wait = false;
115
+ break;
116
+ case "findElement":
117
+ response.value = getElement(false, message.request.using, message.request.value);
118
+ response.wait = false;
119
+ break;
120
+ case "findElements":
121
+ response.value = getElement(true, message.request.using, message.request.value);
122
+ response.wait = false;
123
+ break;
124
+ case "getElementAttribute":
125
+ response.value = getElementAttribute(element, message.request.attribute);
126
+ response.wait = false;
127
+ break;
128
+ case "getElementValueOfCssProperty":
129
+ response.value = {statusCode: 0, value: getStyle(element, message.request.css)};
130
+ response.wait = false;
131
+ break;
132
+ case "getElementLocation":
133
+ var coords = getElementCoords(element);
134
+ response.value = {statusCode: 0, value: {type: "POINT", x: coords[0], y: coords[1]}};
135
+ response.wait = false;
136
+ break;
137
+ case "getElementLocationOnceScrolledIntoView":
138
+ element.scrollIntoView(true);
139
+ var coords = getElementCoords(element);
140
+ response.value = {statusCode: 0, value: {type: "POINT", x: coords[0], y: coords[1]}};
141
+ break;
142
+ case "getElementSize":
143
+ response.value = {statusCode: 0, value: getOffsetSizeFromSubElements(element)};
144
+ response.wait = false;
145
+ break;
146
+ case "getElementTagName":
147
+ response.value = {statusCode: 0, value: element.tagName.toLowerCase()};
148
+ response.wait = false;
149
+ break;
150
+ case "getElementText":
151
+ response.value = {statusCode: 0, value: Utils.getText(element)};
152
+ response.wait = false;
153
+ break;
154
+ case "getElementValue":
155
+ response.value = {statusCode: 0, value: element.value};
156
+ response.wait = false;
157
+ break;
158
+ case "getFrameNameFromIndex":
159
+ //TODO(danielwh): Do this by simply looking it up in window.frames when Chrome is fixed. Track: crbug.com 20773
160
+ getFrameNameFromIndex(message.request.index);
161
+ break;
162
+ case "getPageSource":
163
+ response.value = getSource();
164
+ response.wait = false;
165
+ break;
166
+ case "getTitle":
167
+ response.value = {statusCode: 0, value: ChromeDriverContentScript.currentDocument.title};
168
+ response.wait = false;
169
+ break;
170
+ case "getCurrentUrl":
171
+ response.value = {statusCode: 0, value: ChromeDriverContentScript.currentDocument.location.href};
172
+ response.wait = false;
173
+ break;
174
+ case "goBack":
175
+ history.back();
176
+ response.value = {statusCode: 0};
177
+ break;
178
+ case "goForward":
179
+ history.forward();
180
+ response.value = {statusCode: 0};
181
+ break;
182
+ case "hoverOverElement":
183
+ response.value = hoverElement(element, message.request.elementId);
184
+ break;
185
+ case "injectEmbed":
186
+ injectEmbed();
187
+ break;
188
+ case "isElementDisplayed":
189
+ response.value = {statusCode: 0, value: isElementDisplayed(element)};
190
+ response.wait = false;
191
+ break;
192
+ case "isElementEnabled":
193
+ response.value = {statusCode: 0, value: !element.disabled};
194
+ response.wait = false;
195
+ break;
196
+ case "isElementSelected":
197
+ response.value = {statusCode: 0, value: findWhetherElementIsSelected(element)};
198
+ response.wait = false;
199
+ break;
200
+ case "refresh":
201
+ ChromeDriverContentScript.currentDocument.location.reload(true);
202
+ response.value = {statusCode: 0};
203
+ break;
204
+ case "sendKeysToElement":
205
+ response.value = sendElementKeys(element, message.request.keys, message.request.elementId);
206
+ response.wait = false;
207
+ break;
208
+ case "sendElementNonNativeKeys":
209
+ response.value = sendElementNonNativeKeys(element, message.request.keys);
210
+ response.wait = false;
211
+ break;
212
+ case "setElementSelected":
213
+ response.value = selectElement(element);
214
+ break;
215
+ case "getActiveElement":
216
+ response.value = {statusCode: 0, value: [addElementToInternalArray(ChromeDriverContentScript.currentDocument.activeElement).toString()]};
217
+ response.wait = false;
218
+ break;
219
+ case "switchToNamedIFrameIfOneExists":
220
+ response.value = switchToNamedIFrameIfOneExists(message.request.name);
221
+ response.wait = false;
222
+ break;
223
+ case "submitElement":
224
+ response.value = submitElement(element);
225
+ break;
226
+ case "toggleElement":
227
+ response.value = toggleElement(element);
228
+ break;
229
+ case "sniffForMetaRedirects":
230
+ response.value = sniffForMetaRedirects();
231
+ break;
232
+ default:
233
+ response.value = {statusCode: 9, value: {message: message.request.request + " is unsupported"}};
234
+ break;
235
+ }
236
+ if (response.value != null) {
237
+ ChromeDriverContentScript.port.postMessage({response: response, sequenceNumber: message.sequenceNumber})
238
+ console.log("Sent response: " + JSON.stringify(response) + " (seq:" + message.sequenceNumber + ")");
239
+ }
240
+ if (message.request.followup) {
241
+ setTimeout(parsePortMessage(message.request.followup), 100);
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Deletes all cookies accessible from the current page.
247
+ */
248
+ function deleteAllCookies() {
249
+ var cookies = getAllCookiesAsStrings();
250
+ for (var i = 0; i < cookies.length; ++i) {
251
+ var cookie = cookies[i].split("=");
252
+ deleteCookie(cookie[0]);
253
+ }
254
+ return {statusCode: 0};
255
+ }
256
+
257
+ /**
258
+ * Deletes the cookie with the passed name, accessible from the current page (i.e. with path of the current directory or above)
259
+ * @param cookieName name of the cookie to delete
260
+ */
261
+ function deleteCookie(cookieName) {
262
+ //It's possible we're trying to delete cookies within iframes.
263
+ //iframe stuff is unsupported in Chrome at the moment (track crbug.com/20773)
264
+ //But for the iframe to be loaded and have cookies, it must be of same origin,
265
+ //so we'll try deleting the cookie as if it was on this page anyway...
266
+ //(Yes, this is a hack)
267
+ //TODO(danielwh): Remove the cookieDocument stuff when Chrome fix frame support
268
+ try {
269
+ var fullpath = ChromeDriverContentScript.currentDocument.location.pathname;
270
+ var cookieDocument = ChromeDriverContentScript.currentDocument;
271
+ } catch (e) {
272
+ console.log("Falling back on non-iframe document to delete cookies");
273
+ var cookieDocument = document;
274
+ var fullpath = cookieDocument.location.pathname;
275
+ }
276
+ var hostParts = cookieDocument.location.hostname.split(".");
277
+
278
+ fullpath = fullpath.split('/');
279
+ fullpath.pop(); //Get rid of the file
280
+ //TODO(danielwh): Tidy up these loops and this repeated code
281
+ for (var segment in fullpath) {
282
+ var path = '';
283
+ for (var i = 0; i < segment; ++i) {
284
+ path += fullpath[i + 1] + '/';
285
+ }
286
+ //Delete cookie with trailing /
287
+ cookieDocument.cookie = cookieName + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/' + path;
288
+ //Delete cookie without trailing /
289
+ cookieDocument.cookie = cookieName + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/' + path.substring(0, path.length - 1);
290
+
291
+ var domain = "";
292
+ for (var i = hostParts.length - 1; i >= 0; --i) {
293
+ domain = "." + hostParts[i] + domain;
294
+ //Delete cookie with trailing /
295
+ cookieDocument.cookie = cookieName + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/' + path + ";domain=" + domain;
296
+
297
+ cookieDocument.cookie = cookieName + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/' + path + ";domain=" + domain;
298
+ //Delete cookie without trailing /
299
+ cookieDocument.cookie = cookieName + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/' + path.substring(0, path.length - 1) + ";domain=" + domain;
300
+
301
+ cookieDocument.cookie = cookieName + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/' + path.substring(0, path.length - 1) + ";domain=" + domain.substring(1);
302
+ }
303
+ }
304
+ return {statusCode: 0};
305
+ }
306
+
307
+ /**
308
+ * Get all cookies accessible from the current page as an array of
309
+ * {name: some name, value: some value, secure: false} values
310
+ */
311
+ function getCookies() {
312
+ var cookies = [];
313
+ var cookieStrings = getAllCookiesAsStrings();
314
+ for (var i = 0; i < cookieStrings.length; ++i) {
315
+ var cookie = cookieStrings[i].split("=");
316
+ cookies.push({type: "COOKIE", name: cookie[0], value: cookie[1], secure: false});
317
+ }
318
+ return {statusCode: 0, value: cookies};
319
+ }
320
+
321
+ /**
322
+ * Get the cookie accessible from the current page with the passed name
323
+ * @param name name of the cookie
324
+ */
325
+ function getCookieNamed(name) {
326
+ var cookies = [];
327
+ var cookieStrings = getAllCookiesAsStrings();
328
+ for (var i = 0; i < cookieStrings.length; ++i) {
329
+ var cookie = cookieStrings[i].split("=");
330
+ if (cookie[0] == name) {
331
+ return {statusCode: 0, value: {type: "COOKIE", name: cookie[0], value: cookie[1], secure: false}};
332
+ }
333
+ }
334
+ return {statusCode: 0, value: null};
335
+ }
336
+
337
+ /**
338
+ * Gets all cookies accessible from the current page as an array of
339
+ * key=value strings
340
+ */
341
+ function getAllCookiesAsStrings() {
342
+ //It's possible we're trying to delete cookies within iframes.
343
+ //iframe stuff is unsupported in Chrome at the moment (track crbug.com/20773)
344
+ //But for the iframe to be loaded and have cookies, it must be of same origin,
345
+ //so we'll try deleting the cookie as if it was on this page anyway...
346
+ //(Yes, this is a hack)
347
+ //TODO(danielwh): Remove the cookieDocument stuff when Chrome fix frame support
348
+ var cookieDocument = ChromeDriverContentScript.currentDocument;
349
+ try {
350
+ var tempFullpath = ChromeDriverContentScript.currentDocument.location.pathname;
351
+ } catch (e) {
352
+ cookieDocument = document;
353
+ }
354
+ var cookieStrings = cookieDocument.cookie.split('; ');
355
+ var cookies = [];
356
+ for (var i = 0; i < cookieStrings.length; ++i) {
357
+ if (cookieStrings[i] == '') {
358
+ break;
359
+ }
360
+ cookies.push(cookieStrings[i]);
361
+ }
362
+ return cookies;
363
+ }
364
+
365
+ /**
366
+ * Add the passed cookie to the page's cookies
367
+ * @param cookie org.openqa.selenium.Cookie to add
368
+ */
369
+ function setCookie(cookie) {
370
+ //It's possible we're trying to delete cookies within iframes.
371
+ //iframe stuff is unsupported in Chrome at the moment (track crbug.com/20773)
372
+ //But for the iframe to be loaded and have cookies, it must be of same origin,
373
+ //so we'll try deleting the cookie as if it was on this page anyway...
374
+ //(Yes, this is a hack)
375
+ //TODO(danielwh): Remove the cookieDocument stuff when Chrome fix frame support
376
+ try {
377
+ var currLocation = ChromeDriverContentScript.currentDocument.location;
378
+ var currDomain = currLocation.host;
379
+ var cookieDocument = ChromeDriverContentScript.currentDocument;
380
+ } catch (e) {
381
+ cookieDocument = document;
382
+ var currLocation = ChromeDriverContentScript.currentDocument.location;
383
+ var currDomain = currLocation.host;
384
+ }
385
+
386
+ if (currLocation.port != 80) { currDomain += ":" + currLocation.port; }
387
+ if (cookie.domain != null && cookie.domain !== undefined &&
388
+ currDomain.indexOf(cookie.domain) == -1) {
389
+ // Not quite right, but close enough. (See r783)
390
+ return {statusCode: 2, value: {
391
+ message: "You may only set cookies for the current domain"}};
392
+ } else if (guessPageType() != "html") {
393
+ return {statusCode: 2, value: {
394
+ message: "You may only set cookies on html documents"}};
395
+ } else {
396
+ cookieDocument.cookie = cookie.name + '=' + escape(cookie.value) +
397
+ ((cookie.expiry == null || cookie.expiry === undefined) ?
398
+ '' : ';expires=' + (new Date(cookie.expiry.time)).toGMTString()) +
399
+ ((cookie.path == null || cookie.path === undefined) ?
400
+ '' : ';path=' + cookie.path);
401
+ return {statusCode: 0};
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Get an element, or a set of elements, by some lookup
407
+ * Called by both findElement and findElements
408
+ * @param plural true if want array of all elements, false if singular element
409
+ * @param parsed array showing how to look up, e.g. ["id", "cheese"] or
410
+ * [{"id": 0, using: "id", value: "cheese"}]
411
+ */
412
+ function getElement(plural, lookupBy, lookupValue, id) {
413
+ var root = "";
414
+ var parent = null;
415
+ if (id !== undefined && id != null) {
416
+ try {
417
+ parent = internalGetElement(id);
418
+ } catch (e) {
419
+ return e;
420
+ }
421
+ //Looking for children
422
+ root = getXPathOfElement(parent);
423
+ } else {
424
+ parent = ChromeDriverContentScript.currentDocument;
425
+ }
426
+
427
+ var elements = [];
428
+ var attribute = '';
429
+ switch (lookupBy) {
430
+ case "class name":
431
+ elements = getElementsByXPath(root +
432
+ "//*[contains(concat(' ',normalize-space(@class),' '),' " +
433
+ lookupValue + " ')]");
434
+ break;
435
+ case "name":
436
+ attribute = 'name';
437
+ break;
438
+ case "id":
439
+ attribute = 'id';
440
+ break;
441
+ case "link text":
442
+ elements = getElementsByLinkText(parent, lookupValue);
443
+ break;
444
+ case "partial link text":
445
+ elements = getElementsByPartialLinkText(parent, lookupValue);
446
+ break;
447
+ case "tag name":
448
+ elements = getElementsByXPath(root + "//" + lookupValue);
449
+ break;
450
+ case "xpath":
451
+ if (root != "") {
452
+ //Because xpath is relative to the parent, if there is a parent, we add a /
453
+ root = root + "/";
454
+ }
455
+ var xpath = root + lookupValue;
456
+ try {
457
+ elements = getElementsByXPath(xpath);
458
+ } catch (e) {
459
+ return {statusCode: 19, value: {message: "Could not look up element by xpath " + xpath}};
460
+ }
461
+ break;
462
+ }
463
+ if (attribute != '') {
464
+ elements = getElementsByXPath(root + "//*[@" + attribute + "='" + lookupValue + "']");
465
+ }
466
+ if (elements == null || elements.length == 0) {
467
+ if (plural) {
468
+ //Fine, no elements matched
469
+ return {statusCode: 0, value: []};
470
+ } else {
471
+ //Problem - we were expecting an element
472
+ return {statusCode: 7, value: {
473
+ message: "Unable to find element with " + lookupBy + " " + lookupValue}};
474
+ }
475
+ } else {
476
+ var elementsToReturnArray = [];
477
+ if (plural) {
478
+ //Add all found elements to the page's elements, and push each to the array to return
479
+ var addedElements = addElementsToInternalArray(elements);
480
+ for (var addedElement in addedElements) {
481
+ elementsToReturnArray.push(addedElements[addedElement].toString());
482
+ }
483
+ } else {
484
+ if (!elements[0]) {
485
+ return {statusCode: 7, value: {
486
+ message: "Unable to find element with " + lookupBy + " " + lookupValue}};
487
+ }
488
+ //Add the first found elements to the page's elements, and push it to the array to return
489
+ elementsToReturnArray.push(addElementToInternalArray(elements[0]).toString());
490
+ }
491
+ return {statusCode: 0, value: elementsToReturnArray};
492
+ }
493
+ }
494
+
495
+ function addElementToInternalArray(element) {
496
+ for (var existingElement in ChromeDriverContentScript.internalElementArray) {
497
+ if (element == ChromeDriverContentScript.internalElementArray[existingElement]) {
498
+ return existingElement;
499
+ }
500
+ }
501
+ ChromeDriverContentScript.internalElementArray.push(element);
502
+ return (ChromeDriverContentScript.internalElementArray.length - 1);
503
+ }
504
+
505
+ function addElementsToInternalArray(elements) {
506
+ var toReturn = [];
507
+ for (var element in elements) {
508
+ toReturn.push(addElementToInternalArray(elements[element]));
509
+ }
510
+ return toReturn;
511
+ }
512
+
513
+ /**
514
+ * Gets an element which we have previously looked up by its internal ID
515
+ * @param elementIdAsString the element's internal ID
516
+ * @return element with the passed internal ID
517
+ * @throws if element ID was stale: wrapped up
518
+ * org.openqa.selenium.StaleElementReferenceException ready to send
519
+ */
520
+ function internalGetElement(elementIdAsString) {
521
+ var elementId = parseInt(elementIdAsString);
522
+ if (elementId != null && elementId >= 0 &&
523
+ ChromeDriverContentScript.internalElementArray.length >= elementId + 1) {
524
+ var element = ChromeDriverContentScript.internalElementArray[elementId];
525
+ var parent = element;
526
+ while (parent && parent != element.ownerDocument.documentElement) {
527
+ parent = parent.parentNode;
528
+ }
529
+ if (parent !== element.ownerDocument.documentElement) {
530
+ throw {statusCode: 10, value: {message: "Element is obsolete"}};
531
+ }
532
+ return element;
533
+ } else {
534
+ throw {statusCode: 10, value: {message: "Element is obsolete"}};
535
+ }
536
+ }
537
+
538
+ /**
539
+ * Ensures the passed element is in view, so that the native click event can be sent
540
+ * @return object to send back to background page to trigger a native click
541
+ */
542
+ function clickElement(element, elementId) {
543
+ try {
544
+ checkElementIsDisplayed(element)
545
+ } catch (e) {
546
+ return e;
547
+ }
548
+ element.scrollIntoView(true);
549
+ var size = getOffsetSizeFromSubElements(element);
550
+ var coords = getElementCoords(element);
551
+ return {statusCode: "no-op", elementId: elementId,
552
+ x: parseInt(coords[0] - ChromeDriverContentScript.currentDocument.body.scrollLeft + (size.width ? size.width / 2 : 0)),
553
+ y: parseInt(coords[1] - ChromeDriverContentScript.currentDocument.body.scrollTop + (size.height ? size.height / 2 : 0))};
554
+ }
555
+
556
+ /**
557
+ * Ensures the passed element is in view, so that the native click event can be sent
558
+ * @return object to send back to background page to trigger a native click
559
+ */
560
+ function hoverElement(element, elementId) {
561
+ try {
562
+ checkElementIsDisplayed(element)
563
+ } catch (e) {
564
+ return e;
565
+ }
566
+ element.scrollIntoView(true);
567
+ var size = getOffsetSizeFromSubElements(element)
568
+ var coords = getElementCoords(element);
569
+ console.log("element.clientX: " + element.clientX);
570
+ return {statusCode: "no-op", elementId: elementId,
571
+ oldX: 0,
572
+ oldY: 0,
573
+ newX: coords[0] - ChromeDriverContentScript.currentDocument.body.scrollLeft + (size.width ? size.width / 2 : 0),
574
+ newY: coords[1] - ChromeDriverContentScript.currentDocument.body.scrollTop + (size.height ? size.height / 2 : 0)};
575
+ }
576
+
577
+ /**
578
+ * Clears the passed element
579
+ */
580
+ function clearElement(element) {
581
+ var oldValue = element.value;
582
+ element.value = '';
583
+ if (oldValue != '') {
584
+ //TODO: Work out a way of firing events,
585
+ //now that synthesising them gives appendMessage errors
586
+ Utils.fireHtmlEvent(element, "change");
587
+ }
588
+ return {statusCode: 0};
589
+ }
590
+
591
+ /**
592
+ * Gets the attribute of the element
593
+ * If the attribute is {disabled, selected, checked, index}, always returns
594
+ * the sensible default (i.e. never null)
595
+ */
596
+ function getElementAttribute(element, attribute) {
597
+ var value = null;
598
+ switch (attribute.toLowerCase()) {
599
+ case "disabled":
600
+ value = (element.disabled ? element.disabled : "false");
601
+ break;
602
+ case "selected":
603
+ value = findWhetherElementIsSelected(element);
604
+ break;
605
+ case "checked":
606
+ value = (element.checked ? element.checked : "false");
607
+ break;
608
+ case "index":
609
+ value = element.index;
610
+ break;
611
+ }
612
+ if (value == null) {
613
+ value = element.getAttribute(attribute);
614
+ }
615
+ return {statusCode: 0, value: value};
616
+ }
617
+
618
+ /**
619
+ * Gets the source of the current document
620
+ */
621
+ function getSource() {
622
+ if (guessPageType() == "html") {
623
+ return {statusCode: 0, value: ChromeDriverContentScript.currentDocument.getElementsByTagName("html")[0].outerHTML};
624
+ } else if (guessPageType() == "text") {
625
+ return {statusCode: 0, value: ChromeDriverContentScript.currentDocument.getElementsByTagName("pre")[0].innerHTML};
626
+ }
627
+ }
628
+
629
+ /**
630
+ * Get whether the element is currently displayed (i.e. can be seen in the browser)
631
+ */
632
+ function isElementDisplayed(element) {
633
+ try {
634
+ checkElementIsDisplayed(element)
635
+ } catch (e) {
636
+ return false;
637
+ }
638
+ return true;
639
+ }
640
+
641
+ /**
642
+ * Selects the element (i.e. sets its selected/checked value to true)
643
+ * @param element An option element or input element with type checkbox or radio
644
+ */
645
+ function selectElement(element) {
646
+ var oldValue = true;
647
+ try {
648
+ checkElementIsDisplayed(element);
649
+ checkElementNotDisabled(element);
650
+ var tagName = element.tagName.toLowerCase();
651
+ if (tagName == "option") {
652
+ oldValue = element.selected;
653
+ element.selected = true;
654
+ } else if (tagName == "input") {
655
+ var type = element.getAttribute("type").toLowerCase();
656
+ if (type == "checkbox") {
657
+ oldValue = element.checked;
658
+ element.checked = true;
659
+ } else if (type == "radio") {
660
+ oldValue = element.checked;
661
+ element.checked = true;
662
+ } else {
663
+ throw {statusCode: 12, value: {message: "Cannot select an input." + type}};
664
+ }
665
+ } else {
666
+ throw {statusCode: 12, value: {message: "Cannot select a " + tagName}};
667
+ }
668
+ } catch(e) {
669
+ return e;
670
+ }
671
+ if (!oldValue) {
672
+ //TODO: Work out a way of firing events,
673
+ //now that synthesising them gives appendMessage errors
674
+ Utils.fireHtmlEvent(element, "change");
675
+ }
676
+ return {statusCode: 0};
677
+ }
678
+
679
+ /**
680
+ * Focus the element so that native code can type to it
681
+ * @param keys the string to type. Must not be an array.
682
+ */
683
+ function sendElementKeys(element, keys, elementId) {
684
+ try {
685
+ checkElementIsDisplayed(element)
686
+ } catch (e) {
687
+ return e;
688
+ }
689
+ var oldFocusedElement = ChromeDriverContentScript.currentDocument.activeElement;
690
+ if (oldFocusedElement != element) {
691
+ //TODO: Work out a way of firing events,
692
+ //now that synthesising them gives appendMessage errors
693
+ oldFocusedElement.blur();
694
+ Utils.fireHtmlEvent(oldFocusedElement, "blur");
695
+ element.focus();
696
+ Utils.fireHtmlEvent(element, "focus");
697
+ }
698
+ return {statusCode: "no-op", keys: keys, elementId: elementId};
699
+ }
700
+
701
+ /**
702
+ * @param keys the string to type. Must not be an array.
703
+ */
704
+ function sendElementNonNativeKeys(element, keys) {
705
+ //TODO(danielwh): Any kind of actually support for non-native keys
706
+ for (var i = 0; i < keys.length; i++) {
707
+ element.value += keys.charAt(i);
708
+ }
709
+ return {statusCode: 0};
710
+ }
711
+
712
+ /**
713
+ * Submits the element if it is a form, or the closest enclosing form otherwise
714
+ */
715
+ function submitElement(element) {
716
+ while (element != null) {
717
+ if (element.tagName.toLowerCase() == "form") {
718
+ element.submit();
719
+ return {statusCode: 0};
720
+ }
721
+ element = element.parentElement;
722
+ }
723
+ return {statusCode: 12, value: {message: "Cannot submit an element not in a form"}};
724
+ }
725
+
726
+ /**
727
+ * Toggles the element if it is an input element of type checkbox,
728
+ * or option element in a multi-select select element
729
+ */
730
+ function toggleElement(element) {
731
+ var changed = false;
732
+ var oldValue = null;
733
+ var newValue = null;
734
+ try {
735
+ checkElementIsDisplayed(element);
736
+ checkElementNotDisabled(element);
737
+ var tagName = element.tagName.toLowerCase();
738
+ if (tagName == "option") {
739
+ var parent = element;
740
+ while (parent != null && parent.tagName.toLowerCase() != "select") {
741
+ parent = parent.parentElement;
742
+ }
743
+ if (parent == null) {
744
+ throw {statusCode: 12, value: {message: "option tag had no select tag parent"}};
745
+ }
746
+ oldValue = element.selected;
747
+ if (oldValue && !parent.multiple) {
748
+ throw {statusCode: 12, value: {message: "Cannot unselect a single element select"}};
749
+ }
750
+ newValue = element.selected = !oldValue;
751
+ } else if (tagName == "input") {
752
+ var type = element.getAttribute("type").toLowerCase();
753
+ if (type == "checkbox") {
754
+ oldValue = element.checked;
755
+ newValue = element.checked = !oldValue;
756
+ changed = true;
757
+ } else {
758
+ throw {statusCode: 12, value: {message: "Cannot toggle an input." + type}};
759
+ }
760
+ } else {
761
+ throw {statusCode: 12, value: {message: "Cannot toggle a " + tagName}};
762
+ }
763
+ } catch (e) {
764
+ return e;
765
+ }
766
+ if (changed) {
767
+ //TODO: Work out a way of firing events,
768
+ //now that synthesising them gives appendMessage errors
769
+ Utils.fireHtmlEvent(element, "change");
770
+ }
771
+ return {statusCode: 0, value: newValue};
772
+ }
773
+
774
+ /**
775
+ * Gets the CSS property asked for
776
+ * @param style CSS property to get
777
+ */
778
+ function getStyle(element, style) {
779
+ var value = ChromeDriverContentScript.currentDocument.defaultView.getComputedStyle(element, null).getPropertyValue(style);
780
+ return rgbToRRGGBB(value);
781
+ }
782
+
783
+ function switchToNamedIFrameIfOneExists(name) {
784
+ var iframes = ChromeDriverContentScript.currentDocument.getElementsByTagName("iframe");
785
+ for (var i = 0; i < iframes.length; ++i) {
786
+ if (iframes[i].name == name || iframes[i].id == name) {
787
+ ChromeDriverContentScript.currentDocument = iframes[i].contentDocument;
788
+ return {statusCode: 0};
789
+ }
790
+ }
791
+ return {statusCode: 8, value: {message: 'Could not find iframe to switch to by name:' + name}};
792
+ }
793
+
794
+ function sniffForMetaRedirects() {
795
+ for (var i = 0; i < ChromeDriverContentScript.currentDocument.getElementsByTagName("meta").length; ++i) {
796
+ if (ChromeDriverContentScript.currentDocument.getElementsByTagName("meta")[i].hasAttribute("http-equiv") &&
797
+ ChromeDriverContentScript.currentDocument.getElementsByTagName("meta")[i].getAttribute("http-equiv").toLowerCase == "refresh") {
798
+ return {statusCode: "no-op", value: true};
799
+ }
800
+ }
801
+ return {statusCode: "no-op", value: false};
802
+ }
803
+
804
+ function parseWrappedArguments(argument) {
805
+ //Parse the arguments into actual values (which are wrapped up in JSON)
806
+ if (argument.length !== undefined) {
807
+ var array = [];
808
+ for (var i = 0; i < argument.length; ++i) {
809
+ array.push(parseWrappedArguments(argument[i]));
810
+ }
811
+ return {success: true, value: array};
812
+ }
813
+ switch (argument.type) {
814
+ case "ELEMENT":
815
+ //Wrap up as a special object with the element's canonical xpath, which the page can work out
816
+ var element_id = argument.value;
817
+ var element = null;
818
+ try {
819
+ element = internalGetElement(element_id);
820
+ } catch (e) {
821
+ return {success: false, value: {response: "execute", value:
822
+ {statusCode: 10,
823
+ message: "Tried use obsolete element as argument when executing javascript."}}};
824
+ }
825
+ return {success: true, value: {webdriverElementXPath: getXPathOfElement(element)}};
826
+ break;
827
+ //Intentional falling through because Javascript will parse things properly
828
+ case "STRING":
829
+ case "BOOLEAN":
830
+ case "NUMBER":
831
+ return {success: true, value: argument.value};
832
+ break;
833
+ }
834
+ return {success: false, value: {statusCode: 17,
835
+ message: "Bad argument to javascript."}}
836
+ }
837
+
838
+ /**
839
+ * Execute arbitrary javascript in the page.
840
+ * Returns by callback to returnFromJavascriptInPage.
841
+ * Yes, this is *horribly* hacky.
842
+ * We can't share objects between content script and page, so have to wrap up arguments as JSON
843
+ * @param script script to execute as a string
844
+ * @param passedArgs array of arguments to pass to the script
845
+ */
846
+ function execute(script, passedArgs) {
847
+ console.log("execing " + script + ", args: " + JSON.stringify(passedArgs));
848
+ var func = "function(){" + script + "}";
849
+ var args = [];
850
+ for (var i = 0; i < passedArgs.length; ++i) {
851
+ console.log("Parsing: " + passedArgs[i]);
852
+ var value = parseWrappedArguments(passedArgs[i]);
853
+ if (value.success) {
854
+ args.push(value.value);
855
+ } else {
856
+ ChromeDriverContentScript.port.postMessage(value.value);
857
+ return;
858
+ }
859
+ }
860
+ //Add a script tag to the page, containing the script we wish to execute
861
+ var scriptTag = ChromeDriverContentScript.currentDocument.createElement('script');
862
+ var argsString = JSON.stringify(args).replace(/"/g, "\\\"");
863
+ scriptTag.innerText = 'var e = document.createEvent("MutationEvent");' +
864
+ //Dump our arguments in an array
865
+ 'var args = JSON.parse("' + argsString + '");' +
866
+ 'var element = null;' +
867
+ 'for (var i = 0; i < args.length; ++i) {' +
868
+ 'if (args[i] && typeof(args[i]) == "object" && args[i].webdriverElementXPath) {' +
869
+ //If this is an element (because it has the proper xpath), turn it into an actual element object
870
+ 'args[i] = document.evaluate(args[i].webdriverElementXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;' +
871
+ '}' +
872
+ '}' +
873
+ 'try {' +
874
+ 'var val = eval(' + func + ').apply(window, args);' +
875
+ 'if (typeof(val) == "string") { val = JSON.stringify(val); }' +
876
+ //TODO(danielwh): Deal with the undefined != null case better
877
+ 'else if (val === undefined) { val = null; }' +
878
+ 'else if (typeof(val) == "object" && val && val.nodeType == 1) {' +
879
+ //If we're returning an element, turn it into a special xpath-containing object
880
+ 'var path = "";' +
881
+ 'for (; val && val.nodeType == 1; val = val.parentNode) {' +
882
+ 'var index = 1;' +
883
+ 'for (var sibling = val.previousSibling; sibling; sibling = sibling.previousSibling) {' +
884
+ 'if (sibling.nodeType == 1 && sibling.tagName && sibling.tagName == val.tagName) {' +
885
+ 'index++;' +
886
+ '}' +
887
+ '}' +
888
+ 'path = "/" + val.tagName + "[" + index + "]" + path;' +
889
+ '}' +
890
+ 'val = JSON.stringify({webdriverElementXPath: path});' +
891
+ '} else {' +
892
+ 'val = JSON.stringify(val);' +
893
+ '}' +
894
+ //Fire mutation event with newValue set to the JSON of our return value
895
+ 'e.initMutationEvent("DOMAttrModified", true, false, null, null, "{value: " + val + "}", null, 0);' +
896
+ '} catch (exn) {' +
897
+ //Fire mutation event with prevValue set to EXCEPTION to indicate an error in the script
898
+ 'e.initMutationEvent("DOMAttrModified", true, false, null, "EXCEPTION", null, null, 0);' +
899
+ '}' +
900
+ 'document.getElementsByTagName("script")[document.getElementsByTagName("script").length - 1].dispatchEvent(e);' +
901
+ 'document.getElementsByTagName("html")[0].removeChild(document.getElementsByTagName("script")[document.getElementsByTagName("script").length - 1]);';
902
+ scriptTag.addEventListener('DOMAttrModified', returnFromJavascriptInPage, false);
903
+ console.log("Injecting script element");
904
+ ChromeDriverContentScript.currentDocument.getElementsByTagName("html")[0].appendChild(scriptTag);
905
+ }
906
+
907
+ function parseReturnValueFromScript(result) {
908
+ console.log("Parsing: " + JSON.stringify(result));
909
+ var value = {"type":"NULL"};
910
+ if (result !== undefined && result != null && typeof(result) == "object") {
911
+ if (result.webdriverElementXPath) {
912
+ //If we're returning an element, turn it into an actual element object
913
+ value = {value: addElementToInternalArray(getElementsByXPath(result.webdriverElementXPath)[0]).toString(), type:"ELEMENT"};
914
+ } else if (result.length !== undefined) {
915
+ value = [];
916
+ for (var i = 0; i < result.length; ++i) {
917
+ value.push(parseReturnValueFromScript(result[i]));
918
+ }
919
+ }
920
+ } else if (result !== undefined && result != null) {
921
+ switch (typeof(result)) {
922
+ //Intentional falling through because we treat all "VALUE"s the same
923
+ case "string":
924
+ case "number":
925
+ case "boolean":
926
+ value = {value: result, type: "VALUE"};
927
+ break;
928
+ }
929
+ }
930
+ return value;
931
+ }
932
+
933
+ /**
934
+ * Callback from execute
935
+ */
936
+ function returnFromJavascriptInPage(e) {
937
+ if (e.prevValue == "EXCEPTION") {
938
+ ChromeDriverContentScript.port.postMessage({sequenceNumber: ChromeDriverContentScript.currentSequenceNumber,
939
+ response: {response: "execute", value: {statusCode: 17,
940
+ message: "Tried to execute bad javascript."}}});
941
+ return;
942
+ }
943
+ console.log("Got result");
944
+ console.log("Result was: " + e.newValue.value);
945
+ var result = JSON.parse(e.newValue).value;
946
+ var value = parseReturnValueFromScript(result);
947
+ console.log("reutrn value: " + JSON.stringify(value));
948
+ ChromeDriverContentScript.port.postMessage({sequenceNumber: ChromeDriverContentScript.currentSequenceNumber, response: {response: "execute", value: {statusCode: 0, value: value}}});
949
+ }
950
+
951
+ function getFrameNameFromIndex(index) {
952
+ var scriptTag = ChromeDriverContentScript.currentDocument.createElement('script');
953
+ scriptTag.innerText = 'var e = document.createEvent("MutationEvent");' +
954
+ 'try {' +
955
+ 'var val = window.frames[' + index + '].name;' +
956
+ 'e.initMutationEvent("DOMAttrModified", true, false, null, null, val, null, 0);' +
957
+ '} catch (exn) {' +
958
+ //Fire mutation event with prevValue set to EXCEPTION to indicate an error in the script
959
+ 'e.initMutationEvent("DOMAttrModified", true, false, null, "EXCEPTION", null, null, 0);' +
960
+ '}' +
961
+ 'document.getElementsByTagName("script")[document.getElementsByTagName("script").length - 1].dispatchEvent(e);' +
962
+ 'document.getElementsByTagName("html")[0].removeChild(document.getElementsByTagName("script")[document.getElementsByTagName("script").length - 1]);';
963
+ scriptTag.addEventListener('DOMAttrModified', returnFromGetFrameNameFromIndexJavascriptInPage, false);
964
+ try {
965
+ if (ChromeDriverContentScript.currentDocument.getElementsByTagName("frameset").length > 0) {
966
+ ChromeDriverContentScript.currentDocument.getElementsByTagName("frameset")[0].appendChild(scriptTag);
967
+ } else {
968
+ ChromeDriverContentScript.currentDocument.getElementsByTagName("html")[0].appendChild(scriptTag);
969
+ }
970
+ } catch (e) {
971
+ ChromeDriverContentScript.port.postMessage({sequenceNumber: ChromeDriverContentScript.currentSequenceNumber,
972
+ response: {response: "getFrameNameFromIndex", value: {statusCode: 8,
973
+ message: "Page seemed not to be a frameset. Couldn't find frame"}}});
974
+ }
975
+ }
976
+
977
+ function returnFromGetFrameNameFromIndexJavascriptInPage(e) {
978
+ if (e.prevValue == "EXCEPTION") {
979
+ ChromeDriverContentScript.port.postMessage({sequenceNumber: ChromeDriverContentScript.currentSequenceNumber,
980
+ response: {response: "getFrameNameFromIndex", value: {statusCode: 8,
981
+ message: "No such frame"}}});
982
+ } else {
983
+ ChromeDriverContentScript.port.postMessage({sequenceNumber: ChromeDriverContentScript.currentSequenceNumber,
984
+ response: {response: "getFrameNameFromIndex", value: {statusCode: "no-op",
985
+ name: e.newValue}}});
986
+ }
987
+ }
988
+
989
+ /**
990
+ * Inject an embed tag so the native code can grab the HWND
991
+ */
992
+ function injectEmbed() {
993
+ ChromeDriverContentScript.injectedEmbedElement = ChromeDriverContentScript.currentDocument.createElement('embed');
994
+ ChromeDriverContentScript.injectedEmbedElement.setAttribute("type", "application/x-chromedriver-reporter");
995
+ ChromeDriverContentScript.injectedEmbedElement.setAttribute("style", "width: 0; height: 0;");
996
+ ChromeDriverContentScript.currentDocument.getElementsByTagName("html")[0].appendChild(ChromeDriverContentScript.injectedEmbedElement);
997
+ //Give the embed time to render. Hope that the followup doesn't count embeds or anything
998
+ setTimeout(removeInjectedEmbed, 100);
999
+ }
1000
+
1001
+ function removeInjectedEmbed() {
1002
+ if (ChromeDriverContentScript.injectedEmbedElement != null) {
1003
+ ChromeDriverContentScript.currentDocument.getElementsByTagName("html")[0].removeChild(ChromeDriverContentScript.injectedEmbedElement);
1004
+ ChromeDriverContentScript.injectedEmbedElement = null;
1005
+ }
1006
+ }
1007
+
1008
+ /**
1009
+ * Guesses whether we have an HTML document or a text file
1010
+ */
1011
+ function guessPageType() {
1012
+ var source = ChromeDriverContentScript.currentDocument.getElementsByTagName("html")[0].outerHTML;
1013
+ var textSourceBegins = '<html><body><pre style="word-wrap: break-word; white-space: pre-wrap;">';
1014
+ var textSourceEnds = '</pre></body></html>';
1015
+
1016
+ if (source.substr(0, textSourceBegins.length) == textSourceBegins &&
1017
+ source.substr(0 - textSourceEnds.length) == textSourceEnds) {
1018
+ return "text";
1019
+ } else {
1020
+ return "html";
1021
+ }
1022
+ }
1023
+
1024
+ /**
1025
+ * Gets an array of elements which match the passed xpath string
1026
+ */
1027
+ function getElementsByXPath(xpath) {
1028
+ var elements = [];
1029
+ var foundElements = ChromeDriverContentScript.currentDocument.evaluate(xpath, ChromeDriverContentScript.currentDocument, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
1030
+ var this_element = foundElements.iterateNext();
1031
+ while (this_element) {
1032
+ elements.push(this_element);
1033
+ this_element = foundElements.iterateNext();
1034
+ }
1035
+ return elements;
1036
+ }
1037
+
1038
+ /**
1039
+ * Gets canonical xpath of the passed element, e.g. /HTML/BODY/P[1]
1040
+ */
1041
+ function getXPathOfElement(element) {
1042
+ var path = "";
1043
+ for (; element && element.nodeType == 1; element = element.parentNode) {
1044
+ index = getElementIndexForXPath(element);
1045
+ path = "/" + element.tagName + "[" + index + "]" + path;
1046
+ }
1047
+ return path;
1048
+ }
1049
+
1050
+ /**
1051
+ * Returns n for the nth element of type element.tagName in the page
1052
+ */
1053
+ function getElementIndexForXPath(element) {
1054
+ var index = 1;
1055
+ for (var sibling = element.previousSibling; sibling ; sibling = sibling.previousSibling) {
1056
+ if (sibling.nodeType == 1 && sibling.tagName == element.tagName) {
1057
+ index++;
1058
+ }
1059
+ }
1060
+ return index;
1061
+ }
1062
+
1063
+ /**
1064
+ * Gets an array of link elements whose displayed text is linkText
1065
+ */
1066
+ function getElementsByLinkText(parent, linkText) {
1067
+ var links = parent.getElementsByTagName("a");
1068
+ var matchingLinks = [];
1069
+ for (var i = 0; i < links.length; i++) {
1070
+ if (Utils.getText(links[i]) == linkText) {
1071
+ matchingLinks.push(links[i]);
1072
+ }
1073
+ }
1074
+ return matchingLinks;
1075
+ }
1076
+
1077
+ /**
1078
+ * Gets an array of link elements whose displayed text includes linkText
1079
+ */
1080
+ function getElementsByPartialLinkText(parent, partialLinkText) {
1081
+ var links = parent.getElementsByTagName("a");
1082
+ var matchingLinks = [];
1083
+ for (var i = 0; i < links.length; i++) {
1084
+ if (Utils.getText(links[i]).indexOf(partialLinkText) > -1) {
1085
+ matchingLinks.push(links[i]);
1086
+ }
1087
+ }
1088
+ return matchingLinks;
1089
+ }
1090
+
1091
+ /**
1092
+ * Throws exception if element is not displayed
1093
+ * @return nothing if element is displayed
1094
+ * @throws ElementNotVisibleException object ready to be sent if element is not displayed
1095
+ */
1096
+ function checkElementIsDisplayed(element) {
1097
+ if (element.tagName.toLowerCase() == "title") {
1098
+ //Always visible
1099
+ return;
1100
+ }
1101
+ if (!Utils.isDisplayed(element)) {
1102
+ throw {statusCode: 11, value: {message: "Element was not visible"}};
1103
+ }
1104
+ }
1105
+
1106
+ /**
1107
+ * Throws exception if element is disabled
1108
+ * @return nothing if element is enabled
1109
+ * @throws UnsupoprtedOperationException object ready to be sent if element is disabled
1110
+ */
1111
+ function checkElementNotDisabled(element) {
1112
+ if (element.disabled) {
1113
+ throw {statusCode: 12, value: {message: "Cannot operate on disabled element"}};
1114
+ }
1115
+ }
1116
+
1117
+ /**
1118
+ * Checks whether element is selected/checked
1119
+ * @return true if element is {selectable and selected, checkable and checked},
1120
+ * false otherwise
1121
+ */
1122
+ function findWhetherElementIsSelected(element) {
1123
+ var selected = false;
1124
+ try {
1125
+ var tagName = element.tagName.toLowerCase();
1126
+ if (tagName == "option") {
1127
+ selected = element.selected;
1128
+ } else if (tagName == "input") {
1129
+ var type = element.getAttribute("type").toLowerCase();
1130
+ if (type == "checkbox" || type == "radio") {
1131
+ selected = element.checked;
1132
+ }
1133
+ } else {
1134
+ selected = element.getAttribute("selected");
1135
+ }
1136
+ } catch (e) {
1137
+ selected = false;
1138
+ }
1139
+ return selected;
1140
+ }
1141
+
1142
+ /**
1143
+ * Gets the coordinates of the top-left corner of the element on the browser window
1144
+ * (NOT the displayed portion, the WHOLE page)
1145
+ * Heavily influenced by com.google.gwt.dom.client.DOMImplSafari,
1146
+ * which is released under Apache 2
1147
+ * It's not actually correct...
1148
+ * @return array: [x, y]
1149
+ */
1150
+ function getElementCoords(elem) {
1151
+ var left = 0;
1152
+ var top = 0;
1153
+ if (frameElement) {
1154
+ left += frameElement.offsetLeft;
1155
+ top += frameElement.offsetTop;
1156
+ }
1157
+ try {
1158
+ if (elem.getBoundingClientRect) {
1159
+ var rect = elem.getBoundingClientRect();
1160
+ left += rect.left + ChromeDriverContentScript.currentDocument.body.scrollLeft;
1161
+ top += rect.top + ChromeDriverContentScript.currentDocument.body.scrollTop;
1162
+ return [left, top];
1163
+ }
1164
+ } catch(e) {
1165
+ var left = 0;
1166
+ var top = 0;
1167
+ if (frameElement) {
1168
+ left += frameElement.offsetLeft;
1169
+ top += frameElement.offsetTop;
1170
+ }
1171
+ }
1172
+
1173
+ //The below is ugly and NOT ACTUALLY RIGHT
1174
+
1175
+ // Unattached elements and elements (or their ancestors) with style
1176
+ // 'display: none' have no offset{Top,Left}.
1177
+ if (elem.offsetTop == null || elem.offsetLeft == null) {
1178
+ return [left, top];
1179
+ }
1180
+
1181
+ var doc = elem.ownerDocument;
1182
+ var curr = elem.parentNode;
1183
+ if (curr) {
1184
+ // This intentionally excludes body which has a null offsetParent.
1185
+ while (curr.offsetParent) {
1186
+ top -= curr.scrollTop;
1187
+ left -= curr.scrollLeft;
1188
+
1189
+ // In RTL mode, offsetLeft is relative to the left edge of the
1190
+ // scrollable area when scrolled all the way to the right, so we need
1191
+ // to add back that difference.
1192
+ if (getStyle(curr, 'direction') == 'rtl') {
1193
+ left += (curr.scrollWidth - curr.clientWidth);
1194
+ }
1195
+
1196
+ curr = curr.parentNode;
1197
+ }
1198
+ }
1199
+
1200
+ while (elem) {
1201
+ top += elem.offsetTop;
1202
+ left += elem.offsetLeft;
1203
+
1204
+ if (getStyle(elem, 'position') == 'fixed') {
1205
+ top += doc.body.scrollTop;
1206
+ left += doc.body.scrollLeft;
1207
+ return [left, top];
1208
+ }
1209
+
1210
+
1211
+ // Webkit bug: a top-level absolutely positioned element includes the
1212
+ // body's offset position already.
1213
+ var parent = elem.offsetParent;
1214
+ if (parent && (parent.tagName == 'BODY') &&
1215
+ (getStyle(elem, 'position') == 'absolute')) {
1216
+ break;
1217
+ }
1218
+
1219
+ elem = parent;
1220
+ }
1221
+ return [left, top];
1222
+ }
1223
+
1224
+ /**
1225
+ * Gets the maximum offsetHeight and offsetWidth of an element or those of its sub-elements
1226
+ * In place because element.offset{Height,Width} returns incorrectly in WebKit (see bug 28810)
1227
+ * @param element element to get max dimensions of
1228
+ * @param width optional greatest width seen so far (omit when calling)
1229
+ * @param height optional greatest height seen so far (omit when calling)
1230
+ * @return an object of form: {type: "DIMENSION", width: maxOffsetWidth, height: maxOffsetHeight}
1231
+ */
1232
+ function getOffsetSizeFromSubElements(element, maxWidth, maxHeight) {
1233
+ if (element.getBoundingClientRect) {
1234
+ var rect = element.getBoundingClientRect();
1235
+ return {type: "DIMENSION", width: rect.width, height: rect.height};
1236
+ }
1237
+ //The below isn't correct, but is a hack with a decent probability of being correct, if the element has no BoundingClientRect
1238
+ //TODO(danielwh): Fix this up a bit
1239
+ maxWidth = (maxWidth === undefined || element.offsetWidth > maxWidth) ? element.offsetWidth : maxWidth;
1240
+ maxHeight = (maxHeight === undefined || element.offsetHeight > maxHeight) ? element.offsetHeight : maxHeight;
1241
+ for (var child in element.children) {
1242
+ var childSize = getOffsetSizeFromSubElements(element.children[child], maxWidth, maxHeight);
1243
+ maxWidth = (childSize.width > maxWidth) ? childSize.width : maxWidth;
1244
+ maxHeight = (childSize.height > maxHeight) ? childSize.height : maxHeight;
1245
+ }
1246
+ return {type: "DIMENSION", width: maxWidth, height: maxHeight};
1247
+ }
1248
+
1249
+ /**
1250
+ * Converts rgb(x, y, z) colours to #RRGGBB colours
1251
+ * @param rgb string of form either rgb(x, y, z) or rgba(x, y, z, a) with x, y, z, a numbers
1252
+ * @return string of form #RRGGBB where RR, GG, BB are two-digit lower-case hex values
1253
+ */
1254
+ function rgbToRRGGBB(rgb) {
1255
+ var r, g, b;
1256
+ var values = rgb.split(",");
1257
+ if (values.length == 3 && values[0].length > 4 && values[0].substr(0, 4) == "rgb(") {
1258
+ r = decimalToHex(values[0].substr(4));
1259
+ g = decimalToHex(values[1]);
1260
+ b = decimalToHex(values[2].substr(0, values[2].length - 1));
1261
+ if (r == null || g == null || b == null) {
1262
+ return null;
1263
+ }
1264
+ return "#" + r + g + b;
1265
+ } else if (rgb == "rgba(0, 0, 0, 0)") {
1266
+ return "transparent";
1267
+ } else {
1268
+ return rgb;
1269
+ }
1270
+ }
1271
+
1272
+ /**
1273
+ * Convert a number from decimal to a hex string of at least two digits
1274
+ * @return null if value was not an int, two digit string representation
1275
+ * (with leading zero if needed) of value in base 16 otherwise
1276
+ */
1277
+ function decimalToHex(value) {
1278
+ value = parseInt(value).toString(16);
1279
+ if (value == null) {
1280
+ return null;
1281
+ }
1282
+ if (value.length == 1) {
1283
+ value = '0' + '' + value;
1284
+ }
1285
+ return value;
1286
+ }