/* * Copyright 2007, 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 "webhistory" #include "config.h" #include "WebHistory.h" #include "AndroidLog.h" #include "BackForwardList.h" #include "BackForwardListImpl.h" #include "DocumentLoader.h" #include "Frame.h" #include "FrameLoader.h" #include "FrameLoaderClientAndroid.h" #include "FrameTree.h" #include "GraphicsJNI.h" #include "HistoryItem.h" #include "IconDatabase.h" #include "Page.h" #include "TextEncoding.h" #include "WebCoreFrameBridge.h" #include "WebCoreJni.h" #include "WebIconDatabase.h" #include #include "JNIUtility.h" #include #include #include #include #include namespace android { // Forward declarations static void writeItem(WTF::Vector& vector, WebCore::HistoryItem* item); static void writeChildrenRecursive(WTF::Vector& vector, WebCore::HistoryItem* parent); static bool readItemRecursive(WebCore::HistoryItem* child, const char** pData, int length); // Field ids for WebHistoryClassicItems struct WebHistoryItemClassicFields { jmethodID mInit; } gWebHistoryItemClassic; struct WebBackForwardListClassicFields { jmethodID mAddHistoryItem; jmethodID mRemoveHistoryItem; jmethodID mSetCurrentIndex; } gWebBackForwardListClassic; //-------------------------------------------------------------------------- // WebBackForwardListClassic native methods. //-------------------------------------------------------------------------- static void WebHistoryClose(JNIEnv* env, jobject obj, jint frame) { ALOG_ASSERT(frame, "Close needs a valid Frame pointer!"); WebCore::Frame* pFrame = (WebCore::Frame*)frame; WebCore::BackForwardListImpl* list = static_cast(pFrame->page()->backForwardList()); RefPtr current = list->currentItem(); // Remove each item instead of using close(). close() is intended to be used // right before the list is deleted. WebCore::HistoryItemVector& entries = list->entries(); int size = entries.size(); for (int i = size - 1; i >= 0; --i) list->removeItem(entries[i].get()); // Add the current item back to the list. if (current) { current->setBridge(0); // addItem will update the children to match the newly created bridge list->addItem(current); /* * The Grand Prix site uses anchor navigations to change the display. * WebKit tries to be smart and not load child frames that have the * same history urls during an anchor navigation. This means that the * current history item stored in the child frame's loader does not * match the item found in the history tree. If we remove all the * entries in the back/foward list, we have to restore the entire tree * or else a HistoryItem might have a deleted parent. * * In order to restore the history tree correctly, we have to look up * all the frames first and then look up the history item. We do this * because the history item in the tree may be null at this point. * Unfortunately, a HistoryItem can only search its immediately * children so we do a breadth-first rebuild of the tree. */ // Keep a small list of child frames to traverse. WTF::Vector frameQueue; // Fix the top-level item. pFrame->loader()->history()->setCurrentItem(current.get()); WebCore::Frame* child = pFrame->tree()->firstChild(); // Remember the parent history item so we can search for a child item. RefPtr parent = current; while (child) { // Use the old history item since the current one may have a // deleted parent. WebCore::HistoryItem* item = parent->childItemWithTarget(child->tree()->name()); child->loader()->history()->setCurrentItem(item); // Append the first child to the queue if it exists. If there is no // item, then we do not need to traverse the children since there // will be no parent history item. WebCore::Frame* firstChild; if (item && (firstChild = child->tree()->firstChild())) frameQueue.append(firstChild); child = child->tree()->nextSibling(); // If we don't have a sibling for this frame and the queue isn't // empty, use the next entry in the queue. if (!child && !frameQueue.isEmpty()) { child = frameQueue.at(0); frameQueue.remove(0); // Figure out the parent history item used when searching for // the history item to use. parent = child->tree()->parent()->loader()->history()->currentItem(); } } } } static void WebHistoryRestoreIndex(JNIEnv* env, jobject obj, jint frame, jint index) { ALOG_ASSERT(frame, "RestoreState needs a valid Frame pointer!"); WebCore::Frame* pFrame = (WebCore::Frame*)frame; WebCore::Page* page = pFrame->page(); WebCore::HistoryItem* currentItem = static_cast(page->backForwardList())->entries()[index].get(); // load the current page with FrameLoadTypeIndexedBackForward so that it // will use cache when it is possible page->goToItem(currentItem, FrameLoadTypeIndexedBackForward); } static jint WebHistoryInflate(JNIEnv* env, jobject obj, jint frame, jbyteArray data) { ALOG_ASSERT(frame, "Inflate needs a valid frame pointer!"); ALOG_ASSERT(data, "Inflate needs a valid data pointer!"); // Get the actual bytes and the length from the java array. const jbyte* bytes = env->GetByteArrayElements(data, NULL); jsize size = env->GetArrayLength(data); // Inflate the history tree into one HistoryItem or null if the inflation // failed. RefPtr newItem = WebCore::HistoryItem::create(); WebHistoryItem* bridge = new WebHistoryItem(newItem.get()); newItem->setBridge(bridge); // Inflate the item recursively. If it fails, that is ok. We'll have an // incomplete HistoryItem but that is better than crashing due to a null // item. // We have a 2nd local variable since read_item_recursive may change the // ptr's value. We can't pass &bytes since we have to send bytes to // ReleaseByteArrayElements unchanged. const char* ptr = reinterpret_cast(bytes); readItemRecursive(newItem.get(), &ptr, (int)size); env->ReleaseByteArrayElements(data, const_cast(bytes), JNI_ABORT); bridge->setActive(); // Add the new item to the back/forward list. WebCore::Frame* pFrame = (WebCore::Frame*)frame; pFrame->page()->backForwardList()->addItem(newItem); // Update the item. bridge->updateHistoryItem(newItem.get()); // Ref here because Java expects to adopt the reference, and as such will not // call ref on it. However, setBridge has also adopted the reference // TODO: This is confusing as hell, clean up ownership and have setBridge // take a RefPtr instead of a raw ptr and calling adoptRef on it bridge->ref(); return reinterpret_cast(bridge); } static void WebHistoryRef(JNIEnv* env, jobject obj, jint ptr) { if (ptr) reinterpret_cast(ptr)->ref(); } static void WebHistoryUnref(JNIEnv* env, jobject obj, jint ptr) { if (ptr) reinterpret_cast(ptr)->deref(); } static jobject WebHistoryGetTitle(JNIEnv* env, jobject obj, jint ptr) { if (!ptr) return 0; WebHistoryItem* item = reinterpret_cast(ptr); MutexLocker locker(item->m_lock); return wtfStringToJstring(env, item->m_title, false); } static jobject WebHistoryGetUrl(JNIEnv* env, jobject obj, jint ptr) { if (!ptr) return 0; WebHistoryItem* item = reinterpret_cast(ptr); MutexLocker locker(item->m_lock); return wtfStringToJstring(env, item->m_url, false); } static jobject WebHistoryGetOriginalUrl(JNIEnv* env, jobject obj, jint ptr) { if (!ptr) return 0; WebHistoryItem* item = reinterpret_cast(ptr); MutexLocker locker(item->m_lock); return wtfStringToJstring(env, item->m_originalUrl, false); } static jobject WebHistoryGetFlattenedData(JNIEnv* env, jobject obj, jint ptr) { if (!ptr) return 0; WebHistoryItem* item = reinterpret_cast(ptr); MutexLocker locker(item->m_lock); if (!item->m_dataCached) { // Try to create a new java byte array. jbyteArray b = env->NewByteArray(item->m_data.size()); if (!b) return NULL; // Write our flattened data to the java array. env->SetByteArrayRegion(b, 0, item->m_data.size(), (const jbyte*)item->m_data.data()); item->m_dataCached = env->NewGlobalRef(b); env->DeleteLocalRef(b); } return item->m_dataCached; } static jobject WebHistoryGetFavicon(JNIEnv* env, jobject obj, jint ptr) { if (!ptr) return 0; WebHistoryItem* item = reinterpret_cast(ptr); MutexLocker locker(item->m_lock); if (!item->m_faviconCached && item->m_favicon) { jobject favicon = GraphicsJNI::createBitmap(env, item->m_favicon, false, NULL); item->m_favicon = 0; // Framework now owns the pointer item->m_faviconCached = env->NewGlobalRef(favicon); env->DeleteLocalRef(favicon); } return item->m_faviconCached; } // 6 empty strings + no document state + children count + 2 scales = 10 unsigned values // 1 char for isTargetItem. #define HISTORY_MIN_SIZE ((int)(sizeof(unsigned) * 10 + sizeof(char))) void WebHistory::Flatten(JNIEnv* env, WTF::Vector& vector, WebCore::HistoryItem* item) { if (!item) return; // Reserve a vector of chars with an initial size of HISTORY_MIN_SIZE. vector.reserveCapacity(HISTORY_MIN_SIZE); // Write the top-level history item and then write all the children // recursively. ALOG_ASSERT(item->bridge(), "Why don't we have a bridge object here?"); writeItem(vector, item); writeChildrenRecursive(vector, item); } void WebHistoryItem::updateHistoryItem(WebCore::HistoryItem* item) { // Do not want to update during inflation. if (!m_active) return; WebHistoryItem* webItem = this; // Now we need to update the top-most WebHistoryItem based on the top-most // HistoryItem. if (m_parent) { webItem = m_parent.get(); if (webItem->hasOneRef()) { // if the parent only has one ref, it is from this WebHistoryItem. // This means that the matching WebCore::HistoryItem has been freed. // This can happen during clear(). ALOGW("Can't updateHistoryItem as the top HistoryItem is gone"); return; } while (webItem->parent()) webItem = webItem->parent(); item = webItem->historyItem(); if (!item) { // If a HistoryItem only exists for page cache, it is possible that // the parent HistoryItem destroyed before the child HistoryItem. If // it happens, skip updating. ALOGW("Can't updateHistoryItem as the top HistoryItem is gone"); return; } } JNIEnv* env = JSC::Bindings::getJNIEnv(); if (!env) return; MutexLocker locker(webItem->m_lock); // TODO: Figure out if we can't just use item->urlString() instead... const WTF::String urlString = WebFrame::convertIDNToUnicode(item->url()); webItem->m_url = urlString.threadsafeCopy(); const WTF::String originalUrlString = WebFrame::convertIDNToUnicode(item->originalURL()); webItem->m_originalUrl = originalUrlString.threadsafeCopy(); const WTF::String& titleString = item->title(); webItem->m_title = titleString.threadsafeCopy(); // Try to get the favicon from the history item. For some pages like Grand // Prix, there are history items with anchors. If the icon fails for the // item, try to get the icon using the url without the ref. jobject favicon = NULL; WTF::String url = item->urlString(); if (item->url().hasFragmentIdentifier()) { int refIndex = url.reverseFind('#'); url = url.substring(0, refIndex); } // FIXME: This method should not be used from outside WebCore and will be removed. // http://trac.webkit.org/changeset/81484 WebCore::Image* icon = WebCore::iconDatabase().synchronousIconForPageURL(url, WebCore::IntSize(16, 16)); delete webItem->m_favicon; webItem->m_favicon = webcoreImageToSkBitmap(icon); if (webItem->m_faviconCached) { env->DeleteGlobalRef(webItem->m_faviconCached); webItem->m_faviconCached = 0; } webItem->m_data.clear(); WebHistory::Flatten(env, webItem->m_data, item); if (webItem->m_dataCached) { env->DeleteGlobalRef(webItem->m_dataCached); webItem->m_dataCached = 0; } } WebHistoryItem::~WebHistoryItem() { delete m_favicon; JNIEnv* env = JSC::Bindings::getJNIEnv(); if (!env) { ALOGW("Failed to get JNIEnv*! Potential memory leak!"); return; } if (m_faviconCached) { env->DeleteGlobalRef(m_faviconCached); m_faviconCached = 0; } if (m_dataCached) { env->DeleteGlobalRef(m_dataCached); m_dataCached = 0; } } static void historyItemChanged(WebCore::HistoryItem* item) { ALOG_ASSERT(item, "historyItemChanged called with a null item"); if (item->bridge()) item->bridge()->updateHistoryItem(item); } void WebHistory::AddItem(const AutoJObject& list, WebCore::HistoryItem* item) { ALOG_ASSERT(item, "newItem must take a valid HistoryItem!"); // Item already added. Should only happen when we are inflating the list. if (item->bridge() || !list.get()) return; JNIEnv* env = list.env(); // Create the bridge, make it active, and attach it to the item. WebHistoryItem* bridge = new WebHistoryItem(item); bridge->setActive(); item->setBridge(bridge); // Allocate a blank WebHistoryItemClassic jclass clazz = env->FindClass("android/webkit/WebHistoryItemClassic"); jobject newItem = env->NewObject(clazz, gWebHistoryItemClassic.mInit, reinterpret_cast(bridge)); env->DeleteLocalRef(clazz); // Update the history item which will flatten the data and call update on // the java item. bridge->updateHistoryItem(item); // Add it to the list. env->CallVoidMethod(list.get(), gWebBackForwardListClassic.mAddHistoryItem, newItem); // Delete our local reference. env->DeleteLocalRef(newItem); } void WebHistory::RemoveItem(const AutoJObject& list, int index) { if (list.get()) list.env()->CallVoidMethod(list.get(), gWebBackForwardListClassic.mRemoveHistoryItem, index); } void WebHistory::UpdateHistoryIndex(const AutoJObject& list, int newIndex) { if (list.get()) list.env()->CallVoidMethod(list.get(), gWebBackForwardListClassic.mSetCurrentIndex, newIndex); } static void writeString(WTF::Vector& vector, const WTF::String& str) { unsigned strLen = str.length(); // Only do work if the string has data. if (strLen) { // Determine how much to grow the vector. Use the worst case for utf8 to // avoid reading the string twice. Add sizeof(unsigned) to hold the // string length in utf8. unsigned vectorLen = vector.size() + sizeof(unsigned); unsigned length = (strLen << 2) + vectorLen; // Grow the vector. This will change the value of v.size() but we // remember the original size above. vector.grow(length); // Grab the position to write to. char* data = vector.begin() + vectorLen; // Write the actual string int l = SkUTF16_ToUTF8(str.characters(), strLen, data); ALOGV("Writing string %d %.*s", l, l, data); // Go back and write the utf8 length. Subtract sizeof(unsigned) from // data to get the position to write the length. memcpy(data - sizeof(unsigned), (char*)&l, sizeof(unsigned)); // Shrink the internal state of the vector so we match what was // actually written. vector.shrink(vectorLen + l); } else vector.append((char*)&strLen, sizeof(unsigned)); } static void writeItem(WTF::Vector& vector, WebCore::HistoryItem* item) { // Original url writeString(vector, item->originalURLString()); // Url writeString(vector, item->urlString()); // Title writeString(vector, item->title()); // Form content type writeString(vector, item->formContentType()); // Form data const WebCore::FormData* formData = item->formData(); if (formData) { WTF::String flattenedFormData = formData->flattenToString(); writeString(vector, flattenedFormData); if (!flattenedFormData.isEmpty()) { // save the identifier as it is not included in the flatten data int64_t id = formData->identifier(); vector.append((char*)&id, sizeof(int64_t)); } } else writeString(vector, WTF::String()); // Empty constructor does not allocate a buffer. // Target writeString(vector, item->target()); AndroidWebHistoryBridge* bridge = item->bridge(); ALOG_ASSERT(bridge, "We should have a bridge here!"); // Screen scale const float scale = bridge->scale(); ALOGV("Writing scale %f", scale); vector.append((char*)&scale, sizeof(float)); const float textWrapScale = bridge->textWrapScale(); ALOGV("Writing text wrap scale %f", textWrapScale); vector.append((char*)&textWrapScale, sizeof(float)); // Scroll position. const int scrollX = item->scrollPoint().x(); vector.append((char*)&scrollX, sizeof(int)); const int scrollY = item->scrollPoint().y(); vector.append((char*)&scrollY, sizeof(int)); // Document state const WTF::Vector& docState = item->documentState(); WTF::Vector::const_iterator end = docState.end(); unsigned stateSize = docState.size(); ALOGV("Writing docState %d", stateSize); vector.append((char*)&stateSize, sizeof(unsigned)); for (WTF::Vector::const_iterator i = docState.begin(); i != end; ++i) { writeString(vector, *i); } // Is target item ALOGV("Writing isTargetItem %d", item->isTargetItem()); vector.append((char)item->isTargetItem()); // Children count unsigned childCount = item->children().size(); ALOGV("Writing childCount %d", childCount); vector.append((char*)&childCount, sizeof(unsigned)); } static void writeChildrenRecursive(WTF::Vector& vector, WebCore::HistoryItem* parent) { const WebCore::HistoryItemVector& children = parent->children(); WebCore::HistoryItemVector::const_iterator end = children.end(); for (WebCore::HistoryItemVector::const_iterator i = children.begin(); i != end; ++i) { WebCore::HistoryItem* item = (*i).get(); ALOG_ASSERT(parent->bridge(), "The parent item should have a bridge object!"); if (!item->bridge()) { WebHistoryItem* bridge = new WebHistoryItem(static_cast(parent->bridge())); item->setBridge(bridge); bridge->setActive(); } else { // The only time this item's parent may not be the same as the // parent's bridge is during history close. In that case, the // parent must not have a parent bridge. WebHistoryItem* bridge = static_cast(item->bridge()); WebHistoryItem* parentBridge = static_cast(parent->bridge()); ALOG_ASSERT(parentBridge->parent() == 0 || bridge->parent() == parentBridge, "Somehow this item has an incorrect parent"); bridge->setParent(parentBridge); } writeItem(vector, item); writeChildrenRecursive(vector, item); } } bool readUnsigned(const char*& data, const char* end, unsigned& result, const char* dbgLabel = 0); bool readInt(const char*& data, const char* end, int& result, const char* dbgLabel = 0); bool readInt64(const char*& data, const char* end, int64_t& result, const char* dbgLabel = 0); bool readFloat(const char*& data, const char* end, float& result, const char* dbgLabel = 0); bool readBool(const char*& data, const char* end, bool& result, const char* dbgLabel = 0); bool readString(const char*& data, const char* end, String& result, const char* dbgLabel = 0); bool readUnsigned(const char*& data, const char* end, unsigned& result, const char* dbgLabel) { // Check if we have enough data left to continue. if ((end < data) || (static_cast(end - data) < sizeof(unsigned))) { ALOGW("\tNot enough data to read unsigned; tag=\"%s\" end=%p data=%p", dbgLabel ? dbgLabel : "", end, data); return false; } memcpy(&result, data, sizeof(unsigned)); data += sizeof(unsigned); if (dbgLabel) ALOGV("Reading %-16s %u", dbgLabel, result); return true; } bool readInt(const char*& data, const char* end, int& result, const char* dbgLabel) { // Check if we have enough data left to continue. if ((end < data) || (static_cast(end - data) < sizeof(int))) { ALOGW("Not enough data to read int; tag=\"%s\" end=%p data=%p", dbgLabel ? dbgLabel : "", end, data); return false; } memcpy(&result, data, sizeof(int)); data += sizeof(int); if (dbgLabel) ALOGV("Reading %-16s %d", dbgLabel, result); return true; } bool readInt64(const char*& data, const char* end, int64_t& result, const char* dbgLabel) { // Check if we have enough data left to continue. if ((end < data) || (static_cast(end - data) < sizeof(int64_t))) { ALOGW("Not enough data to read int64_t; tag=\"%s\" end=%p data=%p", dbgLabel ? dbgLabel : "", end, data); return false; } memcpy(&result, data, sizeof(int64_t)); data += sizeof(int64_t); if (dbgLabel) ALOGV("Reading %-16s %ll", dbgLabel, result); return true; } bool readFloat(const char*& data, const char* end, float& result, const char* dbgLabel) { // Check if we have enough data left to continue. if ((end < data) || (static_cast(end - data) < sizeof(float))) { ALOGW("Not enough data to read float; tag=\"%s\" end=%p data=%p", dbgLabel ? dbgLabel : "", end, data); return false; } memcpy(&result, data, sizeof(float)); data += sizeof(float); if (dbgLabel) ALOGV("Reading %-16s %f", dbgLabel, result); return true; } // Note that the return value indicates success or failure, while the result // parameter indicates the read value of the bool bool readBool(const char*& data, const char* end, bool& result, const char* dbgLabel) { // Check if we have enough data left to continue. if ((end < data) || (static_cast(end - data) < sizeof(char))) { ALOGW("Not enough data to read bool; tag=\"%s\" end=%p data=%p", dbgLabel ? dbgLabel : "", end, data); return false; } char c; memcpy(&c, data, sizeof(char)); data += sizeof(char); if (dbgLabel) ALOGV("Reading %-16s %d", dbgLabel, c); result = c; // Valid bool results are 0 or 1 if ((c != 0) && (c != 1)) { ALOGW("Invalid value for bool; tag=\"%s\" end=%p data=%p c=%u", dbgLabel ? dbgLabel : "", end, data, c); return false; } return true; } bool readString(const char*& data, const char* end, String& result, const char* dbgLabel) { unsigned stringLength; if (!readUnsigned(data, end, stringLength)) { ALOGW("Not enough data to read string length; tag=\"%s\" end=%p data=%p", dbgLabel ? dbgLabel : "", end, data); return false; } if (dbgLabel) ALOGV("Reading %-16s %d %.*s", dbgLabel, stringLength, stringLength, data); // If length was 0, there will be no string content, but still return true if (!stringLength) { result = String(); return true; } if ((end < data) || ((unsigned)(end - data) < stringLength)) { ALOGW("Not enough data to read content; tag=\"%s\" end=%p data=%p stringLength=%u", dbgLabel ? dbgLabel : "", end, data, stringLength); return false; } const unsigned MAX_REASONABLE_STRING_LENGTH = 10000; if (stringLength > MAX_REASONABLE_STRING_LENGTH) { ALOGW("String length is suspiciously large (>%d); tag=\"%s\" end=%p data=%p stringLength=%u", MAX_REASONABLE_STRING_LENGTH, dbgLabel ? dbgLabel : "", end, data, stringLength); } bool decodeFailed = false; static const WebCore::TextEncoding& encoding = WebCore::UTF8Encoding(); result = encoding.decode(data, stringLength, true, decodeFailed); if (decodeFailed) { ALOGW("Decode failed, tag=\"%s\" end=%p data=%p stringLength=%u content=\"%s\"", dbgLabel ? dbgLabel : "", end, data, stringLength, result.utf8().data()); return false; } if (stringLength > MAX_REASONABLE_STRING_LENGTH) { ALOGW("\tdecodeFailed=%d (flag is ignored) content=\"%s\"", decodeFailed, result.utf8().data()); } data += stringLength; return true; } static bool readItemRecursive(WebCore::HistoryItem* newItem, const char** pData, int length) { if (!pData || length < HISTORY_MIN_SIZE) { ALOGW("readItemRecursive() bad params; pData=%p length=%d", pData, length); return false; } const char* data = *pData; const char* end = data + length; String content; // Read the original url if (readString(data, end, content, "Original url")) newItem->setOriginalURLString(content); else return false; // Read the url if (readString(data, end, content, "Url")) newItem->setURLString(content); else return false; // Read the title if (readString(data, end, content, "Title")) newItem->setTitle(content); else return false; // Generate a new ResourceRequest object for populating form information. // Read the form content type WTF::String formContentType; if (!readString(data, end, formContentType, "Content type")) return false; // Read the form data size unsigned formDataSize; if (!readUnsigned(data, end, formDataSize, "Form data size")) return false; // Read the form data WTF::RefPtr formData; if (formDataSize) { ALOGV("Reading Form data %d %.*s", formDataSize, formDataSize, data); if ((end < data) || ((size_t)(end - data) < formDataSize)) { ALOGW("\tNot enough data to read form data; returning"); return false; } formData = WebCore::FormData::create(data, formDataSize); data += formDataSize; // Read the identifier int64_t id; if (!readInt64(data, end, id, "Form id")) return false; if (id) formData->setIdentifier(id); } // Set up the form info if (formData != NULL) { WebCore::ResourceRequest r; r.setHTTPMethod("POST"); r.setHTTPContentType(formContentType); r.setHTTPBody(formData); newItem->setFormInfoFromRequest(r); } // Read the target if (readString(data, end, content, "Target")) newItem->setTarget(content); else return false; AndroidWebHistoryBridge* bridge = newItem->bridge(); ALOG_ASSERT(bridge, "There should be a bridge object during inflate"); // Read the screen scale float fValue; if (readFloat(data, end, fValue, "Screen scale")) bridge->setScale(fValue); else return false; // Read the text wrap scale if (readFloat(data, end, fValue, "Text wrap scale")) bridge->setTextWrapScale(fValue); else return false; // Read scroll position. int scrollX; if (!readInt(data, end, scrollX, "Scroll pos x")) return false; int scrollY; if (!readInt(data, end, scrollY, "Scroll pos y")) return false; newItem->setScrollPoint(IntPoint(scrollX, scrollY)); // Read the document state unsigned docStateCount; if (!readUnsigned(data, end, docStateCount, "Doc state count")) return false; if (docStateCount) { // Create a new vector and reserve enough space for the document state. WTF::Vector docState; docState.reserveCapacity(docStateCount); while (docStateCount--) { // Read a document state string if (readString(data, end, content, "Document state")) docState.append(content); else return false; } newItem->setDocumentState(docState); } // Read is target item bool c; if (readBool(data, end, c, "Target item")) newItem->setIsTargetItem(c); else return false; // Read the child count unsigned count; if (!readUnsigned(data, end, count, "Child count")) return false; *pData = data; if (count) { while (count--) { // No need to check the length each time because read_item_recursive // will return null if there isn't enough data left to parse. WTF::RefPtr child = WebCore::HistoryItem::create(); // Set a bridge that will not call into java. child->setBridge(new WebHistoryItem(static_cast(bridge))); // Read the child item. if (!readItemRecursive(child.get(), pData, end - data)) return false; child->bridge()->setActive(); newItem->addChildItem(child); } } return true; } // On arm, this test will cause memory corruption since converting char* will // byte align the result and this test does not use memset (it probably // should). // On the simulator, using HistoryItem will invoke the IconDatabase which will // initialize the main thread. Since this is invoked by the Zygote process, the // main thread will be incorrect and an assert will fire later. // In conclusion, define UNIT_TEST only if you know what you are doing. #ifdef UNIT_TEST static void unitTest() { ALOGD("Entering history unit test!"); const char* test1 = new char[0]; WTF::RefPtr item = WebCore::HistoryItem::create(); WebCore::HistoryItem* testItem = item.get(); testItem->setBridge(new WebHistoryItem(0)); ALOG_ASSERT(!readItemRecursive(testItem, &test1, 0), "0 length array should fail!"); delete[] test1; const char* test2 = new char[2]; ALOG_ASSERT(!readItemRecursive(testItem, &test2, 2), "Small array should fail!"); delete[] test2; ALOG_ASSERT(!readItemRecursive(testItem, NULL, HISTORY_MIN_SIZE), "Null data should fail!"); // Original Url char* test3 = new char[HISTORY_MIN_SIZE]; const char* ptr = (const char*)test3; memset(test3, 0, HISTORY_MIN_SIZE); *(int*)test3 = 4000; ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length originalUrl should fail!"); // Url int offset = 4; memset(test3, 0, HISTORY_MIN_SIZE); ptr = (const char*)test3; *(int*)(test3 + offset) = 4000; ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length url should fail!"); // Title offset += 4; memset(test3, 0, HISTORY_MIN_SIZE); ptr = (const char*)test3; *(int*)(test3 + offset) = 4000; ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length title should fail!"); // Form content type offset += 4; memset(test3, 0, HISTORY_MIN_SIZE); ptr = (const char*)test3; *(int*)(test3 + offset) = 4000; ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length contentType should fail!"); // Form data offset += 4; memset(test3, 0, HISTORY_MIN_SIZE); ptr = (const char*)test3; *(int*)(test3 + offset) = 4000; ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length form data should fail!"); // Target offset += 4; memset(test3, 0, HISTORY_MIN_SIZE); ptr = (const char*)test3; *(int*)(test3 + offset) = 4000; ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length target should fail!"); offset += 4; // Screen scale offset += 4; // Text wrap scale offset += 4; // Scroll pos x offset += 4; // Scroll pos y // Document state offset += 4; memset(test3, 0, HISTORY_MIN_SIZE); ptr = (const char*)test3; *(int*)(test3 + offset) = 4000; ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length document state should fail!"); // Is target item offset += 1; memset(test3, 0, HISTORY_MIN_SIZE); ptr = (const char*)test3; *(char*)(test3 + offset) = '!'; ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "IsTargetItem should fail with ! as the value!"); // Child count offset += 4; memset(test3, 0, HISTORY_MIN_SIZE); ptr = (const char*)test3; *(int*)(test3 + offset) = 4000; ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 kids should fail!"); // Test document state offset = 40; delete[] test3; test3 = new char[HISTORY_MIN_SIZE + sizeof(unsigned)]; memset(test3, 0, HISTORY_MIN_SIZE + sizeof(unsigned)); ptr = (const char*)test3; *(int*)(test3 + offset) = 1; *(int*)(test3 + offset + 4) = 20; ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE + sizeof(unsigned)), "1 20 length document state string should fail!"); delete[] test3; test3 = new char[HISTORY_MIN_SIZE + 2 * sizeof(unsigned)]; memset(test3, 0, HISTORY_MIN_SIZE + 2 * sizeof(unsigned)); ptr = (const char*)test3; *(int*)(test3 + offset) = 2; *(int*)(test3 + offset + 4) = 0; *(int*)(test3 + offset + 8) = 20; ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE + 2 * sizeof(unsigned) ), "2 20 length document state string should fail!"); delete[] test3; ALOGD("Leaving history unit test!"); } #endif //--------------------------------------------------------- // JNI registration //--------------------------------------------------------- static JNINativeMethod gWebBackForwardListClassicMethods[] = { { "nativeClose", "(I)V", (void*) WebHistoryClose }, { "restoreIndex", "(II)V", (void*) WebHistoryRestoreIndex } }; static JNINativeMethod gWebHistoryItemClassicMethods[] = { { "inflate", "(I[B)I", (void*) WebHistoryInflate }, { "nativeRef", "(I)V", (void*) WebHistoryRef }, { "nativeUnref", "(I)V", (void*) WebHistoryUnref }, { "nativeGetTitle", "(I)Ljava/lang/String;", (void*) WebHistoryGetTitle }, { "nativeGetUrl", "(I)Ljava/lang/String;", (void*) WebHistoryGetUrl }, { "nativeGetOriginalUrl", "(I)Ljava/lang/String;", (void*) WebHistoryGetOriginalUrl }, { "nativeGetFlattenedData", "(I)[B", (void*) WebHistoryGetFlattenedData }, { "nativeGetFavicon", "(I)Landroid/graphics/Bitmap;", (void*) WebHistoryGetFavicon }, }; int registerWebHistory(JNIEnv* env) { // Get notified of all changes to history items. WebCore::notifyHistoryItemChanged = historyItemChanged; #ifdef UNIT_TEST unitTest(); #endif // Find WebHistoryItemClassic, its constructor, and the update method. jclass clazz = env->FindClass("android/webkit/WebHistoryItemClassic"); ALOG_ASSERT(clazz, "Unable to find class android/webkit/WebHistoryItemClassic"); gWebHistoryItemClassic.mInit = env->GetMethodID(clazz, "", "(I)V"); ALOG_ASSERT(gWebHistoryItemClassic.mInit, "Could not find WebHistoryItemClassic constructor"); env->DeleteLocalRef(clazz); // Find the WebBackForwardListClassic object and method. clazz = env->FindClass("android/webkit/WebBackForwardListClassic"); ALOG_ASSERT(clazz, "Unable to find class android/webkit/WebBackForwardListClassic"); gWebBackForwardListClassic.mAddHistoryItem = env->GetMethodID(clazz, "addHistoryItem", "(Landroid/webkit/WebHistoryItem;)V"); ALOG_ASSERT(gWebBackForwardListClassic.mAddHistoryItem, "Could not find method addHistoryItem"); gWebBackForwardListClassic.mRemoveHistoryItem = env->GetMethodID(clazz, "removeHistoryItem", "(I)V"); ALOG_ASSERT(gWebBackForwardListClassic.mRemoveHistoryItem, "Could not find method removeHistoryItem"); gWebBackForwardListClassic.mSetCurrentIndex = env->GetMethodID(clazz, "setCurrentIndex", "(I)V"); ALOG_ASSERT(gWebBackForwardListClassic.mSetCurrentIndex, "Could not find method setCurrentIndex"); env->DeleteLocalRef(clazz); int result = jniRegisterNativeMethods(env, "android/webkit/WebBackForwardListClassic", gWebBackForwardListClassicMethods, NELEM(gWebBackForwardListClassicMethods)); return (result < 0) ? result : jniRegisterNativeMethods(env, "android/webkit/WebHistoryItemClassic", gWebHistoryItemClassicMethods, NELEM(gWebHistoryItemClassicMethods)); } } /* namespace android */