1 package com.jbergin;
2
3 import java.io.IOException;
4 import java.net.MalformedURLException;
5 import java.net.URL;
6 import java.util.Collections;
7 import java.util.Date;
8 import java.util.Iterator;
9 import java.util.List;
10 import java.util.NoSuchElementException;
11 import java.util.regex.Pattern;
12
13 import org.apache.commons.httpclient.Cookie;
14 import org.apache.commons.httpclient.HttpState;
15 import org.apache.commons.logging.Log;
16 import org.apache.commons.logging.LogFactory;
17 import org.mozilla.javascript.Function;
18 import org.mozilla.javascript.Scriptable;
19 import org.mozilla.javascript.WrappedException;
20
21 import com.gargoylesoftware.htmlunit.AlertHandler;
22 import com.gargoylesoftware.htmlunit.ConfirmHandler;
23 import com.gargoylesoftware.htmlunit.Page;
24 import com.gargoylesoftware.htmlunit.RefreshHandler;
25 import com.gargoylesoftware.htmlunit.ScriptException;
26 import com.gargoylesoftware.htmlunit.ScriptResult;
27 import com.gargoylesoftware.htmlunit.WebClient;
28 import com.gargoylesoftware.htmlunit.WebWindow;
29 import com.gargoylesoftware.htmlunit.WebWindowEvent;
30 import com.gargoylesoftware.htmlunit.WebWindowListener;
31 import com.gargoylesoftware.htmlunit.html.ClickableElement;
32 import com.gargoylesoftware.htmlunit.html.DomNode;
33 import com.gargoylesoftware.htmlunit.html.DomText;
34 import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
35 import com.gargoylesoftware.htmlunit.html.HtmlDivision;
36 import com.gargoylesoftware.htmlunit.html.HtmlElement;
37 import com.gargoylesoftware.htmlunit.html.HtmlForm;
38 import com.gargoylesoftware.htmlunit.html.HtmlInput;
39 import com.gargoylesoftware.htmlunit.html.HtmlOption;
40 import com.gargoylesoftware.htmlunit.html.HtmlPage;
41 import com.gargoylesoftware.htmlunit.html.HtmlRadioButtonInput;
42 import com.gargoylesoftware.htmlunit.html.HtmlSelect;
43 import com.gargoylesoftware.htmlunit.html.HtmlTextArea;
44 import com.gargoylesoftware.htmlunit.javascript.host.Event;
45
46
47
48 class ElementFactory
49 {
50 final Log logger = LogFactory.getLog(this.getClass());
51 final WebClient webClient;
52 private boolean jsExecuting = false;
53
54 ElementFactory(WebClient client) {
55 this.webClient = client;
56 webClient.setPageCreator(new LocalPageCreator());
57 webClient.setRedirectEnabled(true);
58 webClient.setAlertHandler(alertHandler);
59 webClient.setConfirmHandler(confirmHandler);
60 webClient.setRefreshHandler(refreshHandler);
61 webClient.addWebWindowListener(windowListener);
62 webClient.setThrowExceptionOnFailingStatusCode(false);
63 }
64
65 private static HtmlElement focusElement;
66 private String htmlSource;
67
68 HtmlElement getFocusElement() {
69 return focusElement;
70 }
71 void setFocusElement(HtmlElement e) {
72 if (e != null) {
73 logger.debug("Set focus element to " + e.asText());
74 } else {
75 logger.debug("Set focus to null");
76 }
77 focusElement = e;
78 }
79 String getHtmlSource() {
80 return htmlSource;
81 }
82 void setHtmlSource(String s) {
83 this.htmlSource = s;
84 }
85
86 private WebClient getWebClient() {
87 return this.webClient;
88 }
89
90 public void setJavaScriptEnabled(boolean b) {
91 getWebClient().setJavaScriptEnabled(b);
92 }
93 public boolean isJavaScriptEnabled() {
94 return getWebClient().isJavaScriptEnabled();
95 }
96
97 HtmlPage getHtmlPage() {
98 return getFocusElement().getPage();
99 }
100 URL getUrl() {
101 return getHtmlPage().getWebResponse().getUrl();
102 }
103
104 public static boolean isUrlValid(String url) {
105 try {
106 new URL(url);
107 return true;
108 } catch (MalformedURLException e) {
109 return false;
110 }
111 }
112
113 public void clickFocusElement() throws IOException {
114 HtmlElement focusElement = getFocusElement();
115
116 HtmlPage page = null;
117
118 if (focusElement instanceof HtmlForm) {
119 HtmlForm form = (HtmlForm) focusElement;
120 page = (HtmlPage)form.submit();
121 setFocusElement(page.getDocumentElement());
122 } else if (focusElement instanceof ClickableElement) {
123 final ClickableElement clickable = (ClickableElement) focusElement;
124
125 page = (HtmlPage)clickable.click();
126
127 final HtmlPage oldPage = clickable.getPage();
128
129 if (oldPage != page) {
130 setFocusElement(page.getDocumentElement());
131 }
132 } else {
133 throw new IllegalArgumentException("Current element is not clickable.");
134 }
135 }
136
137
138 final private DomNodeMemory iMemory = new DomNodeMemory();
139
140 public void clear() {
141 iMemory.clear();
142 }
143 public void store(String key, Object value) {
144 iMemory.store(key, value);
145 }
146 public DomNode retrieve(String key) {
147 return retrieve(key, false);
148 }
149 public DomNode retrieve(String key, boolean throwOnNull) {
150 if ("$currentPage$".equals(key)) {
151 final DomNode node = getFocusElement();
152 if (node == null) {
153 throw new IllegalStateException("no focus element");
154 }
155 return node.getPage();
156 }
157 final DomNode node = iMemory.retrieve(key);
158 if (node != null || !throwOnNull) return node;
159 throw new NoSuchElementException();
160 }
161 public Object remove(Object key) {
162 if ("alerts".equals(key)) {
163 HtmlDivision alerts = getAlerts();
164 if (alerts != null) {
165 alerts.removeAllChildren();
166 }
167 return alerts;
168 }
169 return iMemory.remove(key);
170 }
171 int storageSize() {
172 return iMemory.size();
173 }
174 boolean memoryContains(String key) {
175 return iMemory.containsKey(key);
176 }
177
178 private HtmlElement loadElementFromUrlOrMemory(String sourceNameOrUrl) throws MalformedURLException, IOException {
179 if (!isUrlValid(sourceNameOrUrl))
180 return (HtmlElement) retrieve(sourceNameOrUrl, false);
181
182 return loadElementFromUrl(new URL(sourceNameOrUrl));
183 }
184 private HtmlElement loadElementFromUrl(URL url) throws IOException {
185 Page aPage = getWebClient().getPage(url);
186 if (aPage instanceof HtmlPage) {
187 return ((HtmlPage) aPage).getDocumentElement();
188 }
189 throw new IllegalStateException("Document isn't an HtmlPage: " + url);
190 }
191
192 public void loadElementRelative(String urlString) throws IOException {
193 URL url = getFocusElement().getPage().getFullyQualifiedUrl(urlString);
194 HtmlElement oldElement = getFocusElement();
195 setHtmlSource(urlString);
196 setFocusElement(null);
197 setFocusElement(loadElementFromUrl(url));
198 if (getFocusElement() == null) {
199 setFocusElement(oldElement);
200 throw new NoSuchElementException(url.toString());
201 }
202 }
203
204 public void loadElementSafely(String sourceNameOrUrl) throws NoSuchElementException, IOException, IOException {
205 HtmlElement oldElement = getFocusElement();
206 setHtmlSource(sourceNameOrUrl);
207 setFocusElement(loadElementFromUrlOrMemory(sourceNameOrUrl));
208 if (getFocusElement() == null) {
209 setFocusElement(oldElement);
210 throw new NoSuchElementException("Element is not in storage: ");
211 }
212 }
213
214 public void setInputFieldValue(String textValue) {
215 String tagName = getFocusElement().getNodeName().toLowerCase();
216 if ("input".equals(tagName))
217 this.setInputValue(textValue);
218 else if ("textarea".equals(tagName))
219 ((HtmlTextArea) getFocusElement()).setText(textValue);
220 else if ("select".equals(tagName))
221 ((HtmlSelect) getFocusElement()).setSelectedAttribute(textValue, true);
222 else
223 throw new UnsupportedOperationException(tagName + " is not currently a supported input type");
224 }
225
226
227 private void setInputValue(String textValue) {
228 String inputType = getFocusElement().getAttributeValue("type");
229 String message = inputType + " is not currently a supported input type";
230 if (inputType != null) {
231 inputType = inputType.toLowerCase();
232 if (inputType.equals("") || inputType.equals("text") || inputType.equals("password"))
233 ((HtmlInput) getFocusElement()).setValueAttribute(textValue);
234 else if (inputType.equals("checkbox")) {
235 if (textValue.toLowerCase().equals("checked"))
236 ((HtmlCheckBoxInput) getFocusElement()).setChecked(true);
237 else
238 ((HtmlCheckBoxInput) getFocusElement()).setChecked(false);
239 } else if (inputType.equals("radio")) {
240 if (textValue.toLowerCase().equals("checked"))
241 ((HtmlRadioButtonInput) getFocusElement()).setChecked(true);
242 } else
243 throw new UnsupportedOperationException(message);
244 } else
245 throw new UnsupportedOperationException(message);
246 }
247
248 /***
249 * @param deselectOthers
250 * @return number of options matched. if the select box is not a multi and
251 * more than one match is found, only the last option matched will be selected.
252 */
253 public int selectOptions(String textValue, boolean deselectOthers) {
254 HtmlSelect select = (HtmlSelect) getFocusElement();
255
256 List options = select.getAllOptions();
257
258 Pattern pattern = Pattern.compile(textValue);
259
260 final boolean prevJsExecuting = jsExecuting;
261
262 logger.debug("begin select command");
263
264 jsExecuting = true;
265
266 int found = 0;
267 for (Iterator itr = options.iterator(); itr.hasNext();) {
268 HtmlOption option = (HtmlOption) itr.next();
269 if (pattern.matcher(option.asText()).matches()) {
270 select.setSelectedAttribute(option, true);
271 found++;
272 } else if (deselectOthers) {
273 select.setSelectedAttribute(option, false);
274 }
275 }
276
277 jsExecuting = prevJsExecuting;
278 logger.debug("select command complete");
279 logger.debug("focus element: " + getFocusElement().asText());
280 return found;
281 }
282
283 public void expireAllCookies(HtmlPage page) {
284 URL url = page.getWebResponse().getUrl();
285 HttpState state = page.getWebClient().getWebConnection().getStateForUrl(url);
286
287 Date ancient = new Date(0);
288
289 Cookie[] cookies = state.getCookies();
290 for (int i = 0; i < cookies.length; i++) {
291 cookies[i].setExpiryDate(ancient);
292 }
293 state.purgeExpiredCookies();
294 }
295
296 public Object executeEventHandler(String actionToRun) {
297 Function functionToRun = getFocusElement().getEventHandler(actionToRun);
298
299 if (functionToRun == null) {
300 throw new IllegalArgumentException(actionToRun + " event handler is not defined");
301 }
302
303 boolean jsEnable = this.isJavaScriptEnabled();
304 try {
305 jsExecuting = true;
306 setJavaScriptEnabled(true);
307
308 Event event = new Event(getFocusElement(), getFocusElement().getScriptObject());
309 Object[] args = new Object[] { event };
310 ScriptResult result = getHtmlPage().executeJavaScriptFunctionIfPossible(functionToRun, (Scriptable)getFocusElement().getScriptObject(), args, getFocusElement());
311 return result.getJavaScriptResult();
312 } catch (ScriptException e) {
313 try {
314 throw e.getEnclosedException();
315 } catch(WrappedException we) {
316 Throwable t = we.getCause();
317 if (t instanceof RuntimeException) {
318 throw (RuntimeException) t;
319 }
320 throw new RuntimeException(t);
321 } catch (RuntimeException re) {
322 throw re;
323 } catch (Throwable ex) {
324 throw new RuntimeException(ex);
325 }
326 } finally {
327 setJavaScriptEnabled(jsEnable);
328 jsExecuting = false;
329 }
330 }
331
332 public Object evaluateExpression(String expression) {
333 return getHtmlPage().executeJavaScriptIfPossible(expression, "HtmlFixture expression", false, getFocusElement());
334 }
335
336 HtmlDivision getAlerts() {
337 return (HtmlDivision) retrieve("alerts", false);
338 }
339 HtmlDivision getAlerts(Page page) {
340 HtmlDivision alerts = getAlerts();
341 if (alerts == null) {
342 alerts = new HtmlDivision((HtmlPage) page, Collections.EMPTY_MAP);
343 store("alerts", alerts);
344 }
345 return alerts;
346 }
347
348 AlertHandler alertHandler = new AlertHandler() {
349 public void handleAlert(Page page, String message) {
350 getAlerts(page).appendChild(new DomText((HtmlPage) page, message));
351 }
352 };
353
354
355 final ConfirmHandler confirmHandler = new ConfirmHandler() {
356 public boolean handleConfirm(Page page, String message) {
357 return iMemory.getNextAnswer(message);
358 }
359 };
360
361 /***
362 * Always returns false, to keep the URL from being fetched infinitely recursively.
363 */
364 RefreshHandler refreshHandler = new RefreshHandler() {
365 public boolean shouldRefresh(Page page, URL url, int timeBeforeRefresh) {
366 return false;
367 }
368 };
369
370 WebWindowListener windowListener = new WebWindowListener()
371 {
372
373 private void reportEvent(WebWindowEvent event) {
374 StringBuffer buffer = new StringBuffer("WebWindowEvent:\n");
375 reportNVP("Type", getEventType(event), buffer);
376 reportNVP("Source", event.getSource().toString(), buffer);
377 reportNVP("WindowName", event.getWebWindow().getName(), buffer);
378 reportNVP("OldPage", getPageDescription((HtmlPage) event.getOldPage()), buffer);
379 reportNVP("NewPage", getPageDescription((HtmlPage) event.getNewPage()), buffer);
380
381 logger.debug(buffer.toString());
382 }
383 void reportNVP(String name, String value, StringBuffer buffer) {
384 buffer.append(name + ":\t" + value + "\n");
385 }
386
387 String getPageDescription(HtmlPage page) {
388 if (page == null)
389 return "Null page";
390
391 try {
392 return page.hashCode() + " " + page.getTitleText() + " (" + page.getFullyQualifiedUrl("") + ")";
393 } catch (MalformedURLException e) {
394 throw new RuntimeException(e);
395 }
396 }
397
398 String getEventType(WebWindowEvent event) {
399 switch (event.getEventType()) {
400 case WebWindowEvent.CHANGE: return "CHANGE";
401 case WebWindowEvent.OPEN: return "OPEN";
402 case WebWindowEvent.CLOSE: return "CLOSE";
403 default:
404 return Integer.toString(event.getEventType());
405 }
406 }
407
408 public void webWindowOpened( WebWindowEvent event )
409 {
410 reportEvent(event);
411 }
412
413 public void webWindowContentChanged( WebWindowEvent event )
414 {
415 reportEvent(event);
416
417 iMemory.purgeElementsInPage(event.getOldPage());
418
419 final WebWindow webWindow = event.getWebWindow();
420
421 final HtmlPage newPage = (HtmlPage) event.getNewPage();
422
423 String windowName = webWindow.getName();
424
425 if (windowName.length() > 0) {
426 store(windowName, newPage.getDocumentElement() );
427 }
428
429 final String titleText = newPage.getTitleText();
430 if (titleText.length() > 0) {
431 final HtmlElement documentElement = newPage.getDocumentElement();
432 store( titleText, documentElement );
433 }
434 logger.debug("Focus on new page with title '" + newPage.getTitleText() + "'");
435 setFocusElement(newPage.getDocumentElement());
436 }
437 public void webWindowClosed( WebWindowEvent event )
438 {
439 reportEvent(event);
440 iMemory.purgeElementsInPage(event.getOldPage());
441 }
442 };
443
444 public void addAnswer(String message, boolean answer) {
445 this.iMemory.addAnswer(message, answer);
446 }
447 public void setPreserve(boolean preserve) {
448 this.iMemory.setPreserve(preserve);
449 }
450 }