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,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
+ }