diff options
author | tedchoc@chromium.org <tedchoc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-19 23:54:22 +0000 |
---|---|---|
committer | tedchoc@chromium.org <tedchoc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-19 23:54:22 +0000 |
commit | 1a915766adf9c390e7d02be5257aee35c2082f01 (patch) | |
tree | 514373e8b989f49974a6c4a5087279d35d9ea197 /android_webview | |
parent | 3ad1722d24ea0fb3e81e159e0ed0952b098cd003 (diff) | |
download | chromium_src-1a915766adf9c390e7d02be5257aee35c2082f01.zip chromium_src-1a915766adf9c390e7d02be5257aee35c2082f01.tar.gz chromium_src-1a915766adf9c390e7d02be5257aee35c2082f01.tar.bz2 |
Add WebView.saveWebArchive support to android_webview.
BUG=146004
Review URL: https://chromiumcodereview.appspot.com/10946013
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@157658 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'android_webview')
4 files changed, 269 insertions, 5 deletions
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java index 0481380..ba8b4f1 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwContents.java +++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java @@ -4,15 +4,25 @@ package org.chromium.android_webview; -import android.view.ViewGroup; +import android.os.AsyncTask; import android.os.Message; +import android.text.TextUtils; +import android.util.Log; +import android.view.ViewGroup; +import android.webkit.ValueCallback; import org.chromium.base.CalledByNative; import org.chromium.base.JNINamespace; +import org.chromium.base.ThreadUtils; import org.chromium.content.browser.ContentViewCore; +import org.chromium.content.browser.NavigationHistory; import org.chromium.content.common.CleanupReference; import org.chromium.ui.gfx.NativeWindow; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + /** * Exposes the native AwContents class, and together these classes wrap the ContentViewCore * and Browser components that are required to implement Android WebView API. This is the @@ -23,6 +33,9 @@ import org.chromium.ui.gfx.NativeWindow; */ @JNINamespace("android_webview") public class AwContents { + private static final String TAG = AwContents.class.getSimpleName(); + + private static final String WEB_ARCHIVE_EXTENSION = ".mht"; private int mNativeAwContents; private ContentViewCore mContentViewCore; @@ -102,7 +115,37 @@ public class AwContents { //-------------------------------------------------------------------------------------------- public void documentHasImages(Message message) { - nativeDocumentHasImages(mNativeAwContents, message); + nativeDocumentHasImages(mNativeAwContents, message); + } + + public void saveWebArchive( + final String basename, boolean autoname, final ValueCallback<String> callback) { + if (!autoname) { + saveWebArchiveInternal(basename, callback); + return; + } + // If auto-generating the file name, handle the name generation on a background thread + // as it will require I/O access for checking whether previous files existed. + new AsyncTask<Void, Void, String>() { + @Override + protected String doInBackground(Void... params) { + return generateArchiveAutoNamePath(getOriginalUrl(), basename); + } + + @Override + protected void onPostExecute(String result) { + saveWebArchiveInternal(result, callback); + } + }.execute(); + } + + public String getOriginalUrl() { + NavigationHistory history = mContentViewCore.getNavigationHistory(); + int currentIndex = history.getCurrentEntryIndex(); + if (currentIndex >= 0 && currentIndex < history.getEntryCount()) { + return history.getEntryAtIndex(currentIndex).getOriginalUrl(); + } + return null; } //-------------------------------------------------------------------------------------------- @@ -115,11 +158,70 @@ public class AwContents { message.sendToTarget(); } + /** Callback for generateMHTML. */ + @CalledByNative + private static void generateMHTMLCallback( + String path, long size, ValueCallback<String> callback) { + if (callback == null) return; + callback.onReceiveValue(size < 0 ? null : path); + } + @CalledByNative private void onReceivedHttpAuthRequest(AwHttpAuthHandler handler, String host, String realm) { mContentsClient.onReceivedHttpAuthRequest(handler, host, realm); } + // ------------------------------------------------------------------------------------------- + // Helper methods + // ------------------------------------------------------------------------------------------- + + private void saveWebArchiveInternal(String path, final ValueCallback<String> callback) { + if (path == null) { + ThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + callback.onReceiveValue(null); + } + }); + } else { + nativeGenerateMHTML(mNativeAwContents, path, callback); + } + } + + /** + * Try to generate a pathname for saving an MHTML archive. This roughly follows WebView's + * autoname logic. + */ + private static String generateArchiveAutoNamePath(String originalUrl, String baseName) { + String name = null; + if (originalUrl != null && !originalUrl.isEmpty()) { + try { + String path = new URL(originalUrl).getPath(); + int lastSlash = path.lastIndexOf('/'); + if (lastSlash > 0) { + name = path.substring(lastSlash + 1); + } else { + name = path; + } + } catch (MalformedURLException e) { + // If it fails parsing the URL, we'll just rely on the default name below. + } + } + + if (TextUtils.isEmpty(name)) name = "index"; + + String testName = baseName + name + WEB_ARCHIVE_EXTENSION; + if (!new File(testName).exists()) return testName; + + for (int i = 1; i < 100; i++) { + testName = baseName + name + "-" + i + WEB_ARCHIVE_EXTENSION; + if (!new File(testName).exists()) return testName; + } + + Log.e(TAG, "Unable to auto generate archive name for path: " + baseName); + return null; + } + //-------------------------------------------------------------------------------------------- // Native methods //-------------------------------------------------------------------------------------------- @@ -131,6 +233,8 @@ public class AwContents { private native int nativeGetWebContents(int nativeAwContents); private native void nativeDocumentHasImages(int nativeAwContents, Message message); + private native void nativeGenerateMHTML( + int nativeAwContents, String path, ValueCallback<String> callback); private native void nativeSetIoThreadClient(int nativeAwContents, AwContentsIoThreadClient ioThreadClient); diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/ArchiveTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/ArchiveTest.java new file mode 100644 index 0000000..b487d7c --- /dev/null +++ b/android_webview/javatests/src/org/chromium/android_webview/test/ArchiveTest.java @@ -0,0 +1,138 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.android_webview.test; + +import android.test.suitebuilder.annotation.SmallTest; +import android.webkit.ValueCallback; + +import org.chromium.android_webview.AwContents; +import org.chromium.base.ThreadUtils; +import org.chromium.base.test.Feature; + +import java.io.File; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +public class ArchiveTest extends AndroidWebViewTestBase { + + private static final long TEST_TIMEOUT = 20000L; + + private static final String TEST_PAGE = + "data:text/html;utf-8,<html><head></head><body>test</body></html>"; + + private TestAwContentsClient mContentsClient = new TestAwContentsClient(); + private AwTestContainerView mTestContainerView; + + @Override + protected void setUp() throws Exception { + mTestContainerView = createAwTestContainerViewOnMainSync(mContentsClient); + } + + private void doArchiveTest(final AwContents contents, final String path, + final boolean autoName, String expectedPath) throws InterruptedException { + if (expectedPath != null) { + File file = new File(expectedPath); + file.delete(); + } + + // Set up a handler to handle the completion callback + final Semaphore s = new Semaphore(0); + final AtomicReference<String> msgPath = new AtomicReference<String>(); + final ValueCallback<String> callback = new ValueCallback<String>() { + @Override + public void onReceiveValue(String path) { + msgPath.set(path); + s.release(); + } + }; + + // Generate MHTML and wait for completion + ThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + contents.saveWebArchive(path, autoName, callback); + } + }); + assertTrue(s.tryAcquire(TEST_TIMEOUT, TimeUnit.MILLISECONDS)); + + assertEquals(expectedPath, msgPath.get()); + if (expectedPath != null) { + File file = new File(expectedPath); + assertTrue(file.exists()); + assertTrue(file.length() > 0); + } else { + // A path was provided, but the expected path was null. This means the save should have + // failed, and so there shouldn't be a file path path. + if (path != null) { + assertFalse(new File(path).exists()); + } + } + } + + @SmallTest + @Feature({"Android-WebView"}) + public void testExplicitGoodPath() throws Throwable { + final String path = new File(getActivity().getFilesDir(), "test.mht").getAbsolutePath(); + File file = new File(path); + file.delete(); + assertFalse(file.exists()); + + loadUrlSync(mTestContainerView.getContentViewCore(), + mContentsClient.getOnPageFinishedHelper(), TEST_PAGE); + + doArchiveTest(mTestContainerView.getAwContents(), path, false, path); + } + + @SmallTest + @Feature({"Android-WebView"}) + public void testAutoGoodPath() throws Throwable { + final String path = getActivity().getFilesDir().getAbsolutePath() + "/"; + + loadUrlSync(mTestContainerView.getContentViewCore(), + mContentsClient.getOnPageFinishedHelper(), TEST_PAGE); + + // Create the first archive + { + String expectedPath = path + "index.mht"; + doArchiveTest(mTestContainerView.getAwContents(), path, true, expectedPath); + } + + // Create a second archive, making sure that the second archive's name is auto incremented. + { + String expectedPath = path + "index-1.mht"; + doArchiveTest(mTestContainerView.getAwContents(), path, true, expectedPath); + } + } + + @SmallTest + @Feature({"Android-WebView"}) + public void testExplicitBadPath() throws Throwable { + final String path = new File("/foo/bar/baz.mht").getAbsolutePath(); + File file = new File(path); + file.delete(); + assertFalse(file.exists()); + + loadUrlSync(mTestContainerView.getContentViewCore(), + mContentsClient.getOnPageFinishedHelper(), TEST_PAGE); + + doArchiveTest(mTestContainerView.getAwContents(), path, false, null); + } + + @SmallTest + @Feature({"Android-WebView"}) + public void testAutoBadPath() throws Throwable { + final String path = new File("/foo/bar/").getAbsolutePath(); + File file = new File(path); + file.delete(); + assertFalse(file.exists()); + + loadUrlSync(mTestContainerView.getContentViewCore(), + mContentsClient.getOnPageFinishedHelper(), TEST_PAGE); + + doArchiveTest(mTestContainerView.getAwContents(), path, true, null); + } + +} diff --git a/android_webview/native/aw_contents.cc b/android_webview/native/aw_contents.cc index be5193c..4b21b58 100644 --- a/android_webview/native/aw_contents.cc +++ b/android_webview/native/aw_contents.cc @@ -102,10 +102,31 @@ void DocumentHasImagesCallback(ScopedJavaGlobalRef<jobject>* message, } // namespace void AwContents::DocumentHasImages(JNIEnv* env, jobject obj, jobject message) { + ScopedJavaGlobalRef<jobject>* j_message = new ScopedJavaGlobalRef<jobject>(); + j_message->Reset(env, message); render_view_host_ext_->DocumentHasImages( - base::Bind(&DocumentHasImagesCallback, - base::Owned(new ScopedJavaGlobalRef<jobject>( - ScopedJavaLocalRef<jobject>(env, message))))); + base::Bind(&DocumentHasImagesCallback, base::Owned(j_message))); +} + +namespace { +void GenerateMHTMLCallback(ScopedJavaGlobalRef<jobject>* callback, + const FilePath& path, int64 size) { + JNIEnv* env = AttachCurrentThread(); + // Android files are UTF8, so the path conversion below is safe. + Java_AwContents_generateMHTMLCallback( + env, + base::android::ConvertUTF8ToJavaString(env, path.AsUTF8Unsafe()).obj(), + size, callback->obj()); +} +} // namespace + +void AwContents::GenerateMHTML(JNIEnv* env, jobject obj, + jstring jpath, jobject callback) { + ScopedJavaGlobalRef<jobject>* j_callback = new ScopedJavaGlobalRef<jobject>(); + j_callback->Reset(env, callback); + contents_container_->GetWebContents()->GenerateMHTML( + FilePath(base::android::ConvertJavaStringToUTF8(env, jpath)), + base::Bind(&GenerateMHTMLCallback, base::Owned(j_callback))); } void AwContents::onReceivedHttpAuthRequest( diff --git a/android_webview/native/aw_contents.h b/android_webview/native/aw_contents.h index c756e25..e441bd1 100644 --- a/android_webview/native/aw_contents.h +++ b/android_webview/native/aw_contents.h @@ -49,6 +49,7 @@ class AwContents { jint GetWebContents(JNIEnv* env, jobject obj); void Destroy(JNIEnv* env, jobject obj); void DocumentHasImages(JNIEnv* env, jobject obj, jobject message); + void GenerateMHTML(JNIEnv* env, jobject obj, jstring jpath, jobject callback); void SetIoThreadClient(JNIEnv* env, jobject obj, jobject client); private: |