DOMUtils.java revision 010d83a9304c5a91596085d917d248abff47903a
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 private 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