DOMUtils.java revision 116680a4aac90f2aa7413d9095a592090648e557
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 + viewCore.getViewportSizeOffsetWidthPix(); 256 int clickY = (int) viewCore.getRenderCoordinates().fromLocalCssToPix(bounds.exactCenterY()) 257 + viewCore.getViewportSizeOffsetHeightPix(); 258 return new int[] { clickX, clickY }; 259 } 260} 261