rainux-selenium-webdriver 0.0.17

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