DOMUtils.java revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
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 whether the video with given {@code nodeId} has ended.
26     */
27    public static boolean hasVideoEnded(final ContentViewCore viewCore, final String nodeId)
28            throws InterruptedException, TimeoutException {
29        return getNodeField("ended", viewCore, nodeId, Boolean.class);
30    }
31
32    /**
33     * Wait until the end of the video with given {@code nodeId}.
34     * @return Whether the video has ended.
35     */
36    public static boolean waitForEndOfVideo(final ContentViewCore viewCore, final String nodeId)
37            throws InterruptedException {
38        return CriteriaHelper.pollForCriteria(new Criteria() {
39            @Override
40            public boolean isSatisfied() {
41                try {
42                    return DOMUtils.hasVideoEnded(viewCore, nodeId);
43                } catch (InterruptedException e) {
44                    // Intentionally do nothing
45                    return false;
46                } catch (TimeoutException e) {
47                    // Intentionally do nothing
48                    return false;
49                }
50            }
51        });
52    }
53
54    /**
55     * Makes the document exit fullscreen.
56     */
57    public static void exitFullscreen(final ContentViewCore viewCore) {
58        StringBuilder sb = new StringBuilder();
59        sb.append("(function() {");
60        sb.append("  if (document.webkitExitFullscreen) document.webkitExitFullscreen();");
61        sb.append("})();");
62
63        JavaScriptUtils.executeJavaScript(viewCore, sb.toString());
64    }
65
66    /**
67     * Returns the rect boundaries for a node by its id.
68     */
69    public static Rect getNodeBounds(final ContentViewCore viewCore, String nodeId)
70            throws InterruptedException, TimeoutException {
71        StringBuilder sb = new StringBuilder();
72        sb.append("(function() {");
73        sb.append("  var node = document.getElementById('" + nodeId + "');");
74        sb.append("  if (!node) return null;");
75        sb.append("  var width = Math.round(node.offsetWidth);");
76        sb.append("  var height = Math.round(node.offsetHeight);");
77        sb.append("  var x = -window.scrollX;");
78        sb.append("  var y = -window.scrollY;");
79        sb.append("  do {");
80        sb.append("    x += node.offsetLeft;");
81        sb.append("    y += node.offsetTop;");
82        sb.append("  } while (node = node.offsetParent);");
83        sb.append("  return [ Math.round(x), Math.round(y), width, height ];");
84        sb.append("})();");
85
86        String jsonText = JavaScriptUtils.executeJavaScriptAndWaitForResult(
87                viewCore, sb.toString());
88
89        Assert.assertFalse("Failed to retrieve bounds for " + nodeId,
90                jsonText.trim().equalsIgnoreCase("null"));
91
92        JsonReader jsonReader = new JsonReader(new StringReader(jsonText));
93        int[] bounds = new int[4];
94        try {
95            jsonReader.beginArray();
96            int i = 0;
97            while (jsonReader.hasNext()) {
98                bounds[i++] = jsonReader.nextInt();
99            }
100            jsonReader.endArray();
101            Assert.assertEquals("Invalid bounds returned.", 4, i);
102
103            jsonReader.close();
104        } catch (IOException exception) {
105            Assert.fail("Failed to evaluate JavaScript: " + jsonText + "\n" + exception);
106        }
107
108        return new Rect(bounds[0], bounds[1], bounds[0] + bounds[2], bounds[1] + bounds[3]);
109    }
110
111    /**
112     * Focus a DOM node by its id.
113     */
114    public static void focusNode(final ContentViewCore viewCore, String nodeId)
115            throws InterruptedException, TimeoutException {
116        StringBuilder sb = new StringBuilder();
117        sb.append("(function() {");
118        sb.append("  var node = document.getElementById('" + nodeId + "');");
119        sb.append("  if (node) node.focus();");
120        sb.append("})();");
121
122        JavaScriptUtils.executeJavaScriptAndWaitForResult(viewCore, sb.toString());
123    }
124
125    /**
126     * Click a DOM node by its id.
127     */
128    public static void clickNode(ActivityInstrumentationTestCase2 activityTestCase,
129            final ContentViewCore viewCore, String nodeId)
130            throws InterruptedException, TimeoutException {
131        int[] clickTarget = getClickTargetForNode(viewCore, nodeId);
132        TouchCommon touchCommon = new TouchCommon(activityTestCase);
133        touchCommon.singleClickView(viewCore.getContainerView(), clickTarget[0], clickTarget[1]);
134    }
135
136    /**
137     * Long-press a DOM node by its id.
138     */
139    public static void longPressNode(ActivityInstrumentationTestCase2 activityTestCase,
140            final ContentViewCore viewCore, String nodeId)
141            throws InterruptedException, TimeoutException {
142        int[] clickTarget = getClickTargetForNode(viewCore, nodeId);
143        TouchCommon touchCommon = new TouchCommon(activityTestCase);
144        touchCommon.longPressView(viewCore.getContainerView(), clickTarget[0], clickTarget[1]);
145    }
146
147    /**
148     * Scrolls the view to ensure that the required DOM node is visible.
149     */
150    public static void scrollNodeIntoView(ContentViewCore viewCore, String nodeId)
151            throws InterruptedException, TimeoutException {
152        JavaScriptUtils.executeJavaScriptAndWaitForResult(viewCore,
153                "document.getElementById('" + nodeId + "').scrollIntoView()");
154    }
155
156    /**
157     * Returns the contents of the node by its id.
158     */
159    public static String getNodeContents(ContentViewCore viewCore, String nodeId)
160            throws InterruptedException, TimeoutException {
161        return getNodeField("textContent", viewCore, nodeId, String.class);
162    }
163
164    /**
165     * Returns the value of the node by its id.
166     */
167    public static String getNodeValue(final ContentViewCore viewCore, String nodeId)
168            throws InterruptedException, TimeoutException {
169        return getNodeField("value", viewCore, nodeId, String.class);
170    }
171
172    /**
173     * Returns the string value of a field of the node by its id.
174     */
175    public static String getNodeField(String fieldName, final ContentViewCore viewCore,
176            String nodeId)
177            throws InterruptedException, TimeoutException {
178        return getNodeField(fieldName, viewCore, nodeId, String.class);
179    }
180
181    private static <T> T getNodeField(String fieldName, final ContentViewCore viewCore,
182            String nodeId, Class<T> valueType)
183            throws InterruptedException, TimeoutException {
184        StringBuilder sb = new StringBuilder();
185        sb.append("(function() {");
186        sb.append("  var node = document.getElementById('" + nodeId + "');");
187        sb.append("  if (!node) return null;");
188        sb.append("  return [ node." + fieldName + " ];");
189        sb.append("})();");
190
191        String jsonText = JavaScriptUtils.executeJavaScriptAndWaitForResult(
192                viewCore, sb.toString());
193        Assert.assertFalse("Failed to retrieve contents for " + nodeId,
194                jsonText.trim().equalsIgnoreCase("null"));
195
196        JsonReader jsonReader = new JsonReader(new StringReader(jsonText));
197        T value = null;
198        try {
199            jsonReader.beginArray();
200            if (jsonReader.hasNext()) value = readValue(jsonReader, valueType);
201            jsonReader.endArray();
202            Assert.assertNotNull("Invalid contents returned.", value);
203
204            jsonReader.close();
205        } catch (IOException exception) {
206            Assert.fail("Failed to evaluate JavaScript: " + jsonText + "\n" + exception);
207        }
208        return value;
209    }
210
211    @SuppressWarnings("unchecked")
212    private static <T> T readValue(JsonReader jsonReader, Class<T> valueType)
213            throws IOException {
214        if (valueType.equals(String.class)) return ((T) jsonReader.nextString());
215        if (valueType.equals(Boolean.class)) return ((T) ((Boolean) jsonReader.nextBoolean()));
216        if (valueType.equals(Integer.class)) return ((T) ((Integer) jsonReader.nextInt()));
217        if (valueType.equals(Long.class)) return ((T) ((Long) jsonReader.nextLong()));
218        if (valueType.equals(Double.class)) return ((T) ((Double) jsonReader.nextDouble()));
219
220        throw new IllegalArgumentException("Cannot read values of type " + valueType);
221    }
222
223    /**
224     * Wait until a given node has non-zero bounds.
225     * @return Whether the node started having non-zero bounds.
226     */
227    public static boolean waitForNonZeroNodeBounds(final ContentViewCore viewCore,
228            final String nodeName)
229            throws InterruptedException {
230        return CriteriaHelper.pollForCriteria(new Criteria() {
231            @Override
232            public boolean isSatisfied() {
233                try {
234                    return !DOMUtils.getNodeBounds(viewCore, nodeName).isEmpty();
235                } catch (InterruptedException e) {
236                    // Intentionally do nothing
237                    return false;
238                } catch (TimeoutException e) {
239                    // Intentionally do nothing
240                    return false;
241                }
242            }
243        });
244    }
245
246    /**
247     * Returns click targets for a given DOM node.
248     */
249    private static int[] getClickTargetForNode(ContentViewCore viewCore, String nodeName)
250            throws InterruptedException, TimeoutException {
251        Rect bounds = getNodeBounds(viewCore, nodeName);
252        Assert.assertNotNull("Failed to get DOM element bounds of '" + nodeName + "'.", bounds);
253
254        int clickX = (int) viewCore.getRenderCoordinates().fromLocalCssToPix(bounds.exactCenterX());
255        int clickY = (int) viewCore.getRenderCoordinates().fromLocalCssToPix(bounds.exactCenterY())
256                + viewCore.getTopControlsLayoutHeightPix();
257        return new int[] { clickX, clickY };
258    }
259}
260