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 5 package org.chromium.content_shell_apk; 6 7 import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout; 8 9 import android.content.ComponentName; 10 import android.content.Intent; 11 import android.net.Uri; 12 import android.test.ActivityInstrumentationTestCase2; 13 import android.text.TextUtils; 14 import android.view.ViewGroup; 15 16 import org.chromium.base.ThreadUtils; 17 import org.chromium.base.test.util.UrlUtils; 18 import org.chromium.content.browser.ContentView; 19 import org.chromium.content.browser.ContentViewCore; 20 import org.chromium.content.browser.test.util.CallbackHelper; 21 import org.chromium.content.browser.test.util.Criteria; 22 import org.chromium.content.browser.test.util.CriteriaHelper; 23 import org.chromium.content.browser.test.util.TestCallbackHelperContainer; 24 import org.chromium.content_public.browser.LoadUrlParams; 25 import org.chromium.content_shell.Shell; 26 27 import java.lang.annotation.ElementType; 28 import java.lang.annotation.Retention; 29 import java.lang.annotation.RetentionPolicy; 30 import java.lang.annotation.Target; 31 import java.lang.reflect.Method; 32 import java.util.concurrent.TimeUnit; 33 import java.util.concurrent.atomic.AtomicBoolean; 34 35 /** 36 * Base test class for all ContentShell based tests. 37 */ 38 public class ContentShellTestBase extends ActivityInstrumentationTestCase2<ContentShellActivity> { 39 40 /** The maximum time the waitForActiveShellToBeDoneLoading method will wait. */ 41 private static final long WAIT_FOR_ACTIVE_SHELL_LOADING_TIMEOUT = scaleTimeout(10000); 42 43 protected static final long WAIT_PAGE_LOADING_TIMEOUT_SECONDS = scaleTimeout(15); 44 45 public ContentShellTestBase() { 46 super(ContentShellActivity.class); 47 } 48 49 /** 50 * Starts the ContentShell activity and loads the given URL. 51 * The URL can be null, in which case will default to ContentShellActivity.DEFAULT_SHELL_URL. 52 */ 53 protected ContentShellActivity launchContentShellWithUrl(String url) { 54 return launchContentShellWithUrlAndCommandLineArgs(url, null); 55 } 56 57 /** 58 * Starts the ContentShell activity appending the provided command line arguments 59 * and loads the given URL. The URL can be null, in which case will default to 60 * ContentShellActivity.DEFAULT_SHELL_URL. 61 */ 62 protected ContentShellActivity launchContentShellWithUrlAndCommandLineArgs(String url, 63 String[] commandLineArgs) { 64 Intent intent = new Intent(Intent.ACTION_MAIN); 65 intent.addCategory(Intent.CATEGORY_LAUNCHER); 66 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 67 if (url != null) intent.setData(Uri.parse(url)); 68 intent.setComponent(new ComponentName(getInstrumentation().getTargetContext(), 69 ContentShellActivity.class)); 70 if (commandLineArgs != null) { 71 intent.putExtra(ContentShellActivity.COMMAND_LINE_ARGS_KEY, commandLineArgs); 72 } 73 setActivityIntent(intent); 74 return getActivity(); 75 } 76 77 // TODO(cjhopman): These functions are inconsistent with launchContentShell***. Should be 78 // startContentShell*** and should use the url exactly without the getTestFileUrl call. Possibly 79 // these two ways of starting the activity (launch* and start*) should be merged into one. 80 /** 81 * Starts the content shell activity with the provided test url. 82 * The url is synchronously loaded. 83 * @param url Test url to load. 84 */ 85 protected void startActivityWithTestUrl(String url) throws Throwable { 86 launchContentShellWithUrl(UrlUtils.getTestFileUrl(url)); 87 assertNotNull(getActivity()); 88 assertTrue(waitForActiveShellToBeDoneLoading()); 89 assertEquals(UrlUtils.getTestFileUrl(url), getContentViewCore().getWebContents().getUrl()); 90 } 91 92 /** 93 * Starts the content shell activity with the provided test url and optional command line 94 * arguments to append. 95 * The url is synchronously loaded. 96 * @param url Test url to load. 97 * @param commandLineArgs Optional command line args to append when launching the activity. 98 */ 99 protected void startActivityWithTestUrlAndCommandLineArgs( 100 String url, String[] commandLineArgs) throws Throwable { 101 launchContentShellWithUrlAndCommandLineArgs( 102 UrlUtils.getTestFileUrl(url), commandLineArgs); 103 assertNotNull(getActivity()); 104 assertTrue(waitForActiveShellToBeDoneLoading()); 105 } 106 107 /** 108 * Returns the current ContentViewCore or null if there is no ContentView. 109 */ 110 protected ContentViewCore getContentViewCore() { 111 return getActivity().getActiveShell().getContentViewCore(); 112 } 113 114 /** 115 * Waits for the Active shell to finish loading. This times out after 116 * WAIT_FOR_ACTIVE_SHELL_LOADING_TIMEOUT milliseconds and it shouldn't be used for long 117 * loading pages. Instead it should be used more for test initialization. The proper way 118 * to wait is to use a TestCallbackHelperContainer after the initial load is completed. 119 * @return Whether or not the Shell was actually finished loading. 120 * @throws InterruptedException 121 */ 122 protected boolean waitForActiveShellToBeDoneLoading() throws InterruptedException { 123 final ContentShellActivity activity = getActivity(); 124 125 // Wait for the Content Shell to be initialized. 126 return CriteriaHelper.pollForCriteria(new Criteria() { 127 @Override 128 public boolean isSatisfied() { 129 try { 130 final AtomicBoolean isLoaded = new AtomicBoolean(false); 131 runTestOnUiThread(new Runnable() { 132 @Override 133 public void run() { 134 Shell shell = activity.getActiveShell(); 135 if (shell != null) { 136 // There are two cases here that need to be accounted for. 137 // The first is that we've just created a Shell and it isn't 138 // loading because it has no URL set yet. The second is that 139 // we've set a URL and it actually is loading. 140 isLoaded.set(!shell.isLoading() 141 && !TextUtils.isEmpty(shell.getContentViewCore() 142 .getWebContents().getUrl())); 143 } else { 144 isLoaded.set(false); 145 } 146 } 147 }); 148 149 return isLoaded.get(); 150 } catch (Throwable e) { 151 return false; 152 } 153 } 154 }, WAIT_FOR_ACTIVE_SHELL_LOADING_TIMEOUT, CriteriaHelper.DEFAULT_POLLING_INTERVAL); 155 } 156 157 /** 158 * Loads a URL in the specified content view. 159 * 160 * @param viewCore The content view core to load the URL in. 161 * @param callbackHelperContainer The callback helper container used to monitor progress. 162 * @param params The URL params to use. 163 */ 164 protected void loadUrl( 165 final ContentViewCore viewCore, TestCallbackHelperContainer callbackHelperContainer, 166 final LoadUrlParams params) throws Throwable { 167 handleBlockingCallbackAction( 168 callbackHelperContainer.getOnPageFinishedHelper(), 169 new Runnable() { 170 @Override 171 public void run() { 172 viewCore.getWebContents().getNavigationController().loadUrl(params); 173 } 174 }); 175 } 176 177 /** 178 * Handles performing an action on the UI thread that will return when the specified callback 179 * is incremented. 180 * 181 * @param callbackHelper The callback helper that will be blocked on. 182 * @param action The action to be performed on the UI thread. 183 */ 184 protected void handleBlockingCallbackAction( 185 CallbackHelper callbackHelper, Runnable action) throws Throwable { 186 int currentCallCount = callbackHelper.getCallCount(); 187 runTestOnUiThread(action); 188 callbackHelper.waitForCallback( 189 currentCallCount, 1, WAIT_PAGE_LOADING_TIMEOUT_SECONDS, TimeUnit.SECONDS); 190 } 191 192 // TODO(aelias): This method needs to be removed once http://crbug.com/179511 is fixed. 193 // Meanwhile, we have to wait if the page has the <meta viewport> tag. 194 /** 195 * Waits till the ContentViewCore receives the expected page scale factor 196 * from the compositor and asserts that this happens. 197 */ 198 protected void assertWaitForPageScaleFactorMatch(final float expectedScale) 199 throws InterruptedException { 200 assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { 201 @Override 202 public boolean isSatisfied() { 203 return getContentViewCore().getScale() == expectedScale; 204 } 205 })); 206 } 207 208 /** 209 * Replaces the {@link ContentViewCore#mContainerView} with a newly created 210 * {@link ContentView}. 211 */ 212 @SuppressWarnings("javadoc") 213 protected void replaceContainerView() throws Throwable { 214 ThreadUtils.runOnUiThreadBlocking(new Runnable() { 215 @Override 216 public void run() { 217 ContentView cv = ContentView.newInstance(getActivity(), getContentViewCore()); 218 ((ViewGroup) getContentViewCore().getContainerView().getParent()).addView(cv); 219 getContentViewCore().setContainerView(cv); 220 getContentViewCore().setContainerViewInternals(cv); 221 cv.requestFocus(); 222 } 223 }); 224 } 225 226 @Override 227 protected void runTest() throws Throwable { 228 super.runTest(); 229 try { 230 Method method = getClass().getMethod(getName(), (Class[]) null); 231 if (method.isAnnotationPresent(RerunWithUpdatedContainerView.class)) { 232 replaceContainerView(); 233 super.runTest(); 234 } 235 } catch (Throwable e) { 236 throw new Throwable("@RerunWithUpdatedContainerView failed." 237 + " See ContentShellTestBase#runTest.", e); 238 } 239 } 240 241 /** 242 * Annotation for tests that should be executed a second time after replacing 243 * the ContentViewCore's container view (see {@link #runTest()}). 244 * 245 * <p>Please note that {@link #setUp()} is only invoked once before both runs, 246 * and that any state changes produced by the first run are visible to the second run. 247 */ 248 @Target(ElementType.METHOD) 249 @Retention(RetentionPolicy.RUNTIME) 250 public @interface RerunWithUpdatedContainerView { 251 } 252 } 253