summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteve Block <steveblock@google.com>2011-04-14 11:07:04 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2011-04-14 11:07:04 -0700
commit7aa74c3d7a016bb9bdaaa845f2c829c8b0484e04 (patch)
tree01fb80b01c9c94ceb2c7539d40fdc4b00bc84cfa
parent44329ad519f25b9c3cdaa8eb5ad73ceb0ddb494d (diff)
parent600780bb2e1f848279db67b978ba931e10a35821 (diff)
downloadframeworks_base-7aa74c3d7a016bb9bdaaa845f2c829c8b0484e04.zip
frameworks_base-7aa74c3d7a016bb9bdaaa845f2c829c8b0484e04.tar.gz
frameworks_base-7aa74c3d7a016bb9bdaaa845f2c829c8b0484e04.tar.bz2
Merge "First bits of WebDriver containing blocking function to load a page and fetching the page source."
-rw-r--r--core/java/android/webkit/webdriver/WebDriver.java216
-rw-r--r--core/java/android/webkit/webdriver/WebchromeClientWrapper.java193
2 files changed, 409 insertions, 0 deletions
diff --git a/core/java/android/webkit/webdriver/WebDriver.java b/core/java/android/webkit/webdriver/WebDriver.java
new file mode 100644
index 0000000..7a25390
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebDriver.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.view.View;
+import android.webkit.ConsoleMessage;
+import android.webkit.GeolocationPermissions;
+import android.webkit.JsPromptResult;
+import android.webkit.JsResult;
+import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient;
+import android.webkit.WebStorage;
+import android.webkit.WebView;
+
+/**
+ * Drives a web application by controlling the WebView. This class
+ * provides a DOM-like API allowing to get information about the page,
+ * navigate, and interact with the web application. This is particularly useful
+ * for testing a web application.
+ *
+ * <p/>{@link android.webkit.webdriver.WebDriver} should be created in the main
+ * thread, and invoked from another thread. Here is a sample usage:
+ *
+ * public class WebDriverStubActivity extends Activity {
+ * private WebDriver mDriver;
+ *
+ * public void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * WebView view = new WebView(this);
+ * mDriver = new WebDriver(view);
+ * setContentView(view);
+ * }
+ *
+ *
+ * public WebDriver getDriver() {
+ * return mDriver;
+ * }
+ *}
+ *
+ * public class WebDriverTest extends
+ * ActivityInstrumentationTestCase2<WebDriverStubActivity>{
+ * private WebDriver mDriver;
+ *
+ * public WebDriverTest() {
+ * super(WebDriverStubActivity.class);
+ * }
+ *
+ * protected void setUp() throws Exception {
+ * super.setUp();
+ * mDriver = getActivity().getDriver();
+ * }
+ *
+ * public void testGetIsBlocking() {
+ * mDriver.get("http://google.com");
+ * assertTrue(mDriver.getPageSource().startsWith("<html"));
+ * }
+ *}
+ *
+ * @hide
+ */
+public class WebDriver {
+ // Timeout for page load in milliseconds
+ private static final int LOADING_TIMEOUT = 30000;
+ // Timeout for executing JavaScript in the WebView in milliseconds
+ private static final int JS_EXECUTION_TIMEOUT = 10000;
+
+ // Commands posted to the handler
+ private static final int GET_URL = 1;
+ private static final int EXECUTE_SCRIPT = 2;
+
+ private static final long MAIN_THREAD = Thread.currentThread().getId();
+
+ // This is updated by a callabck from JavaScript when the result is ready
+ private String mJsResult;
+
+ // Used for synchronization
+ private final Object mSyncObject;
+
+ // Updated when the command is done executing in the main thread.
+ private volatile boolean mCommandDone;
+
+ private WebView mWebView;
+
+ // This Handler runs in the main UI thread
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == GET_URL) {
+ final String url = (String) msg.obj;
+ mWebView.loadUrl(url);
+ } else if (msg.what == EXECUTE_SCRIPT) {
+ executeScript((String) msg.obj);
+ }
+ }
+ };
+
+ public WebDriver(WebView webview) {
+ if (!mWebView.getSettings().getJavaScriptEnabled()) {
+ throw new RuntimeException("Javascript is disabled in the WebView. "
+ + "Enable it to use WebDriver");
+ }
+ shouldRunInMainThread(true);
+ mSyncObject = new Object();
+ this.mWebView = webview;
+ WebchromeClientWrapper chromeWrapper = new WebchromeClientWrapper(
+ webview.getWebChromeClient(), this);
+ mWebView.setWebChromeClient(chromeWrapper);
+ mWebView.addJavascriptInterface(new JavascriptResultReady(),
+ "webdriver");
+ }
+
+ /**
+ * Loads a URL in the WebView. This function is blocking and will return
+ * when the page has finished loading.
+ *
+ * @param url The URL to load.
+ */
+ public void get(String url) {
+ executeCommand(GET_URL, url, LOADING_TIMEOUT);
+ }
+
+ /**
+ * @return The source page of the currently loaded page in WebView.
+ */
+ public String getPageSource() {
+ executeCommand(EXECUTE_SCRIPT, "return (new XMLSerializer())"
+ + ".serializeToString(document.documentElement);",
+ JS_EXECUTION_TIMEOUT);
+ return mJsResult;
+ }
+
+ private void executeScript(String script) {
+ mWebView.loadUrl("javascript:" + script);
+ }
+
+ private void shouldRunInMainThread(boolean value) {
+ assert (value == (MAIN_THREAD == Thread.currentThread().getId()));
+ }
+
+ /**
+ * Interface called from JavaScript when the result is ready.
+ */
+ private class JavascriptResultReady {
+
+ /**
+ * A callback from JavaScript to Java that passes the result as a
+ * parameter. This method is available from the WebView's
+ * JavaScript DOM as window.webdriver.resultReady().
+ *
+ * @param result The result that should be sent to Java from Javascript.
+ */
+ public void resultReady(String result) {
+ synchronized (mSyncObject) {
+ mJsResult = result;
+ mCommandDone = true;
+ mSyncObject.notify();
+ }
+ }
+ }
+
+ /* package */ void notifyCommandDone() {
+ synchronized (mSyncObject) {
+ mCommandDone = true;
+ mSyncObject.notify();
+ }
+ }
+
+ /**
+ * Executes the given command by posting a message to mHandler. This thread
+ * will block until the command which runs in the main thread is done.
+ *
+ * @param command The command to run.
+ * @param arg The argument for that command.
+ * @param timeout A timeout in milliseconds.
+ */
+ private void executeCommand(int command, String arg, long timeout) {
+ shouldRunInMainThread(false);
+
+ synchronized (mSyncObject) {
+ mCommandDone = false;
+ Message msg = mHandler.obtainMessage(command);
+ msg.obj = arg;
+ mHandler.sendMessage(msg);
+
+ long end = System.currentTimeMillis() + timeout;
+ while (!mCommandDone) {
+ if (System.currentTimeMillis() >= end) {
+ throw new RuntimeException("Timeout executing command.");
+ }
+ try {
+ mSyncObject.wait(timeout);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/webkit/webdriver/WebchromeClientWrapper.java b/core/java/android/webkit/webdriver/WebchromeClientWrapper.java
new file mode 100644
index 0000000..ea33c5b
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebchromeClientWrapper.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Message;
+import android.view.View;
+import android.webkit.ConsoleMessage;
+import android.webkit.GeolocationPermissions;
+import android.webkit.JsPromptResult;
+import android.webkit.JsResult;
+import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient;
+import android.webkit.WebStorage;
+import android.webkit.WebView;
+
+/* package */ class WebchromeClientWrapper extends WebChromeClient {
+
+ private final WebChromeClient mDelegate;
+ private final WebDriver mDriver;
+
+ public WebchromeClientWrapper(WebChromeClient delegate, WebDriver driver) {
+ if (delegate == null) {
+ this.mDelegate = new WebChromeClient();
+ } else {
+ this.mDelegate = delegate;
+ }
+ this.mDriver = driver;
+ }
+
+ @Override
+ public void onProgressChanged(WebView view, int newProgress) {
+ if (newProgress == 100) {
+ mDriver.notifyCommandDone();
+ }
+ mDelegate.onProgressChanged(view, newProgress);
+ }
+
+ @Override
+ public void onReceivedTitle(WebView view, String title) {
+ mDelegate.onReceivedTitle(view, title);
+ }
+
+ @Override
+ public void onReceivedIcon(WebView view, Bitmap icon) {
+ mDelegate.onReceivedIcon(view, icon);
+ }
+
+ @Override
+ public void onReceivedTouchIconUrl(WebView view, String url,
+ boolean precomposed) {
+ mDelegate.onReceivedTouchIconUrl(view, url, precomposed);
+ }
+
+ @Override
+ public void onShowCustomView(View view,
+ CustomViewCallback callback) {
+ mDelegate.onShowCustomView(view, callback);
+ }
+
+ @Override
+ public void onHideCustomView() {
+ mDelegate.onHideCustomView();
+ }
+
+ @Override
+ public boolean onCreateWindow(WebView view, boolean dialog,
+ boolean userGesture, Message resultMsg) {
+ return mDelegate.onCreateWindow(view, dialog, userGesture, resultMsg);
+ }
+
+ @Override
+ public void onRequestFocus(WebView view) {
+ mDelegate.onRequestFocus(view);
+ }
+
+ @Override
+ public void onCloseWindow(WebView window) {
+ mDelegate.onCloseWindow(window);
+ }
+
+ @Override
+ public boolean onJsAlert(WebView view, String url, String message,
+ JsResult result) {
+ return mDelegate.onJsAlert(view, url, message, result);
+ }
+
+ @Override
+ public boolean onJsConfirm(WebView view, String url, String message,
+ JsResult result) {
+ return mDelegate.onJsConfirm(view, url, message, result);
+ }
+
+ @Override
+ public boolean onJsPrompt(WebView view, String url, String message,
+ String defaultValue, JsPromptResult result) {
+ return mDelegate.onJsPrompt(view, url, message, defaultValue, result);
+ }
+
+ @Override
+ public boolean onJsBeforeUnload(WebView view, String url, String message,
+ JsResult result) {
+ return mDelegate.onJsBeforeUnload(view, url, message, result);
+ }
+
+ @Override
+ public void onExceededDatabaseQuota(String url, String databaseIdentifier,
+ long currentQuota, long estimatedSize, long totalUsedQuota,
+ WebStorage.QuotaUpdater quotaUpdater) {
+ mDelegate.onExceededDatabaseQuota(url, databaseIdentifier, currentQuota,
+ estimatedSize, totalUsedQuota, quotaUpdater);
+ }
+
+ @Override
+ public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota,
+ WebStorage.QuotaUpdater quotaUpdater) {
+ mDelegate.onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota,
+ quotaUpdater);
+ }
+
+ @Override
+ public void onGeolocationPermissionsShowPrompt(String origin,
+ GeolocationPermissions.Callback callback) {
+ mDelegate.onGeolocationPermissionsShowPrompt(origin, callback);
+ }
+
+ @Override
+ public void onGeolocationPermissionsHidePrompt() {
+ mDelegate.onGeolocationPermissionsHidePrompt();
+ }
+
+ @Override
+ public boolean onJsTimeout() {
+ return mDelegate.onJsTimeout();
+ }
+
+ @Override
+ public void onConsoleMessage(String message, int lineNumber,
+ String sourceID) {
+ mDelegate.onConsoleMessage(message, lineNumber, sourceID);
+ }
+
+ @Override
+ public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
+ return mDelegate.onConsoleMessage(consoleMessage);
+ }
+
+ @Override
+ public Bitmap getDefaultVideoPoster() {
+ return mDelegate.getDefaultVideoPoster();
+ }
+
+ @Override
+ public View getVideoLoadingProgressView() {
+ return mDelegate.getVideoLoadingProgressView();
+ }
+
+ @Override
+ public void getVisitedHistory(ValueCallback<String[]> callback) {
+ mDelegate.getVisitedHistory(callback);
+ }
+
+ @Override
+ public void openFileChooser(ValueCallback<Uri> uploadFile,
+ String acceptType) {
+ mDelegate.openFileChooser(uploadFile, acceptType);
+ }
+
+ @Override
+ public void setInstallableWebApp() {
+ mDelegate.setInstallableWebApp();
+ }
+
+ @Override
+ public void setupAutoFill(Message msg) {
+ mDelegate.setupAutoFill(msg);
+ }
+}