1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.content.browser.test.util;
6
7import android.graphics.Rect;
8import android.test.ActivityInstrumentationTestCase2;
9import android.util.JsonReader;
10
11import java.io.IOException;
12import java.io.StringReader;
13import java.util.concurrent.TimeoutException;
14
15import junit.framework.Assert;
16
17import org.chromium.content.browser.ContentView;
18
19/**
20 * Collection of DOM-based utilities.
21 */
22public class DOMUtils {
23
24    /**
25     * Returns the rect boundaries for a node by its id.
26     */
27    public static Rect getNodeBounds(
28            final ContentView view, TestCallbackHelperContainer viewClient, String nodeId)
29            throws InterruptedException, TimeoutException {
30        StringBuilder sb = new StringBuilder();
31        sb.append("(function() {");
32        sb.append("  var node = document.getElementById('" + nodeId + "');");
33        sb.append("  if (!node) return null;");
34        sb.append("  var width = node.offsetWidth;");
35        sb.append("  var height = node.offsetHeight;");
36        sb.append("  var x = -window.scrollX;");
37        sb.append("  var y = -window.scrollY;");
38        sb.append("  do {");
39        sb.append("    x += node.offsetLeft;");
40        sb.append("    y += node.offsetTop;");
41        sb.append("  } while (node = node.offsetParent);");
42        sb.append("  return [ x, y, width, height ];");
43        sb.append("})();");
44
45        String jsonText = JavaScriptUtils.executeJavaScriptAndWaitForResult(
46                view, viewClient, sb.toString());
47
48        Assert.assertFalse("Failed to retrieve bounds for " + nodeId,
49                jsonText.trim().equalsIgnoreCase("null"));
50
51        JsonReader jsonReader = new JsonReader(new StringReader(jsonText));
52        int[] bounds = new int[4];
53        try {
54            jsonReader.beginArray();
55            int i = 0;
56            while (jsonReader.hasNext()) {
57                bounds[i++] = jsonReader.nextInt();
58            }
59            jsonReader.endArray();
60            Assert.assertEquals("Invalid bounds returned.", 4, i);
61
62            jsonReader.close();
63        } catch (IOException exception) {
64            Assert.fail("Failed to evaluate JavaScript: " + jsonText + "\n" + exception);
65        }
66
67        return new Rect(bounds[0], bounds[1], bounds[0] + bounds[2], bounds[1] + bounds[3]);
68    }
69
70    /**
71     * Focus a DOM node by its id.
72     */
73    public static void focusNode(ActivityInstrumentationTestCase2 activityTestCase,
74            final ContentView view, TestCallbackHelperContainer viewClient, String nodeId)
75            throws InterruptedException, TimeoutException {
76        StringBuilder sb = new StringBuilder();
77        sb.append("(function() {");
78        sb.append("  var node = document.getElementById('" + nodeId + "');");
79        sb.append("  if (node) node.focus();");
80        sb.append("})();");
81
82        JavaScriptUtils.executeJavaScriptAndWaitForResult(view, viewClient, sb.toString());
83    }
84
85    /**
86     * Click a DOM node by its id.
87     */
88    public static void clickNode(ActivityInstrumentationTestCase2 activityTestCase,
89            final ContentView view, TestCallbackHelperContainer viewClient, String nodeId)
90            throws InterruptedException, TimeoutException {
91        int[] clickTarget = getClickTargetForNode(view, viewClient, nodeId);
92        TouchCommon touchCommon = new TouchCommon(activityTestCase);
93        touchCommon.singleClickView(view, clickTarget[0], clickTarget[1]);
94    }
95
96    /**
97     * Long-press a DOM node by its id.
98     */
99    public static void longPressNode(ActivityInstrumentationTestCase2 activityTestCase,
100            final ContentView view, TestCallbackHelperContainer viewClient, String nodeId)
101            throws InterruptedException, TimeoutException {
102        int[] clickTarget = getClickTargetForNode(view, viewClient, nodeId);
103        TouchCommon touchCommon = new TouchCommon(activityTestCase);
104        touchCommon.longPressView(view, clickTarget[0], clickTarget[1]);
105    }
106
107    /**
108     * Scrolls the view to ensure that the required DOM node is visible.
109     */
110    public static void scrollNodeIntoView(final ContentView view,
111            TestCallbackHelperContainer viewClient, String nodeId)
112            throws InterruptedException, TimeoutException {
113        JavaScriptUtils.executeJavaScriptAndWaitForResult(view, viewClient,
114                "document.getElementById('" + nodeId + "').scrollIntoView()");
115    }
116
117    /**
118     * Returns the contents of the node by its id.
119     */
120    public static String getNodeContents(final ContentView view,
121            TestCallbackHelperContainer viewClient, String nodeId)
122            throws InterruptedException, TimeoutException {
123        return getNodeField("textContent", view, viewClient, nodeId);
124    }
125
126    /**
127     * Returns the value of the node by its id.
128     */
129    public static String getNodeValue(final ContentView view,
130            TestCallbackHelperContainer viewClient, String nodeId)
131            throws InterruptedException, TimeoutException {
132        return getNodeField("value", view, viewClient, nodeId);
133    }
134
135    private static String getNodeField(String fieldName, final ContentView view,
136            TestCallbackHelperContainer viewClient, String nodeId)
137            throws InterruptedException, TimeoutException {
138        StringBuilder sb = new StringBuilder();
139        sb.append("(function() {");
140        sb.append("  var node = document.getElementById('" + nodeId + "');");
141        sb.append("  if (!node) return null;");
142        sb.append("  if (!node." + fieldName +") return null;");
143        sb.append("  return [ node." + fieldName + " ];");
144        sb.append("})();");
145
146        String jsonText = JavaScriptUtils.executeJavaScriptAndWaitForResult(
147                view, viewClient, sb.toString());
148        Assert.assertFalse("Failed to retrieve contents for " + nodeId,
149                jsonText.trim().equalsIgnoreCase("null"));
150
151        JsonReader jsonReader = new JsonReader(new StringReader(jsonText));
152        String value = null;
153        try {
154            jsonReader.beginArray();
155            if (jsonReader.hasNext()) value = jsonReader.nextString();
156            jsonReader.endArray();
157            Assert.assertNotNull("Invalid contents returned.", value);
158
159            jsonReader.close();
160        } catch (IOException exception) {
161            Assert.fail("Failed to evaluate JavaScript: " + jsonText + "\n" + exception);
162        }
163        return value;
164    }
165
166    /**
167     * Returns click targets for a given DOM node.
168     */
169    private static int[] getClickTargetForNode(final ContentView view,
170            TestCallbackHelperContainer viewClient, String nodeName)
171            throws InterruptedException, TimeoutException {
172        Rect bounds = getNodeBounds(view, viewClient, nodeName);
173        Assert.assertNotNull("Failed to get DOM element bounds of '" + nodeName + "'.", bounds);
174
175        int clickX = (int) view.getRenderCoordinates().fromLocalCssToPix(bounds.exactCenterX())
176                + (int) view.getContentViewCore().getViewportSizeOffsetWidthPix();
177        int clickY = (int) view.getRenderCoordinates().fromLocalCssToPix(bounds.exactCenterY())
178                + (int) view.getContentViewCore().getViewportSizeOffsetHeightPix();
179        return new int[] { clickX, clickY };
180    }
181}
182