summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorElliott Slaughter <eds@google.com>2010-06-23 10:29:48 -0700
committerElliott Slaughter <eds@google.com>2010-07-12 09:05:20 -0700
commitd66cc5d8dc3be3dcfc78f5155cf9f7b8015ee7b4 (patch)
tree3449c058e28cff389afae1aa9b30d5b48b11f59b
parentf5a7037b5d31f66594af3428b1ffba88b4cc8d3b (diff)
downloadexternal_webkit-d66cc5d8dc3be3dcfc78f5155cf9f7b8015ee7b4.zip
external_webkit-d66cc5d8dc3be3dcfc78f5155cf9f7b8015ee7b4.tar.gz
external_webkit-d66cc5d8dc3be3dcfc78f5155cf9f7b8015ee7b4.tar.bz2
Browser save page as web archive.
Change-Id: Ie92953142ed31fc859975289a978172bdb8b6d79
-rw-r--r--Android.mk2
-rw-r--r--WebCore/Android.mk7
-rw-r--r--WebCore/config.h2
-rw-r--r--WebCore/loader/archive/ArchiveFactory.cpp4
-rw-r--r--WebCore/loader/archive/android/WebArchiveAndroid.cpp470
-rw-r--r--WebCore/loader/archive/android/WebArchiveAndroid.h55
-rw-r--r--WebKit/android/jni/WebCoreFrameBridge.cpp91
7 files changed, 630 insertions, 1 deletions
diff --git a/Android.mk b/Android.mk
index 5fddb9f..cc58cf7 100644
--- a/Android.mk
+++ b/Android.mk
@@ -131,6 +131,8 @@ LOCAL_C_INCLUDES := $(LOCAL_C_INCLUDES) \
$(LOCAL_PATH)/WebCore/inspector \
$(LOCAL_PATH)/WebCore/loader \
$(LOCAL_PATH)/WebCore/loader/appcache \
+ $(LOCAL_PATH)/WebCore/loader/archive \
+ $(LOCAL_PATH)/WebCore/loader/archive/android \
$(LOCAL_PATH)/WebCore/loader/icon \
$(LOCAL_PATH)/WebCore/notifications \
$(LOCAL_PATH)/WebCore/page \
diff --git a/WebCore/Android.mk b/WebCore/Android.mk
index 007261c..f31c42a 100644
--- a/WebCore/Android.mk
+++ b/WebCore/Android.mk
@@ -1042,6 +1042,13 @@ LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \
loader/CachedXSLStyleSheet.cpp \
dom/TransformSourceLibxslt.cpp
+# For Archive
+LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \
+ loader/archive/ArchiveFactory.cpp \
+ loader/archive/ArchiveResource.cpp \
+ loader/archive/ArchiveResourceCollection.cpp \
+ loader/archive/android/WebArchiveAndroid.cpp
+
# For complex scripts(Arabic, Thai, Hindi...).
ifeq ($(SUPPORT_COMPLEX_SCRIPTS),true)
LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \
diff --git a/WebCore/config.h b/WebCore/config.h
index 6e20614..c198305 100644
--- a/WebCore/config.h
+++ b/WebCore/config.h
@@ -115,7 +115,7 @@
#define ENABLE_XPATH 1
#define ENABLE_XSLT 1
#undef ENABLE_ARCHIVE // Enabled by default in Platform.h
-#define ENABLE_ARCHIVE 0
+#define ENABLE_ARCHIVE 1
#define ENABLE_OFFLINE_WEB_APPLICATIONS 1
#define ENABLE_TOUCH_EVENTS 1
#undef ENABLE_GEOLOCATION // Disabled by default in Platform.h
diff --git a/WebCore/loader/archive/ArchiveFactory.cpp b/WebCore/loader/archive/ArchiveFactory.cpp
index d09b064..5d10415 100644
--- a/WebCore/loader/archive/ArchiveFactory.cpp
+++ b/WebCore/loader/archive/ArchiveFactory.cpp
@@ -34,6 +34,8 @@
#if PLATFORM(CF) && !PLATFORM(QT)
#include "LegacyWebArchive.h"
+#elif PLATFORM(ANDROID)
+#include "WebArchiveAndroid.h"
#endif
#include <wtf/HashMap.h>
@@ -62,6 +64,8 @@ static ArchiveMIMETypesMap& archiveMIMETypes()
#if PLATFORM(CF) && !PLATFORM(QT)
mimeTypes.set("application/x-webarchive", archiveFactoryCreate<LegacyWebArchive>);
+#elif PLATFORM(ANDROID)
+ mimeTypes.set("application/x-webarchive-xml", archiveFactoryCreate<WebArchiveAndroid>);
#endif
initialized = true;
diff --git a/WebCore/loader/archive/android/WebArchiveAndroid.cpp b/WebCore/loader/archive/android/WebArchiveAndroid.cpp
new file mode 100644
index 0000000..ff93a4c
--- /dev/null
+++ b/WebCore/loader/archive/android/WebArchiveAndroid.cpp
@@ -0,0 +1,470 @@
+/*
+ * Copyright 2010, The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define LOG_TAG "webarchive"
+
+#include "config.h"
+#include "WebArchiveAndroid.h"
+
+#include "Base64.h"
+#include <libxml/encoding.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xmlstring.h>
+#include <libxml/xmlwriter.h>
+#include <wtf/text/CString.h>
+
+namespace WebCore {
+
+static const xmlChar* const archiveTag = BAD_CAST "Archive";
+static const xmlChar* const archiveResourceTag = BAD_CAST "ArchiveResource";
+static const xmlChar* const mainResourceTag = BAD_CAST "mainResource";
+static const xmlChar* const subresourcesTag = BAD_CAST "subresources";
+static const xmlChar* const subframesTag = BAD_CAST "subframes";
+static const xmlChar* const urlFieldTag = BAD_CAST "url";
+static const xmlChar* const mimeFieldTag = BAD_CAST "mimeType";
+static const xmlChar* const encodingFieldTag = BAD_CAST "textEncoding";
+static const xmlChar* const frameFieldTag = BAD_CAST "frameName";
+static const xmlChar* const dataFieldTag = BAD_CAST "data";
+
+PassRefPtr<WebArchiveAndroid> WebArchiveAndroid::create(PassRefPtr<ArchiveResource> mainResource,
+ Vector<PassRefPtr<ArchiveResource> >& subresources,
+ Vector<PassRefPtr<Archive> >& subframeArchives)
+{
+ if (mainResource) {
+ return adoptRef(new WebArchiveAndroid(mainResource, subresources, subframeArchives));
+ } else {
+ return NULL;
+ }
+}
+
+PassRefPtr<WebArchiveAndroid> WebArchiveAndroid::create(Frame* frame)
+{
+ PassRefPtr<ArchiveResource> mainResource = frame->loader()->documentLoader()->mainResource();
+ Vector<PassRefPtr<ArchiveResource> > subresources;
+ Vector<PassRefPtr<Archive> > subframes;
+ int children = frame->tree()->childCount();
+
+ frame->loader()->documentLoader()->getSubresources(subresources);
+
+ for (int child = 0; child < children; child++) {
+ subframes.append(create(frame->tree()->child(child)));
+ }
+
+ return create(mainResource, subresources, subframes);
+}
+
+WebArchiveAndroid::WebArchiveAndroid(PassRefPtr<ArchiveResource> mainResource,
+ Vector<PassRefPtr<ArchiveResource> >& subresources,
+ Vector<PassRefPtr<Archive> >& subframeArchives)
+{
+ setMainResource(mainResource);
+
+ for (Vector<PassRefPtr<ArchiveResource> >::iterator subresourcesIterator = subresources.begin();
+ subresourcesIterator != subresources.end();
+ subresourcesIterator++) {
+ addSubresource(*subresourcesIterator);
+ }
+
+ for (Vector<PassRefPtr<Archive> >::iterator subframesIterator = subframeArchives.begin();
+ subframesIterator != subframeArchives.end();
+ subframesIterator++) {
+ addSubframeArchive(*subframesIterator);
+ }
+}
+
+static bool loadArchiveResourceField(xmlNodePtr resourceNode, const xmlChar* fieldName, Vector<char>* outputData)
+{
+ if (!outputData)
+ return false;
+
+ outputData->clear();
+
+ const char* base64Data = NULL;
+
+ for (xmlNodePtr fieldNode = resourceNode->xmlChildrenNode;
+ fieldNode != NULL;
+ fieldNode = fieldNode->next) {
+ if (xmlStrEqual(fieldNode->name, fieldName)) {
+ base64Data = (const char*)xmlNodeGetContent(fieldNode->xmlChildrenNode);
+ if (!base64Data) {
+ /* Empty fields seem to break if they aren't null terminated. */
+ outputData->append('\0');
+ return true;
+ }
+ break;
+ }
+ }
+ if (!base64Data) {
+ LOGD("loadWebArchive: Failed to load field.");
+ return false;
+ }
+
+ const int base64Size = xmlStrlen(BAD_CAST base64Data);
+
+ const int result = base64Decode(base64Data, base64Size, *outputData);
+ if (!result) {
+ LOGD("loadWebArchive: Failed to decode field.");
+ return false;
+ }
+
+ return true;
+}
+
+static PassRefPtr<SharedBuffer> loadArchiveResourceFieldBuffer(xmlNodePtr resourceNode, const xmlChar* fieldName)
+{
+ Vector<char> fieldData;
+
+ if (loadArchiveResourceField(resourceNode, fieldName, &fieldData))
+ return SharedBuffer::create(fieldData.data(), fieldData.size());
+
+ return NULL;
+}
+
+static String loadArchiveResourceFieldString(xmlNodePtr resourceNode, const xmlChar* fieldName)
+{
+ Vector<char> fieldData;
+
+ if (loadArchiveResourceField(resourceNode, fieldName, &fieldData))
+ return String::fromUTF8(fieldData.data(), fieldData.size());
+
+ return String();
+}
+
+static KURL loadArchiveResourceFieldURL(xmlNodePtr resourceNode, const xmlChar* fieldName)
+{
+ Vector<char> fieldData;
+
+ if (loadArchiveResourceField(resourceNode, fieldName, &fieldData))
+ return KURL(ParsedURLString, String::fromUTF8(fieldData.data(), fieldData.size()));
+
+ return KURL();
+}
+
+static PassRefPtr<ArchiveResource> loadArchiveResource(xmlNodePtr resourceNode)
+{
+ if (!xmlStrEqual(resourceNode->name, archiveResourceTag)) {
+ LOGD("loadWebArchive: Malformed resource.");
+ return NULL;
+ }
+
+ KURL url = loadArchiveResourceFieldURL(resourceNode, urlFieldTag);
+ if (url.isNull()) {
+ LOGD("loadWebArchive: Failed to load resource.");
+ return NULL;
+ }
+
+ String mimeType = loadArchiveResourceFieldString(resourceNode, mimeFieldTag);
+ if (mimeType.isNull()) {
+ LOGD("loadWebArchive: Failed to load resource.");
+ return NULL;
+ }
+
+ String textEncoding = loadArchiveResourceFieldString(resourceNode, encodingFieldTag);
+ if (textEncoding.isNull()) {
+ LOGD("loadWebArchive: Failed to load resource.");
+ return NULL;
+ }
+
+ String frameName = loadArchiveResourceFieldString(resourceNode, frameFieldTag);
+ if (frameName.isNull()) {
+ LOGD("loadWebArchive: Failed to load resource.");
+ return NULL;
+ }
+
+ PassRefPtr<SharedBuffer> data = loadArchiveResourceFieldBuffer(resourceNode, dataFieldTag);
+ if (!data) {
+ LOGD("loadWebArchive: Failed to load resource.");
+ return NULL;
+ }
+
+ return ArchiveResource::create(data, url, mimeType, textEncoding, frameName);
+}
+
+static PassRefPtr<WebArchiveAndroid> loadArchive(xmlNodePtr archiveNode)
+{
+ xmlNodePtr resourceNode = NULL;
+
+ PassRefPtr<ArchiveResource> mainResource;
+ Vector<PassRefPtr<ArchiveResource> > subresources;
+ Vector<PassRefPtr<Archive> > subframes;
+
+ if (!xmlStrEqual(archiveNode->name, archiveTag)) {
+ LOGD("loadWebArchive: Malformed archive.");
+ return NULL;
+ }
+
+ for (resourceNode = archiveNode->xmlChildrenNode;
+ resourceNode != NULL;
+ resourceNode = resourceNode->next) {
+ if (xmlStrEqual(resourceNode->name, mainResourceTag)) {
+ resourceNode = resourceNode->xmlChildrenNode;
+ if (!resourceNode) {
+ break;
+ }
+ mainResource = loadArchiveResource(resourceNode);
+ break;
+ }
+ }
+ if (!mainResource) {
+ LOGD("saveWebArchive: Failed to load main resource.");
+ return NULL;
+ }
+
+ for (resourceNode = archiveNode->xmlChildrenNode;
+ resourceNode != NULL;
+ resourceNode = resourceNode->next) {
+ if (xmlStrEqual(resourceNode->name, subresourcesTag)) {
+ for (resourceNode = resourceNode->xmlChildrenNode;
+ resourceNode != NULL;
+ resourceNode = resourceNode->next) {
+ PassRefPtr<ArchiveResource> subresource = loadArchiveResource(resourceNode);
+ if (!subresource) {
+ LOGD("saveWebArchive: Failed to load subresource.");
+ break;
+ }
+ subresources.append(subresource);
+ }
+ break;
+ }
+ }
+
+ for (resourceNode = archiveNode->xmlChildrenNode;
+ resourceNode != NULL;
+ resourceNode = resourceNode->next) {
+ if (xmlStrEqual(resourceNode->name, subframesTag)) {
+ for (resourceNode = resourceNode->xmlChildrenNode;
+ resourceNode != NULL;
+ resourceNode = resourceNode->next) {
+ PassRefPtr<WebArchiveAndroid> subframe = loadArchive(resourceNode);
+ if (!subframe) {
+ LOGD("saveWebArchive: Failed to load subframe.");
+ break;
+ }
+ subframes.append(subframe);
+ }
+ break;
+ }
+ }
+
+ return WebArchiveAndroid::create(mainResource, subresources, subframes);
+}
+
+static PassRefPtr<WebArchiveAndroid> createArchiveForError() {
+ /* When an archive cannot be loaded, we return an empty archive instead. */
+ PassRefPtr<ArchiveResource> mainResource = ArchiveResource::create(
+ SharedBuffer::create(), KURL(ParsedURLString, String::fromUTF8("file:///dummy")),
+ String::fromUTF8("text/plain"), String(""), String(""));
+ Vector<PassRefPtr<ArchiveResource> > subresources;
+ Vector<PassRefPtr<Archive> > subframes;
+
+ return WebArchiveAndroid::create(mainResource, subresources, subframes);
+}
+
+PassRefPtr<WebArchiveAndroid> WebArchiveAndroid::create(SharedBuffer* buffer)
+{
+ const char* const noBaseUrl = "";
+ const char* const defaultEncoding = NULL;
+ const int noParserOptions = 0;
+
+ xmlDocPtr doc = xmlReadMemory(buffer->data(), buffer->size(), noBaseUrl, defaultEncoding, noParserOptions);
+ if (doc == NULL) {
+ LOGD("loadWebArchive: Failed to parse document.");
+ return createArchiveForError();
+ }
+
+ xmlNodePtr root = xmlDocGetRootElement(doc);
+ if (root == NULL) {
+ LOGD("loadWebArchive: Empty document.");
+ xmlFreeDoc(doc);
+ return createArchiveForError();
+ }
+
+ RefPtr<WebArchiveAndroid> archive = loadArchive(root);
+ if (!archive) {
+ LOGD("loadWebArchive: Failed to load archive.");
+ xmlFreeDoc(doc);
+ return createArchiveForError();
+ }
+
+ xmlFreeDoc(doc);
+ return archive.release();
+}
+
+static bool saveArchiveResourceField(xmlTextWriterPtr writer, const xmlChar* tag, const char* data, int size)
+{
+ int result = xmlTextWriterStartElement(writer, tag);
+ if (result < 0) {
+ LOGD("saveWebArchive: Failed to start element.");
+ return false;
+ }
+
+ if (size > 0) {
+ Vector<char> base64Data;
+ base64Encode(data, size, base64Data, false);
+ if (base64Data.isEmpty()) {
+ LOGD("saveWebArchive: Failed to base64 encode data.");
+ return false;
+ }
+
+ result = xmlTextWriterWriteRawLen(writer, BAD_CAST base64Data.data(), base64Data.size());
+ if (result < 0) {
+ LOGD("saveWebArchive: Failed to write data.");
+ return false;
+ }
+ }
+
+ result = xmlTextWriterEndElement(writer);
+ if (result < 0) {
+ LOGD("saveWebArchive: Failed to end element.");
+ return false;
+ }
+
+ return true;
+}
+
+static bool saveArchiveResourceField(xmlTextWriterPtr writer, const xmlChar* tag, SharedBuffer* buffer)
+{
+ return saveArchiveResourceField(writer, tag, buffer->data(), buffer->size());
+}
+
+static bool saveArchiveResourceField(xmlTextWriterPtr writer, const xmlChar* tag, const String& string)
+{
+ CString utf8String = string.utf8();
+
+ return saveArchiveResourceField(writer, tag, utf8String.data(), utf8String.length());
+}
+
+static bool saveArchiveResource(xmlTextWriterPtr writer, PassRefPtr<ArchiveResource> resource)
+{
+ int result = xmlTextWriterStartElement(writer, archiveResourceTag);
+ if (result < 0) {
+ LOGD("saveWebArchive: Failed to start element.");
+ return false;
+ }
+
+ if (!saveArchiveResourceField(writer, urlFieldTag, resource->url().string())
+ || !saveArchiveResourceField(writer, mimeFieldTag, resource->mimeType())
+ || !saveArchiveResourceField(writer, encodingFieldTag, resource->textEncoding())
+ || !saveArchiveResourceField(writer, frameFieldTag, resource->frameName())
+ || !saveArchiveResourceField(writer, dataFieldTag, resource->data()))
+ return false;
+
+ result = xmlTextWriterEndElement(writer);
+ if (result < 0) {
+ LOGD("saveWebArchive: Failed to end element.");
+ return false;
+ }
+
+ return true;
+}
+
+static bool saveArchive(xmlTextWriterPtr writer, PassRefPtr<Archive> archive) {
+ int result = xmlTextWriterStartElement(writer, archiveTag);
+ if (result < 0) {
+ LOGD("saveWebArchive: Failed to start element.");
+ return false;
+ }
+
+ result = xmlTextWriterStartElement(writer, mainResourceTag);
+ if (result < 0) {
+ LOGD("saveWebArchive: Failed to start element.");
+ return false;
+ }
+
+ if (!saveArchiveResource(writer, archive->mainResource()))
+ return false;
+
+ result = xmlTextWriterEndElement(writer);
+ if (result < 0) {
+ LOGD("saveWebArchive: Failed to end element.");
+ return false;
+ }
+
+ result = xmlTextWriterStartElement(writer, subresourcesTag);
+ if (result < 0) {
+ LOGD("saveWebArchive: Failed to start element.");
+ return false;
+ }
+
+ for (Vector<const RefPtr<ArchiveResource> >::iterator subresource = archive->subresources().begin();
+ subresource != archive->subresources().end();
+ subresource++) {
+ if (!saveArchiveResource(writer, *subresource))
+ return false;
+ }
+
+ result = xmlTextWriterEndElement(writer);
+ if (result < 0) {
+ LOGD("saveWebArchive: Failed to end element.");
+ return false;
+ }
+
+ result = xmlTextWriterStartElement(writer, subframesTag);
+ if (result < 0) {
+ LOGD("saveWebArchive: Failed to start element.");
+ return false;
+ }
+
+ for (Vector<const RefPtr<Archive> >::iterator subframe = archive->subframeArchives().begin();
+ subframe != archive->subframeArchives().end();
+ subframe++) {
+ if(!saveArchive(writer, *subframe))
+ return false;
+ }
+
+ result = xmlTextWriterEndElement(writer);
+ if (result < 0) {
+ LOGD("saveWebArchive: Failed to end element.");
+ return true;
+ }
+
+ return true;
+}
+
+bool WebArchiveAndroid::saveWebArchive(xmlTextWriterPtr writer) {
+ const char* const defaultXmlVersion = NULL;
+ const char* const defaultEncoding = NULL;
+ const char* const defaultStandalone = NULL;
+
+ int result = xmlTextWriterStartDocument(writer, defaultXmlVersion, defaultEncoding, defaultStandalone);
+ if (result < 0) {
+ LOGD("saveWebArchive: Failed to start document.");
+ return false;
+ }
+
+ if (!saveArchive(writer, this))
+ return false;
+
+ result = xmlTextWriterEndDocument(writer);
+ if (result< 0) {
+ LOGD("saveWebArchive: Failed to end document.");
+ return false;
+ }
+
+ return true;
+}
+
+}
diff --git a/WebCore/loader/archive/android/WebArchiveAndroid.h b/WebCore/loader/archive/android/WebArchiveAndroid.h
new file mode 100644
index 0000000..f749ef5
--- /dev/null
+++ b/WebCore/loader/archive/android/WebArchiveAndroid.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2010, The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef WEBARCHIVEANDROID_H
+#define WEBARCHIVEANDROID_H
+
+#include "Archive.h"
+#include "DocumentLoader.h"
+#include "Frame.h"
+#include <libxml/xmlwriter.h>
+
+namespace WebCore {
+
+class WebArchiveAndroid : public Archive {
+public:
+ static PassRefPtr<WebArchiveAndroid> create(PassRefPtr<ArchiveResource> mainResource,
+ Vector<PassRefPtr<ArchiveResource> >& subresources,
+ Vector<PassRefPtr<Archive> >& subframeArchives);
+
+ static PassRefPtr<WebArchiveAndroid> create(Frame* frame);
+ static PassRefPtr<WebArchiveAndroid> create(SharedBuffer* buffer);
+
+ bool saveWebArchive(xmlTextWriterPtr writer);
+
+private:
+ WebArchiveAndroid(PassRefPtr<ArchiveResource> mainResource,
+ Vector<PassRefPtr<ArchiveResource> >& subresources,
+ Vector<PassRefPtr<Archive> >& subframeArchives);
+};
+
+}
+
+#endif // WEBARCHIVEANDROID_H
diff --git a/WebKit/android/jni/WebCoreFrameBridge.cpp b/WebKit/android/jni/WebCoreFrameBridge.cpp
index 87ee07b..bfd4b62 100644
--- a/WebKit/android/jni/WebCoreFrameBridge.cpp
+++ b/WebKit/android/jni/WebCoreFrameBridge.cpp
@@ -112,6 +112,10 @@
#include "TimeCounter.h"
#endif
+#if ENABLE(ARCHIVE)
+#include "WebArchiveAndroid.h"
+#endif
+
using namespace JSC::Bindings;
static String* gUploadFileLabel;
@@ -1095,6 +1099,91 @@ static void StopLoading(JNIEnv *env, jobject obj)
pFrame->loader()->stopForUserCancel();
}
+#if ENABLE(ARCHIVE)
+static String saveArchiveAutoname(String basename, String name, String extension) {
+ if (name.isNull() || name.isEmpty()) {
+ name = String("index");
+ }
+
+ String testname = basename;
+ testname.append(name);
+ testname.append(extension);
+
+ errno = 0;
+ struct stat permissions;
+ if (stat(testname.utf8().data(), &permissions) < 0) {
+ if (errno == ENOENT)
+ return testname;
+ return String();
+ }
+
+ const int maxAttempts = 100;
+ for (int i = 1; i < maxAttempts; i++) {
+ String testname = basename;
+ testname.append(name);
+ testname.append("-");
+ testname.append(String::number(i));
+ testname.append(extension);
+
+ errno = 0;
+ if (stat(testname.utf8().data(), &permissions) < 0) {
+ if (errno == ENOENT)
+ return testname;
+ return String();
+ }
+ }
+
+ return String();
+}
+#endif
+
+static jobject SaveWebArchive(JNIEnv *env, jobject obj, jstring basename, jboolean autoname)
+{
+#if ENABLE(ARCHIVE)
+ WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj);
+ LOG_ASSERT(pFrame, "nativeSaveWebArchive must take a valid frame pointer!");
+
+ const char* basenameNative = getCharactersFromJStringInEnv(env, basename);
+ String basenameString = String::fromUTF8(basenameNative);
+ String filename;
+
+ if (autoname) {
+ String name = pFrame->loader()->documentLoader()->originalURL().lastPathComponent();
+ String extension = String(".webarchivexml");
+ filename = saveArchiveAutoname(basenameString, name, extension);
+ } else {
+ filename = basenameString;
+ }
+
+ if (filename.isNull() || filename.isEmpty()) {
+ LOGD("saveWebArchive: Failed to select a filename to save.");
+ releaseCharactersForJStringInEnv(env, basename, basenameNative);
+ return JNI_FALSE;
+ }
+
+ const int noCompression = 0;
+ xmlTextWriterPtr writer = xmlNewTextWriterFilename(filename.utf8().data(), noCompression);
+ if (writer == NULL) {
+ LOGD("saveWebArchive: Failed to initialize xml writer.");
+ releaseCharactersForJStringInEnv(env, basename, basenameNative);
+ return JNI_FALSE;
+ }
+
+ RefPtr<WebArchiveAndroid> archive = WebCore::WebArchiveAndroid::create(pFrame);
+
+ bool result = archive->saveWebArchive(writer);
+
+ releaseCharactersForJStringInEnv(env, basename, basenameNative);
+ xmlFreeTextWriter(writer);
+
+ if (result) {
+ return env->NewStringUTF(filename.utf8().data());
+ }
+
+ return NULL;
+#endif
+}
+
static jstring ExternalRepresentation(JNIEnv *env, jobject obj)
{
#ifdef ANDROID_INSTRUMENT
@@ -1640,6 +1729,8 @@ static JNINativeMethod gBrowserFrameNativeMethods[] = {
(void*) PostUrl },
{ "nativeLoadData", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
(void*) LoadData },
+ { "nativeSaveWebArchive", "(Ljava/lang/String;Z)Ljava/lang/String;",
+ (void*) SaveWebArchive },
{ "externalRepresentation", "()Ljava/lang/String;",
(void*) ExternalRepresentation },
{ "documentAsText", "()Ljava/lang/String;",