diff options
author | Steve Block <steveblock@google.com> | 2011-04-14 11:07:04 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2011-04-14 11:07:04 -0700 |
commit | 7aa74c3d7a016bb9bdaaa845f2c829c8b0484e04 (patch) | |
tree | 01fb80b01c9c94ceb2c7539d40fdc4b00bc84cfa | |
parent | 44329ad519f25b9c3cdaa8eb5ad73ceb0ddb494d (diff) | |
parent | 600780bb2e1f848279db67b978ba931e10a35821 (diff) | |
download | frameworks_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.java | 216 | ||||
-rw-r--r-- | core/java/android/webkit/webdriver/WebchromeClientWrapper.java | 193 |
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); + } +} |