rainux-selenium-webdriver 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
+ }