DOMUtils.java revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
1// Copyright 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 junit.framework.Assert;
12
13import org.chromium.content.browser.ContentViewCore;
14
15import java.io.IOException;
16import java.io.StringReader;
17import java.util.concurrent.TimeoutException;
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(final ContentViewCore viewCore, String nodeId)
28            throws InterruptedException, TimeoutException {
29        StringBuilder sb = new StringBuilder();
30        sb.append("(function() {");
31        sb.append("  var node = document.getElementById('" + nodeId + "');");
32        sb.append("  if (!node) return null;");
33        sb.append("  var width = Math.round(node.offsetWidth);");
34        sb.append("  var height = Math.round(node.offsetHeight);");
35        sb.append("  var x = -window.scrollX;");
36        sb.append("  var y = -window.scrollY;");
37        sb.append("  do {");
38        sb.append("    x += node.offsetLeft;");
39        sb.append("    y += node.offsetTop;");
40        sb.append("  } while (node = node.offsetParent);");
41        sb.append("  return [ Math.round(x), Math.round(y), width, height ];");
42        sb.append("})();");
43
44        String jsonText = JavaScriptUtils.executeJavaScriptAndWaitForResult(
45                viewCore, sb.toString());
46
47        Assert.assertFalse("Failed to retrieve bounds for " + nodeId,
48                jsonText.trim().equalsIgnoreCase("null"));
49
50        JsonReader jsonReader = new JsonReader(new StringReader(jsonText));
51        int[] bounds = new int[4];
52        try {
53            jsonReader.beginArray();
54            int i = 0;
55            while (jsonReader.hasNext()) {
56                bounds[i++] = jsonReader.nextInt();
57            }
58            jsonReader.endArray();
59            Assert.assertEquals("Invalid bounds returned.", 4, i);
60
61            jsonReader.close();
62        } catch (IOException exception) {
63            Assert.fail("Failed to evaluate JavaScript: " + jsonText + "\n" + exception);
64        }
65
66        return new Rect(bounds[0], bounds[1], bounds[0] + bounds[2], bounds[1] + bounds[3]);
67    }
68
69    /**
70     * Focus a DOM node by its id.
71     */
72    public static void focusNode(final ContentViewCore viewCore, String nodeId)
73            throws InterruptedException, TimeoutException {
74        StringBuilder sb = new StringBuilder();
75        sb.append("(function() {");
76        sb.append("  var node = document.getElementById('" + nodeId + "');");
77        sb.append("  if (node) node.focus();");
78        sb.append("})();");
79
80        JavaScriptUtils.executeJavaScriptAndWaitForResult(viewCore, sb.toString());
81    }
82
83    /**
84     * Click a DOM node by its id.
85     */
86    public static void clickNode(ActivityInstrumentationTestCase2 activityTestCase,
87            final ContentViewCore viewCore, String nodeId)
88            throws InterruptedException, TimeoutException {
89        int[] clickTarget = getClickTargetForNode(viewCore, nodeId);
90        TouchCommon touchCommon = new TouchCommon(activityTestCase);
91        touchCommon.singleClickView(viewCore.getContainerView(), clickTarget[0], clickTarget[1]);
92    }
93
94    /**
95     * Long-press a DOM node by its id.
96     */
97    public static void longPressNode(ActivityInstrumentationTestCase2 activityTestCase,
98            final ContentViewCore viewCore, String nodeId)
99            throws InterruptedException, TimeoutException {
100        int[] clickTarget = getClickTargetForNode(viewCore, nodeId);
101        TouchCommon touchCommon = new TouchCommon(activityTestCase);
102        touchCommon.longPressView(viewCore.getContainerView(), clickTarget[0], clickTarget[1]);
103    }
104
105    /**
106     * Scrolls the view to ensure that the required DOM node is visible.
107     */
108    public static void scrollNodeIntoView(ContentViewCore viewCore, String nodeId)
109            throws InterruptedException, TimeoutException {
110        JavaScriptUtils.executeJavaScriptAndWaitForResult(viewCore,
111                "document.getElementById('" + nodeId + "').scrollIntoView()");
112    }
113
114    /**
115     * Returns the contents of the node by its id.
116     */
117    public static String getNodeContents(ContentViewCore viewCore, String nodeId)
118            throws InterruptedException, TimeoutException {
119        return getNodeField("textContent", viewCore, nodeId);
120    }
121
122    /**
123     * Returns the value of the node by its id.
124     */
125    public static String getNodeValue(final ContentViewCore viewCore, String nodeId)
126            throws InterruptedException, TimeoutException {
127        return getNodeField("value", viewCore, nodeId);
128    }
129
130    public static String getNodeField(String fieldName, final ContentViewCore viewCore,
131            String nodeId)
132            throws InterruptedException, TimeoutException {
133        StringBuilder sb = new StringBuilder();
134        sb.append("(function() {");
135        sb.append("  var node = document.getElementById('" + nodeId + "');");
136        sb.append("  if (!node) return null;");
137        sb.append("  if (!node." + fieldName + ") return null;");
138        sb.append("  return [ node." + fieldName + " ];");
139        sb.append("})();");
140
141        String jsonText = JavaScriptUtils.executeJavaScriptAndWaitForResult(
142                viewCore, sb.toString());
143        Assert.assertFalse("Failed to retrieve contents for " + nodeId,
144                jsonText.trim().equalsIgnoreCase("null"));
145
146        JsonReader jsonReader = new JsonReader(new StringReader(jsonText));
147        String value = null;
148        try {
149            jsonReader.beginArray();
150            if (jsonReader.hasNext()) value = jsonReader.nextString();
151            jsonReader.endArray();
152            Assert.assertNotNull("Invalid contents returned.", value);
153
154            jsonReader.close();
155        } catch (IOException exception) {
156            Assert.fail("Failed to evaluate JavaScript: " + jsonText + "\n" + exception);
157        }
158        return value;
159    }
160
161    /**
162     * Wait until a given node has non-zero bounds.
163     * @return Whether the node started having non-zero bounds.
164     */
165    public static boolean waitForNonZeroNodeBounds(final ContentViewCore viewCore,
166            final String nodeName)
167            throws InterruptedException {
168        return CriteriaHelper.pollForCriteria(new Criteria() {
169            @Override
170            public boolean isSatisfied() {
171                try {
172                    return !DOMUtils.getNodeBounds(viewCore, nodeName).isEmpty();
173                } catch (InterruptedException e) {
174                    // Intentionally do nothing
175                    return false;
176                } catch (TimeoutException e) {
177                    // Intentionally do nothing
178                    return false;
179                }
180            }
181        });
182    }
183
184    /**
185     * Returns click targets for a given DOM node.
186     */
187    private static int[] getClickTargetForNode(ContentViewCore viewCore, String nodeName)
188            throws InterruptedException, TimeoutException {
189        Rect bounds = getNodeBounds(viewCore, nodeName);
190        Assert.assertNotNull("Failed to get DOM element bounds of '" + nodeName + "'.", bounds);
191
192        int clickX = (int) viewCore.getRenderCoordinates().fromLocalCssToPix(bounds.exactCenterX())
193                + viewCore.getViewportSizeOffsetWidthPix();
194        int clickY = (int) viewCore.getRenderCoordinates().fromLocalCssToPix(bounds.exactCenterY())
195                + viewCore.getViewportSizeOffsetHeightPix();
196        return new int[] { clickX, clickY };
197    }
198}
199