diff options
author | Elliott Slaughter <eds@google.com> | 2010-06-23 10:29:48 -0700 |
---|---|---|
committer | Elliott Slaughter <eds@google.com> | 2010-07-12 09:05:20 -0700 |
commit | d66cc5d8dc3be3dcfc78f5155cf9f7b8015ee7b4 (patch) | |
tree | 3449c058e28cff389afae1aa9b30d5b48b11f59b | |
parent | f5a7037b5d31f66594af3428b1ffba88b4cc8d3b (diff) | |
download | external_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.mk | 2 | ||||
-rw-r--r-- | WebCore/Android.mk | 7 | ||||
-rw-r--r-- | WebCore/config.h | 2 | ||||
-rw-r--r-- | WebCore/loader/archive/ArchiveFactory.cpp | 4 | ||||
-rw-r--r-- | WebCore/loader/archive/android/WebArchiveAndroid.cpp | 470 | ||||
-rw-r--r-- | WebCore/loader/archive/android/WebArchiveAndroid.h | 55 | ||||
-rw-r--r-- | WebKit/android/jni/WebCoreFrameBridge.cpp | 91 |
7 files changed, 630 insertions, 1 deletions
@@ -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;", |