diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:30:52 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:30:52 -0800 |
commit | 8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2 (patch) | |
tree | 11425ea0b299d6fb89c6d3618a22d97d5bf68d0f /WebCore/loader | |
parent | 648161bb0edfc3d43db63caed5cc5213bc6cb78f (diff) | |
download | external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.zip external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.tar.gz external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'WebCore/loader')
111 files changed, 27515 insertions, 0 deletions
diff --git a/WebCore/loader/Cache.cpp b/WebCore/loader/Cache.cpp new file mode 100644 index 0000000..7d30e5f --- /dev/null +++ b/WebCore/loader/Cache.cpp @@ -0,0 +1,724 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#include "Cache.h" + +#include "CachedCSSStyleSheet.h" +#include "CachedFont.h" +#include "CachedImage.h" +#include "CachedScript.h" +#include "CachedXSLStyleSheet.h" +#include "DocLoader.h" +#include "Document.h" +#if USE(LOW_BANDWIDTH_DISPLAY) +#include "Frame.h" +#endif +#include "FrameLoader.h" +#include "FrameView.h" +#include "Image.h" +#include "ResourceHandle.h" +#include "SystemTime.h" +#include <stdio.h> + + +using namespace std; + +namespace WebCore { + +static const int cDefaultCacheCapacity = 8192 * 1024; +static const double cMinDelayBeforeLiveDecodedPrune = 1; // Seconds. +static const float cTargetPrunePercentage = .95f; // Percentage of capacity toward which we prune, to avoid immediately pruning again. +static const double cDefaultDecodedDataDeletionInterval = 0; + +Cache* cache() +{ + static Cache* staticCache = new Cache; + return staticCache; +} + +Cache::Cache() + : m_disabled(false) + , m_pruneEnabled(true) + , m_inPruneDeadResources(false) + , m_capacity(cDefaultCacheCapacity) + , m_minDeadCapacity(0) + , m_maxDeadCapacity(cDefaultCacheCapacity) + , m_deadDecodedDataDeletionInterval(cDefaultDecodedDataDeletionInterval) + , m_liveSize(0) + , m_deadSize(0) +{ +} + +static CachedResource* createResource(CachedResource::Type type, const KURL& url, const String& charset) +{ + switch (type) { + case CachedResource::ImageResource: + return new CachedImage(url.string()); + case CachedResource::CSSStyleSheet: + return new CachedCSSStyleSheet(url.string(), charset); + case CachedResource::Script: + return new CachedScript(url.string(), charset); + case CachedResource::FontResource: + return new CachedFont(url.string()); +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: + return new CachedXSLStyleSheet(url.string()); +#endif +#if ENABLE(XBL) + case CachedResource::XBLStyleSheet: + return new CachedXBLDocument(url.string()); +#endif + default: + break; + } + + return 0; +} + +CachedResource* Cache::requestResource(DocLoader* docLoader, CachedResource::Type type, const KURL& url, const String& charset, bool isPreload) +{ + // FIXME: Do we really need to special-case an empty URL? + // Would it be better to just go on with the cache code and let it fail later? + if (url.isEmpty()) + return 0; + + // Look up the resource in our map. + CachedResource* resource = m_resources.get(url.string()); + + if (resource) { + if (isPreload && !resource->isPreloaded()) + return 0; + if (FrameLoader::restrictAccessToLocal() && !FrameLoader::canLoad(url, String(), docLoader->doc())) { + Document* doc = docLoader->doc(); + if(doc && !isPreload) + FrameLoader::reportLocalLoadFailed(doc->frame(), resource->url()); + return 0; + } + } else { + if (FrameLoader::restrictAccessToLocal() && !FrameLoader::canLoad(url, String(), docLoader->doc())) { + Document* doc = docLoader->doc(); + if(doc && !isPreload) + FrameLoader::reportLocalLoadFailed(doc->frame(), url.string()); + return 0; + } + + // The resource does not exist. Create it. + resource = createResource(type, url, charset); + ASSERT(resource); + + // Pretend the resource is in the cache, to prevent it from being deleted during the load() call. + // FIXME: CachedResource should just use normal refcounting instead. + resource->setInCache(true); + + resource->load(docLoader); + + if (!disabled()) + m_resources.set(url.string(), resource); // The size will be added in later once the resource is loaded and calls back to us with the new size. + else { + // Kick the resource out of the cache, because the cache is disabled. + resource->setInCache(false); + resource->setDocLoader(docLoader); + if (resource->errorOccurred()) { + // We don't support immediate loads, but we do support immediate failure. + // In that case we should to delete the resource now and return 0 because otherwise + // it would leak if no ref/deref was ever done on it. + delete resource; + return 0; + } + } + } + + if (resource->type() != type) + return 0; + +#if USE(LOW_BANDWIDTH_DISPLAY) + // addLowBandwidthDisplayRequest() returns true if requesting CSS or JS during low bandwidth display. + // Here, return 0 to not block parsing or layout. + if (docLoader->frame() && docLoader->frame()->loader()->addLowBandwidthDisplayRequest(resource)) + return 0; +#endif + + if (!disabled()) { + // This will move the resource to the front of its LRU list and increase its access count. + resourceAccessed(resource); + } + + return resource; +} + +CachedCSSStyleSheet* Cache::requestUserCSSStyleSheet(DocLoader* docLoader, const String& url, const String& charset) +{ + CachedCSSStyleSheet* userSheet; + if (CachedResource* existing = m_resources.get(url)) { + if (existing->type() != CachedResource::CSSStyleSheet) + return 0; + userSheet = static_cast<CachedCSSStyleSheet*>(existing); + } else { + userSheet = new CachedCSSStyleSheet(url, charset); + + // Pretend the resource is in the cache, to prevent it from being deleted during the load() call. + // FIXME: CachedResource should just use normal refcounting instead. + userSheet->setInCache(true); + // Don't load incrementally, skip load checks, don't send resource load callbacks. + userSheet->load(docLoader, false, true, false); + if (!disabled()) + m_resources.set(url, userSheet); + else + userSheet->setInCache(false); + } + + if (!disabled()) { + // This will move the resource to the front of its LRU list and increase its access count. + resourceAccessed(userSheet); + } + + return userSheet; +} + +void Cache::revalidateResource(CachedResource* resource, DocLoader* docLoader) +{ + ASSERT(resource); + ASSERT(!disabled()); + if (resource->resourceToRevalidate()) + return; + if (!resource->canUseCacheValidator()) { + evict(resource); + return; + } + const String& url = resource->url(); + CachedResource* newResource = createResource(resource->type(), KURL(url), resource->encoding()); + newResource->setResourceToRevalidate(resource); + evict(resource); + m_resources.set(url, newResource); + newResource->setInCache(true); + resourceAccessed(newResource); + newResource->load(docLoader); +} + +void Cache::revalidationSucceeded(CachedResource* revalidatingResource, const ResourceResponse& response) +{ + CachedResource* resource = revalidatingResource->resourceToRevalidate(); + ASSERT(resource); + ASSERT(!resource->inCache()); + ASSERT(resource->isLoaded()); + + evict(revalidatingResource); + + ASSERT(!m_resources.get(resource->url())); + m_resources.set(resource->url(), resource); + resource->setInCache(true); + resource->setExpirationDate(response.expirationDate()); + insertInLRUList(resource); + int delta = resource->size(); + if (resource->decodedSize() && resource->hasClients()) + insertInLiveDecodedResourcesList(resource); + if (delta) + adjustSize(resource->hasClients(), delta); + + revalidatingResource->switchClientsToRevalidatedResource(); + // this deletes the revalidating resource + revalidatingResource->clearResourceToRevalidate(); +} + +void Cache::revalidationFailed(CachedResource* revalidatingResource) +{ + ASSERT(revalidatingResource->resourceToRevalidate()); + revalidatingResource->clearResourceToRevalidate(); +} + +CachedResource* Cache::resourceForURL(const String& url) +{ + return m_resources.get(url); +} + +unsigned Cache::deadCapacity() const +{ + // Dead resource capacity is whatever space is not occupied by live resources, bounded by an independent minimum and maximum. + unsigned capacity = m_capacity - min(m_liveSize, m_capacity); // Start with available capacity. + capacity = max(capacity, m_minDeadCapacity); // Make sure it's above the minimum. + capacity = min(capacity, m_maxDeadCapacity); // Make sure it's below the maximum. + return capacity; +} + +unsigned Cache::liveCapacity() const +{ + // Live resource capacity is whatever is left over after calculating dead resource capacity. + return m_capacity - deadCapacity(); +} + +void Cache::pruneLiveResources() +{ + if (!m_pruneEnabled) + return; + + unsigned capacity = liveCapacity(); + if (capacity && m_liveSize <= capacity) + return; + + unsigned targetSize = static_cast<unsigned>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again. + double currentTime = FrameView::currentPaintTimeStamp(); + if (!currentTime) // In case prune is called directly, outside of a Frame paint. + currentTime = WebCore::currentTime(); + + // Destroy any decoded data in live objects that we can. + // Start from the tail, since this is the least recently accessed of the objects. + CachedResource* current = m_liveDecodedResources.m_tail; + while (current) { + CachedResource* prev = current->m_prevInLiveResourcesList; + ASSERT(current->hasClients()); + if (current->isLoaded() && current->decodedSize()) { + // Check to see if the remaining resources are too new to prune. + double elapsedTime = currentTime - current->m_lastDecodedAccessTime; + if (elapsedTime < cMinDelayBeforeLiveDecodedPrune) + return; + + // Destroy our decoded data. This will remove us from + // m_liveDecodedResources, and possibly move us to a differnt LRU + // list in m_allResources. + current->destroyDecodedData(); + + if (targetSize && m_liveSize <= targetSize) + return; + } + current = prev; + } +} + +void Cache::pruneDeadResources() +{ + if (!m_pruneEnabled) + return; + + unsigned capacity = deadCapacity(); + if (capacity && m_deadSize <= capacity) + return; + + unsigned targetSize = static_cast<unsigned>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again. + int size = m_allResources.size(); + bool canShrinkLRULists = true; + m_inPruneDeadResources = true; + for (int i = size - 1; i >= 0; i--) { + // Remove from the tail, since this is the least frequently accessed of the objects. + CachedResource* current = m_allResources[i].m_tail; + + // First flush all the decoded data in this queue. + while (current) { + CachedResource* prev = current->m_prevInAllResourcesList; + if (!current->hasClients() && !current->isPreloaded() && current->isLoaded() && current->decodedSize()) { + // Destroy our decoded data. This will remove us from + // m_liveDecodedResources, and possibly move us to a differnt + // LRU list in m_allResources. + current->destroyDecodedData(); + + if (targetSize && m_deadSize <= targetSize) { + m_inPruneDeadResources = false; + return; + } + } + current = prev; + } + + // Now evict objects from this queue. + current = m_allResources[i].m_tail; + while (current) { + CachedResource* prev = current->m_prevInAllResourcesList; + if (!current->hasClients() && !current->isPreloaded()) { + evict(current); + // If evict() caused pruneDeadResources() to be re-entered, bail out. This can happen when removing an + // SVG CachedImage that has subresources. + if (!m_inPruneDeadResources) + return; + + if (targetSize && m_deadSize <= targetSize) { + m_inPruneDeadResources = false; + return; + } + } + current = prev; + } + + // Shrink the vector back down so we don't waste time inspecting + // empty LRU lists on future prunes. + if (m_allResources[i].m_head) + canShrinkLRULists = false; + else if (canShrinkLRULists) + m_allResources.resize(i); + } + m_inPruneDeadResources = false; +} + +void Cache::setCapacities(unsigned minDeadBytes, unsigned maxDeadBytes, unsigned totalBytes) +{ + ASSERT(minDeadBytes <= maxDeadBytes); + ASSERT(maxDeadBytes <= totalBytes); + m_minDeadCapacity = minDeadBytes; + m_maxDeadCapacity = maxDeadBytes; + m_capacity = totalBytes; + prune(); +} + +void Cache::evict(CachedResource* resource) +{ + // The resource may have already been removed by someone other than our caller, + // who needed a fresh copy for a reload. See <http://bugs.webkit.org/show_bug.cgi?id=12479#c6>. + if (resource->inCache()) { + // Remove from the resource map. + m_resources.remove(resource->url()); + resource->setInCache(false); + + // Remove from the appropriate LRU list. + removeFromLRUList(resource); + removeFromLiveDecodedResourcesList(resource); + + // Notify all doc loaders that might be observing this object still that it has been + // extracted from the set of resources. + HashSet<DocLoader*>::iterator end = m_docLoaders.end(); + for (HashSet<DocLoader*>::iterator itr = m_docLoaders.begin(); itr != end; ++itr) + (*itr)->removeCachedResource(resource); + + // Subtract from our size totals. + int delta = -static_cast<int>(resource->size()); + if (delta) + adjustSize(resource->hasClients(), delta); + } else + ASSERT(m_resources.get(resource->url()) != resource); + + if (resource->canDelete()) + delete resource; +} + +void Cache::addDocLoader(DocLoader* docLoader) +{ + m_docLoaders.add(docLoader); +} + +void Cache::removeDocLoader(DocLoader* docLoader) +{ + m_docLoaders.remove(docLoader); +} + +static inline unsigned fastLog2(unsigned i) +{ + unsigned log2 = 0; + if (i & (i - 1)) + log2 += 1; + if (i >> 16) + log2 += 16, i >>= 16; + if (i >> 8) + log2 += 8, i >>= 8; + if (i >> 4) + log2 += 4, i >>= 4; + if (i >> 2) + log2 += 2, i >>= 2; + if (i >> 1) + log2 += 1; + return log2; +} + +Cache::LRUList* Cache::lruListFor(CachedResource* resource) +{ + unsigned accessCount = max(resource->accessCount(), 1U); + unsigned queueIndex = fastLog2(resource->size() / accessCount); +#ifndef NDEBUG + resource->m_lruIndex = queueIndex; +#endif + if (m_allResources.size() <= queueIndex) + m_allResources.grow(queueIndex + 1); + return &m_allResources[queueIndex]; +} + +void Cache::removeFromLRUList(CachedResource* resource) +{ + // If we've never been accessed, then we're brand new and not in any list. + if (resource->accessCount() == 0) + return; + +#ifndef NDEBUG + unsigned oldListIndex = resource->m_lruIndex; +#endif + + LRUList* list = lruListFor(resource); + +#ifndef NDEBUG + // Verify that the list we got is the list we want. + ASSERT(resource->m_lruIndex == oldListIndex); + + // Verify that we are in fact in this list. + bool found = false; + for (CachedResource* current = list->m_head; current; current = current->m_nextInAllResourcesList) { + if (current == resource) { + found = true; + break; + } + } + ASSERT(found); +#endif + + CachedResource* next = resource->m_nextInAllResourcesList; + CachedResource* prev = resource->m_prevInAllResourcesList; + + if (next == 0 && prev == 0 && list->m_head != resource) + return; + + resource->m_nextInAllResourcesList = 0; + resource->m_prevInAllResourcesList = 0; + + if (next) + next->m_prevInAllResourcesList = prev; + else if (list->m_tail == resource) + list->m_tail = prev; + + if (prev) + prev->m_nextInAllResourcesList = next; + else if (list->m_head == resource) + list->m_head = next; +} + +void Cache::insertInLRUList(CachedResource* resource) +{ + // Make sure we aren't in some list already. + ASSERT(!resource->m_nextInAllResourcesList && !resource->m_prevInAllResourcesList); + ASSERT(resource->inCache()); + ASSERT(resource->accessCount() > 0); + + LRUList* list = lruListFor(resource); + + resource->m_nextInAllResourcesList = list->m_head; + if (list->m_head) + list->m_head->m_prevInAllResourcesList = resource; + list->m_head = resource; + + if (!resource->m_nextInAllResourcesList) + list->m_tail = resource; + +#ifndef NDEBUG + // Verify that we are in now in the list like we should be. + list = lruListFor(resource); + bool found = false; + for (CachedResource* current = list->m_head; current; current = current->m_nextInAllResourcesList) { + if (current == resource) { + found = true; + break; + } + } + ASSERT(found); +#endif + +} + +void Cache::resourceAccessed(CachedResource* resource) +{ + ASSERT(resource->inCache()); + + // Need to make sure to remove before we increase the access count, since + // the queue will possibly change. + removeFromLRUList(resource); + + // Add to our access count. + resource->increaseAccessCount(); + + // Now insert into the new queue. + insertInLRUList(resource); +} + +void Cache::removeFromLiveDecodedResourcesList(CachedResource* resource) +{ + // If we've never been accessed, then we're brand new and not in any list. + if (!resource->m_inLiveDecodedResourcesList) + return; + resource->m_inLiveDecodedResourcesList = false; + +#ifndef NDEBUG + // Verify that we are in fact in this list. + bool found = false; + for (CachedResource* current = m_liveDecodedResources.m_head; current; current = current->m_nextInLiveResourcesList) { + if (current == resource) { + found = true; + break; + } + } + ASSERT(found); +#endif + + CachedResource* next = resource->m_nextInLiveResourcesList; + CachedResource* prev = resource->m_prevInLiveResourcesList; + + if (next == 0 && prev == 0 && m_liveDecodedResources.m_head != resource) + return; + + resource->m_nextInLiveResourcesList = 0; + resource->m_prevInLiveResourcesList = 0; + + if (next) + next->m_prevInLiveResourcesList = prev; + else if (m_liveDecodedResources.m_tail == resource) + m_liveDecodedResources.m_tail = prev; + + if (prev) + prev->m_nextInLiveResourcesList = next; + else if (m_liveDecodedResources.m_head == resource) + m_liveDecodedResources.m_head = next; +} + +void Cache::insertInLiveDecodedResourcesList(CachedResource* resource) +{ + // Make sure we aren't in the list already. + ASSERT(!resource->m_nextInLiveResourcesList && !resource->m_prevInLiveResourcesList && !resource->m_inLiveDecodedResourcesList); + resource->m_inLiveDecodedResourcesList = true; + + resource->m_nextInLiveResourcesList = m_liveDecodedResources.m_head; + if (m_liveDecodedResources.m_head) + m_liveDecodedResources.m_head->m_prevInLiveResourcesList = resource; + m_liveDecodedResources.m_head = resource; + + if (!resource->m_nextInLiveResourcesList) + m_liveDecodedResources.m_tail = resource; + +#ifndef NDEBUG + // Verify that we are in now in the list like we should be. + bool found = false; + for (CachedResource* current = m_liveDecodedResources.m_head; current; current = current->m_nextInLiveResourcesList) { + if (current == resource) { + found = true; + break; + } + } + ASSERT(found); +#endif + +} + +void Cache::addToLiveResourcesSize(CachedResource* resource) +{ + m_liveSize += resource->size(); + m_deadSize -= resource->size(); +} + +void Cache::removeFromLiveResourcesSize(CachedResource* resource) +{ + m_liveSize -= resource->size(); + m_deadSize += resource->size(); +} + +void Cache::adjustSize(bool live, int delta) +{ + if (live) { + ASSERT(delta >= 0 || ((int)m_liveSize + delta >= 0)); + m_liveSize += delta; + } else { + ASSERT(delta >= 0 || ((int)m_deadSize + delta >= 0)); + m_deadSize += delta; + } +} + +Cache::Statistics Cache::getStatistics() +{ + Statistics stats; + CachedResourceMap::iterator e = m_resources.end(); + for (CachedResourceMap::iterator i = m_resources.begin(); i != e; ++i) { + CachedResource *o = i->second; + switch (o->type()) { + case CachedResource::ImageResource: + stats.images.count++; + stats.images.size += o->size(); + stats.images.liveSize += o->hasClients() ? o->size() : 0; + stats.images.decodedSize += o->decodedSize(); + break; + + case CachedResource::CSSStyleSheet: + stats.cssStyleSheets.count++; + stats.cssStyleSheets.size += o->size(); + stats.cssStyleSheets.liveSize += o->hasClients() ? o->size() : 0; + stats.cssStyleSheets.decodedSize += o->decodedSize(); + break; + + case CachedResource::Script: + stats.scripts.count++; + stats.scripts.size += o->size(); + stats.scripts.liveSize += o->hasClients() ? o->size() : 0; + stats.scripts.decodedSize += o->decodedSize(); + break; +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: + stats.xslStyleSheets.count++; + stats.xslStyleSheets.size += o->size(); + stats.xslStyleSheets.liveSize += o->hasClients() ? o->size() : 0; + stats.xslStyleSheets.decodedSize += o->decodedSize(); + break; +#endif + case CachedResource::FontResource: + stats.fonts.count++; + stats.fonts.size += o->size(); + stats.fonts.liveSize += o->hasClients() ? o->size() : 0; + stats.fonts.decodedSize += o->decodedSize(); + break; +#if ENABLE(XBL) + case CachedResource::XBL: + stats.xblDocs.count++; + stats.xblDocs.size += o->size(); + stats.xblDocs.liveSize += o->hasClients() ? o->size() : 0; + stats.xblDocs.decodedSize += o->decodedSize(); + break; +#endif + default: + break; + } + } + + return stats; +} + +void Cache::setDisabled(bool disabled) +{ + m_disabled = disabled; + if (!m_disabled) + return; + + for (;;) { + CachedResourceMap::iterator i = m_resources.begin(); + if (i == m_resources.end()) + break; + evict(i->second); + } +} + +#ifndef NDEBUG +void Cache::dumpLRULists(bool includeLive) const +{ + printf("LRU-SP lists in eviction order (Kilobytes decoded, Kilobytes encoded, Access count, Referenced):\n"); + + int size = m_allResources.size(); + for (int i = size - 1; i >= 0; i--) { + printf("\n\nList %d: ", i); + CachedResource* current = m_allResources[i].m_tail; + while (current) { + CachedResource* prev = current->m_prevInAllResourcesList; + if (includeLive || !current->hasClients()) + printf("(%.1fK, %.1fK, %uA, %dR); ", current->decodedSize() / 1024.0f, current->encodedSize() / 1024.0f, current->accessCount(), current->hasClients()); + current = prev; + } + } +} +#endif + +} // namespace WebCore diff --git a/WebCore/loader/Cache.h b/WebCore/loader/Cache.h new file mode 100644 index 0000000..ec0ea0e --- /dev/null +++ b/WebCore/loader/Cache.h @@ -0,0 +1,213 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style sheets and html + pages from the web. It has a memory cache for these objects. +*/ + +#ifndef Cache_h +#define Cache_h + +#include "CachePolicy.h" +#include "CachedResource.h" +#include "PlatformString.h" +#include "StringHash.h" +#include "loader.h" +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/Noncopyable.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class CachedCSSStyleSheet; +class CachedResource; +class DocLoader; +class KURL; + +// This cache holds subresources used by Web pages: images, scripts, stylesheets, etc. + +// The cache keeps a flexible but bounded window of dead resources that grows/shrinks +// depending on the live resource load. Here's an example of cache growth over time, +// with a min dead resource capacity of 25% and a max dead resource capacity of 50%: + +// |-----| Dead: - +// |----------| Live: + +// --|----------| Cache boundary: | (objects outside this mark have been evicted) +// --|----------++++++++++| +// -------|-----+++++++++++++++| +// -------|-----+++++++++++++++|+++++ + +class Cache : Noncopyable { +public: + friend Cache* cache(); + + typedef HashMap<String, CachedResource*> CachedResourceMap; + + struct LRUList { + CachedResource* m_head; + CachedResource* m_tail; + LRUList() : m_head(0), m_tail(0) { } + }; + + struct TypeStatistic { + int count; + int size; + int liveSize; + int decodedSize; + TypeStatistic() : count(0), size(0), liveSize(0), decodedSize(0) { } + }; + + struct Statistics { + TypeStatistic images; + TypeStatistic cssStyleSheets; + TypeStatistic scripts; +#if ENABLE(XSLT) + TypeStatistic xslStyleSheets; +#endif +#if ENABLE(XBL) + TypeStatistic xblDocs; +#endif + TypeStatistic fonts; + }; + + // The loader that fetches resources. + Loader* loader() { return &m_loader; } + + // Request resources from the cache. A load will be initiated and a cache object created if the object is not + // found in the cache. + CachedResource* requestResource(DocLoader*, CachedResource::Type, const KURL& url, const String& charset, bool isPreload = false); + + CachedCSSStyleSheet* requestUserCSSStyleSheet(DocLoader*, const String& url, const String& charset); + + void revalidateResource(CachedResource*, DocLoader*); + void revalidationSucceeded(CachedResource* revalidatingResource, const ResourceResponse&); + void revalidationFailed(CachedResource* revalidatingResource); + + // Sets the cache's memory capacities, in bytes. These will hold only approximately, + // since the decoded cost of resources like scripts and stylesheets is not known. + // - minDeadBytes: The maximum number of bytes that dead resources should consume when the cache is under pressure. + // - maxDeadBytes: The maximum number of bytes that dead resources should consume when the cache is not under pressure. + // - totalBytes: The maximum number of bytes that the cache should consume overall. + void setCapacities(unsigned minDeadBytes, unsigned maxDeadBytes, unsigned totalBytes); + + // Turn the cache on and off. Disabling the cache will remove all resources from the cache. They may + // still live on if they are referenced by some Web page though. + void setDisabled(bool); + bool disabled() const { return m_disabled; } + + void setPruneEnabled(bool enabled) { m_pruneEnabled = enabled; } + void prune() + { + if (m_liveSize + m_deadSize <= m_capacity && m_maxDeadCapacity && m_deadSize <= m_maxDeadCapacity) // Fast path. + return; + + pruneDeadResources(); // Prune dead first, in case it was "borrowing" capacity from live. + pruneLiveResources(); + } + + void setDeadDecodedDataDeletionInterval(double interval) { m_deadDecodedDataDeletionInterval = interval; } + double deadDecodedDataDeletionInterval() const { return m_deadDecodedDataDeletionInterval; } + + // Remove an existing cache entry from both the resource map and from the LRU list. + void remove(CachedResource* resource) { evict(resource); } + + void addDocLoader(DocLoader*); + void removeDocLoader(DocLoader*); + + CachedResource* resourceForURL(const String&); + + // Calls to put the cached resource into and out of LRU lists. + void insertInLRUList(CachedResource*); + void removeFromLRUList(CachedResource*); + + // Called to adjust the cache totals when a resource changes size. + void adjustSize(bool live, int delta); + + // Track decoded resources that are in the cache and referenced by a Web page. + void insertInLiveDecodedResourcesList(CachedResource*); + void removeFromLiveDecodedResourcesList(CachedResource*); + + void addToLiveResourcesSize(CachedResource*); + void removeFromLiveResourcesSize(CachedResource*); + + // Function to collect cache statistics for the caches window in the Safari Debug menu. + Statistics getStatistics(); + +#ifdef ANDROID_INSTRUMENT + unsigned getLiveSize() { return m_liveSize; } + unsigned getDeadSize() { return m_deadSize; } +#endif + +private: + Cache(); + ~Cache(); // Not implemented to make sure nobody accidentally calls delete -- WebCore does not delete singletons. + + LRUList* lruListFor(CachedResource*); + void resourceAccessed(CachedResource*); +#ifndef NDEBUG + void dumpLRULists(bool includeLive) const; +#endif + + unsigned liveCapacity() const; + unsigned deadCapacity() const; + + void pruneDeadResources(); // Flush decoded and encoded data from resources not referenced by Web pages. + void pruneLiveResources(); // Flush decoded data from resources still referenced by Web pages. + + void evict(CachedResource*); + + // Member variables. + HashSet<DocLoader*> m_docLoaders; + Loader m_loader; + + bool m_disabled; // Whether or not the cache is enabled. + bool m_pruneEnabled; + bool m_inPruneDeadResources; + + unsigned m_capacity; + unsigned m_minDeadCapacity; + unsigned m_maxDeadCapacity; + double m_deadDecodedDataDeletionInterval; + + unsigned m_liveSize; // The number of bytes currently consumed by "live" resources in the cache. + unsigned m_deadSize; // The number of bytes currently consumed by "dead" resources in the cache. + + // Size-adjusted and popularity-aware LRU list collection for cache objects. This collection can hold + // more resources than the cached resource map, since it can also hold "stale" muiltiple versions of objects that are + // waiting to die when the clients referencing them go away. + Vector<LRUList, 32> m_allResources; + + // List just for live resources with decoded data. Access to this list is based off of painting the resource. + LRUList m_liveDecodedResources; + + // A URL-based map of all resources that are in the cache (including the freshest version of objects that are currently being + // referenced by a Web page). + HashMap<String, CachedResource*> m_resources; +}; + +// Function to obtain the global cache. +Cache* cache(); + +} + +#endif diff --git a/WebCore/loader/CachePolicy.h b/WebCore/loader/CachePolicy.h new file mode 100644 index 0000000..16af78a --- /dev/null +++ b/WebCore/loader/CachePolicy.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003, 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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 CachePolicy_h +#define CachePolicy_h + +namespace WebCore { + + enum CachePolicy { + CachePolicyCache, + CachePolicyVerify, + CachePolicyRefresh, + CachePolicyReload + }; + +} + +#endif diff --git a/WebCore/loader/CachedCSSStyleSheet.cpp b/WebCore/loader/CachedCSSStyleSheet.cpp new file mode 100644 index 0000000..9059f25 --- /dev/null +++ b/WebCore/loader/CachedCSSStyleSheet.cpp @@ -0,0 +1,127 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style sheets and html + pages from the web. It has a memory cache for these objects. +*/ + +#include "config.h" +#include "CachedCSSStyleSheet.h" + +#include "CachedResourceClient.h" +#include "CachedResourceClientWalker.h" +#include "TextResourceDecoder.h" +#include "loader.h" +#include <wtf/Vector.h> + +namespace WebCore { + +CachedCSSStyleSheet::CachedCSSStyleSheet(const String& url, const String& charset) + : CachedResource(url, CSSStyleSheet) + , m_decoder(TextResourceDecoder::create("text/css", charset)) +{ + // Prefer text/css but accept any type (dell.com serves a stylesheet + // as text/html; see <http://bugs.webkit.org/show_bug.cgi?id=11451>). + setAccept("text/css,*/*;q=0.1"); +} + +CachedCSSStyleSheet::~CachedCSSStyleSheet() +{ +} + +void CachedCSSStyleSheet::addClient(CachedResourceClient *c) +{ + CachedResource::addClient(c); + + if (!m_loading) + c->setCSSStyleSheet(m_url, m_decoder->encoding().name(), this); +} + +void CachedCSSStyleSheet::setEncoding(const String& chs) +{ + m_decoder->setEncoding(chs, TextResourceDecoder::EncodingFromHTTPHeader); +} + +String CachedCSSStyleSheet::encoding() const +{ + return m_decoder->encoding().name(); +} + +void CachedCSSStyleSheet::data(PassRefPtr<SharedBuffer> data, bool allDataReceived) +{ + if (!allDataReceived) + return; + + m_data = data; + setEncodedSize(m_data.get() ? m_data->size() : 0); + if (m_data.get()) { + m_sheet = m_decoder->decode(m_data->data(), encodedSize()); + m_sheet += m_decoder->flush(); +#ifdef ANDROID_FIX // FIXME Newer webkit makes decode temporary; remove on webkit update + // report decoded size too + setDecodedSize(m_sheet.length() * sizeof(UChar)); +#endif + } + m_loading = false; + checkNotify(); +} + +void CachedCSSStyleSheet::checkNotify() +{ + if (m_loading) + return; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient *c = w.next()) + c->setCSSStyleSheet(m_response.url().string(), m_decoder->encoding().name(), this); + +#if USE(LOW_BANDWIDTH_DISPLAY) + // if checkNotify() is called from error(), client's setCSSStyleSheet(...) + // can't find "this" from url, so they can't do clean up if needed. + // call notifyFinished() to make sure they have a chance. + CachedResourceClientWalker n(m_clients); + while (CachedResourceClient* s = n.next()) + s->notifyFinished(this); +#endif +} + +void CachedCSSStyleSheet::error() +{ + m_loading = false; + m_errorOccurred = true; + checkNotify(); +} + +bool CachedCSSStyleSheet::canUseSheet(bool enforceMIMEType) const +{ + if (errorOccurred()) + return false; + + if (!enforceMIMEType) + return true; + + // This check exactly matches Firefox. + String mimeType = response().mimeType(); + return mimeType.isEmpty() || equalIgnoringCase(mimeType, "text/css") || equalIgnoringCase(mimeType, "application/x-unknown-content-type"); +} + +} diff --git a/WebCore/loader/CachedCSSStyleSheet.h b/WebCore/loader/CachedCSSStyleSheet.h new file mode 100644 index 0000000..b9129ba --- /dev/null +++ b/WebCore/loader/CachedCSSStyleSheet.h @@ -0,0 +1,68 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style sheets and html + pages from the web. It has a memory cache for these objects. +*/ + +#ifndef CachedCSSStyleSheet_h +#define CachedCSSStyleSheet_h + +#include "CachedResource.h" +#include "TextEncoding.h" +#include <wtf/Vector.h> + +namespace WebCore { + + class DocLoader; + class TextResourceDecoder; + + class CachedCSSStyleSheet : public CachedResource { + public: + CachedCSSStyleSheet(const String& URL, const String& charset); + virtual ~CachedCSSStyleSheet(); + + const String sheetText(bool enforceMIMEType = true) const { return canUseSheet(enforceMIMEType) ? m_sheet : ""; } + + virtual void addClient(CachedResourceClient*); + + virtual void setEncoding(const String&); + virtual String encoding() const; + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(); + + virtual bool schedule() const { return true; } + + void checkNotify(); + + private: + bool canUseSheet(bool enforceMIMEType) const; + + protected: + String m_sheet; + RefPtr<TextResourceDecoder> m_decoder; + }; + +} + +#endif diff --git a/WebCore/loader/CachedFont.cpp b/WebCore/loader/CachedFont.cpp new file mode 100644 index 0000000..20479ef --- /dev/null +++ b/WebCore/loader/CachedFont.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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. + */ + +#include "config.h" +#include "CachedFont.h" + +#include "Cache.h" +#include "CachedResourceClient.h" +#include "CachedResourceClientWalker.h" +#include "DOMImplementation.h" +#include "FontPlatformData.h" +#if PLATFORM(CG) || PLATFORM(QT) || PLATFORM(GTK) || PLATFORM(SGL) +#include "FontCustomPlatformData.h" +#endif +#include "TextResourceDecoder.h" +#include "loader.h" +#include <wtf/Vector.h> + +#if ENABLE(SVG_FONTS) +#include "HTMLNames.h" +#include "NodeList.h" +#include "SVGElement.h" +#include "SVGFontElement.h" +#include "SVGGElement.h" +#endif + +namespace WebCore { + +CachedFont::CachedFont(const String &url) + : CachedResource(url, FontResource) + , m_fontData(0) + , m_loadInitiated(false) +#if ENABLE(SVG_FONTS) + , m_isSVGFont(false) +#endif +{ +} + +CachedFont::~CachedFont() +{ +#if PLATFORM(CG) || PLATFORM(QT) || PLATFORM(GTK) + delete m_fontData; +#endif +} + +void CachedFont::load(DocLoader* docLoader) +{ + // Don't load the file yet. Wait for an access before triggering the load. + m_loading = true; +} + +void CachedFont::addClient(CachedResourceClient* c) +{ + CachedResource::addClient(c); + + if (!m_loading) + c->fontLoaded(this); +} + +void CachedFont::data(PassRefPtr<SharedBuffer> data, bool allDataReceived) +{ + if (!allDataReceived) + return; + + m_data = data; + setEncodedSize(m_data.get() ? m_data->size() : 0); + m_loading = false; + checkNotify(); +} + +void CachedFont::beginLoadIfNeeded(DocLoader* dl) +{ + if (!m_loadInitiated) { + m_loadInitiated = true; + cache()->loader()->load(dl, this, false); + } +} + +bool CachedFont::ensureCustomFontData() +{ +#if PLATFORM(CG) || PLATFORM(QT) || PLATFORM(GTK) || PLATFORM(SGL) +#if ENABLE(SVG_FONTS) + ASSERT(!m_isSVGFont); +#endif + if (!m_fontData && !m_errorOccurred && !m_loading && m_data) { + m_fontData = createFontCustomPlatformData(m_data.get()); + if (!m_fontData) + m_errorOccurred = true; + } +#endif + return m_fontData; +} + +FontPlatformData CachedFont::platformDataFromCustomData(float size, bool bold, bool italic, FontRenderingMode renderingMode) +{ +#if ENABLE(SVG_FONTS) + if (m_externalSVGDocument) + return FontPlatformData(size, bold, italic); +#endif +#if PLATFORM(CG) || PLATFORM(QT) || PLATFORM(GTK) || PLATFORM(SGL) + ASSERT(m_fontData); + return m_fontData->fontPlatformData(static_cast<int>(size), bold, italic, renderingMode); +#else + return FontPlatformData(); +#endif +} + +#if ENABLE(SVG_FONTS) +bool CachedFont::ensureSVGFontData() +{ + ASSERT(m_isSVGFont); + if (!m_externalSVGDocument && !m_errorOccurred && !m_loading && m_data) { + m_externalSVGDocument = SVGDocument::create(0); + m_externalSVGDocument->open(); + + RefPtr<TextResourceDecoder> decoder = TextResourceDecoder::create("application/xml"); + m_externalSVGDocument->write(decoder->decode(m_data->data(), m_data->size())); + if (decoder->sawError()) { + m_externalSVGDocument.clear(); + return 0; + } + + m_externalSVGDocument->finishParsing(); + m_externalSVGDocument->close(); + } + + return m_externalSVGDocument; +} + +SVGFontElement* CachedFont::getSVGFontById(const String& fontName) const +{ + ASSERT(m_isSVGFont); + RefPtr<NodeList> list = m_externalSVGDocument->getElementsByTagName(SVGNames::fontTag.localName()); + if (!list) + return 0; + + unsigned fonts = list->length(); + for (unsigned i = 0; i < fonts; ++i) { + Node* node = list->item(i); + ASSERT(node); + + if (static_cast<Element*>(node)->getAttribute(HTMLNames::idAttr) != fontName) + continue; + + ASSERT(node->hasTagName(SVGNames::fontTag)); + return static_cast<SVGFontElement*>(node); + } + + return 0; +} +#endif + +void CachedFont::allClientsRemoved() +{ +#if PLATFORM(CG) || PLATFORM(QT) || PLATFORM(GTK) + if (m_fontData) { + delete m_fontData; + m_fontData = 0; + } +#endif +} + +void CachedFont::checkNotify() +{ + if (m_loading) + return; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient *c = w.next()) + c->fontLoaded(this); +} + + +void CachedFont::error() +{ + m_loading = false; + m_errorOccurred = true; + checkNotify(); +} + +} diff --git a/WebCore/loader/CachedFont.h b/WebCore/loader/CachedFont.h new file mode 100644 index 0000000..fd19cdb --- /dev/null +++ b/WebCore/loader/CachedFont.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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 CachedFont_h +#define CachedFont_h + +#include "CachedResource.h" +#include "FontRenderingMode.h" +#include <wtf/Vector.h> + +#if ENABLE(SVG_FONTS) +#include "SVGElement.h" +#include "SVGDocument.h" +#endif + +namespace WebCore { + +class DocLoader; +class Cache; +class FontCustomPlatformData; +class FontPlatformData; +class SVGFontElement; + +class CachedFont : public CachedResource { +public: + CachedFont(const String& url); + virtual ~CachedFont(); + + virtual void load(DocLoader* docLoader); + + virtual void addClient(CachedResourceClient*); + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(); + + virtual void allClientsRemoved(); + + virtual bool schedule() const { return true; } + + void checkNotify(); + + void beginLoadIfNeeded(DocLoader* dl); + + bool ensureCustomFontData(); + FontPlatformData platformDataFromCustomData(float size, bool bold, bool italic, FontRenderingMode = NormalRenderingMode); + +#if ENABLE(SVG_FONTS) + bool isSVGFont() const { return m_isSVGFont; } + void setSVGFont(bool isSVG) { m_isSVGFont = isSVG; } + bool ensureSVGFontData(); + SVGFontElement* getSVGFontById(const String&) const; +#endif + +private: + FontCustomPlatformData* m_fontData; + bool m_loadInitiated; + +#if ENABLE(SVG_FONTS) + bool m_isSVGFont; + RefPtr<SVGDocument> m_externalSVGDocument; +#endif + + friend class Cache; +}; + +} + +#endif diff --git a/WebCore/loader/CachedImage.cpp b/WebCore/loader/CachedImage.cpp new file mode 100644 index 0000000..3327c38 --- /dev/null +++ b/WebCore/loader/CachedImage.cpp @@ -0,0 +1,364 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#include "CachedImage.h" + +#include "BitmapImage.h" +#include "Cache.h" +#include "CachedResourceClient.h" +#include "CachedResourceClientWalker.h" +#include "DocLoader.h" +#include "Frame.h" +#include "FrameView.h" +#include "Request.h" +#include "Settings.h" +#include "SystemTime.h" +#include <wtf/Vector.h> + +#if PLATFORM(CG) +#include "PDFDocumentImage.h" +#endif + +#if ENABLE(SVG_AS_IMAGE) +#include "SVGImage.h" +#endif + +using std::max; + +namespace WebCore { + +CachedImage::CachedImage(const String& url) + : CachedResource(url, ImageResource) + , m_image(0) + , m_decodedDataDeletionTimer(this, &CachedImage::decodedDataDeletionTimerFired) +{ + m_status = Unknown; +} + +CachedImage::CachedImage(Image* image) + : CachedResource(String(), ImageResource) + , m_image(image) + , m_decodedDataDeletionTimer(this, &CachedImage::decodedDataDeletionTimerFired) +{ + m_status = Cached; + m_loading = false; +} + +CachedImage::~CachedImage() +{ +} + +void CachedImage::decodedDataDeletionTimerFired(Timer<CachedImage>*) +{ + ASSERT(!hasClients()); + destroyDecodedData(); +} + +void CachedImage::load(DocLoader* docLoader) +{ +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + if (!docLoader || (docLoader->autoLoadImages() && !docLoader->shouldBlockNetworkImage(m_url))) +#else + if (!docLoader || docLoader->autoLoadImages()) +#endif + CachedResource::load(docLoader, true, false, true); + else + m_loading = false; +} + +void CachedImage::addClient(CachedResourceClient* c) +{ + CachedResource::addClient(c); + + if (m_decodedDataDeletionTimer.isActive()) + m_decodedDataDeletionTimer.stop(); + + if (m_image && !m_image->rect().isEmpty()) + c->imageChanged(this); + + if (!m_loading) + c->notifyFinished(this); +} + +void CachedImage::allClientsRemoved() +{ + if (m_image && !m_errorOccurred) + m_image->resetAnimation(); + if (double interval = cache()->deadDecodedDataDeletionInterval()) + m_decodedDataDeletionTimer.startOneShot(interval); +} + +static Image* brokenImage() +{ + static RefPtr<Image> brokenImage; + if (!brokenImage) + brokenImage = Image::loadPlatformResource("missingImage"); + return brokenImage.get(); +} + +static Image* nullImage() +{ + static RefPtr<BitmapImage> nullImage = BitmapImage::create(); + return nullImage.get(); +} + +Image* CachedImage::image() const +{ + if (m_errorOccurred) + return brokenImage(); + + if (m_image) + return m_image.get(); + + return nullImage(); +} + +void CachedImage::setImageContainerSize(const IntSize& containerSize) +{ + if (m_image) + m_image->setContainerSize(containerSize); +} + +bool CachedImage::usesImageContainerSize() const +{ + if (m_image) + return m_image->usesContainerSize(); + + return false; +} + +bool CachedImage::imageHasRelativeWidth() const +{ + if (m_image) + return m_image->hasRelativeWidth(); + + return false; +} + +bool CachedImage::imageHasRelativeHeight() const +{ + if (m_image) + return m_image->hasRelativeHeight(); + + return false; +} + +IntSize CachedImage::imageSize(float multiplier) const +{ + if (!m_image) + return IntSize(); + if (multiplier == 1.0f) + return m_image->size(); + + // Don't let images that have a width/height >= 1 shrink below 1 when zoomed. + bool hasWidth = m_image->size().width() > 0; + bool hasHeight = m_image->size().height() > 0; + int width = m_image->size().width() * (m_image->hasRelativeWidth() ? 1.0f : multiplier); + int height = m_image->size().height() * (m_image->hasRelativeHeight() ? 1.0f : multiplier); + if (hasWidth) + width = max(1, width); + if (hasHeight) + height = max(1, height); + return IntSize(width, height); +} + +IntRect CachedImage::imageRect(float multiplier) const +{ + if (!m_image) + return IntRect(); + if (multiplier == 1.0f || (!m_image->hasRelativeWidth() && !m_image->hasRelativeHeight())) + return m_image->rect(); + + float widthMultiplier = (m_image->hasRelativeWidth() ? 1.0f : multiplier); + float heightMultiplier = (m_image->hasRelativeHeight() ? 1.0f : multiplier); + + // Don't let images that have a width/height >= 1 shrink below 1 when zoomed. + bool hasWidth = m_image->rect().width() > 0; + bool hasHeight = m_image->rect().height() > 0; + + int width = static_cast<int>(m_image->rect().width() * widthMultiplier); + int height = static_cast<int>(m_image->rect().height() * heightMultiplier); + if (hasWidth) + width = max(1, width); + if (hasHeight) + height = max(1, height); + + int x = static_cast<int>(m_image->rect().x() * widthMultiplier); + int y = static_cast<int>(m_image->rect().y() * heightMultiplier); + + return IntRect(x, y, width, height); +} + +void CachedImage::notifyObservers() +{ + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient* c = w.next()) + c->imageChanged(this); +} + +void CachedImage::clear() +{ + destroyDecodedData(); + m_image = 0; + setEncodedSize(0); +} + +inline void CachedImage::createImage() +{ + // Create the image if it doesn't yet exist. + if (m_image) + return; +#if PLATFORM(CG) + if (m_response.mimeType() == "application/pdf") { + m_image = PDFDocumentImage::create(); + return; + } +#endif +#if ENABLE(SVG_AS_IMAGE) + if (m_response.mimeType() == "image/svg+xml") { + m_image = SVGImage::create(this); + return; + } +#endif + m_image = BitmapImage::create(this); +#if PLATFORM(SGL) + m_image->setURL(url()); +#endif +} + +size_t CachedImage::maximumDecodedImageSize() +{ + Frame* frame = m_request ? m_request->docLoader()->frame() : 0; + if (!frame) + return 0; + Settings* settings = frame->settings(); + return settings ? settings->maximumDecodedImageSize() : 0; +} + +void CachedImage::data(PassRefPtr<SharedBuffer> data, bool allDataReceived) +{ + m_data = data; + + createImage(); + + bool sizeAvailable = false; + + // Have the image update its data from its internal buffer. + // It will not do anything now, but will delay decoding until + // queried for info (like size or specific image frames). + sizeAvailable = m_image->setData(m_data, allDataReceived); + + // Go ahead and tell our observers to try to draw if we have either + // received all the data or the size is known. Each chunk from the + // network causes observers to repaint, which will force that chunk + // to decode. + if (sizeAvailable || allDataReceived) { + size_t maxDecodedImageSize = maximumDecodedImageSize(); + IntSize s = imageSize(1.0f); + size_t estimatedDecodedImageSize = s.width() * s.height() * 4; // no overflow check + if (m_image->isNull() || (maxDecodedImageSize > 0 && estimatedDecodedImageSize > maxDecodedImageSize)) { + error(); + if (inCache()) + cache()->remove(this); + return; + } + + notifyObservers(); + + if (m_image) + setEncodedSize(m_image->data() ? m_image->data()->size() : 0); + } + + if (allDataReceived) { + m_loading = false; + checkNotify(); + } +} + +void CachedImage::error() +{ + clear(); + m_errorOccurred = true; + notifyObservers(); + m_loading = false; + checkNotify(); +} + +void CachedImage::checkNotify() +{ + if (m_loading) + return; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient* c = w.next()) + c->notifyFinished(this); +} + +void CachedImage::destroyDecodedData() +{ + if (m_image && !m_errorOccurred) + m_image->destroyDecodedData(); +} + +void CachedImage::decodedSizeChanged(const Image* image, int delta) +{ + if (image != m_image) + return; + + setDecodedSize(decodedSize() + delta); +} + +void CachedImage::didDraw(const Image* image) +{ + if (image != m_image) + return; + + double timeStamp = FrameView::currentPaintTimeStamp(); + if (!timeStamp) // If didDraw is called outside of a Frame paint. + timeStamp = currentTime(); + + CachedResource::didAccessDecodedData(timeStamp); +} + +bool CachedImage::shouldPauseAnimation(const Image* image) +{ + if (image != m_image) + return false; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient* c = w.next()) { + if (c->willRenderImage(this)) + return false; + } + + return true; +} + +void CachedImage::animationAdvanced(const Image* image) +{ + if (image == m_image) + notifyObservers(); +} + +} //namespace WebCore diff --git a/WebCore/loader/CachedImage.h b/WebCore/loader/CachedImage.h new file mode 100644 index 0000000..f24e2fb --- /dev/null +++ b/WebCore/loader/CachedImage.h @@ -0,0 +1,101 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef CachedImage_h +#define CachedImage_h + +#include "CachedResource.h" +#include "ImageObserver.h" +#include "Image.h" +#include "IntRect.h" +#include "Timer.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class DocLoader; +class Cache; + +class CachedImage : public CachedResource, public ImageObserver { + friend class Cache; + +public: + CachedImage(const String& url); + CachedImage(Image*); + virtual ~CachedImage(); + + virtual void load(DocLoader* docLoader); + + Image* image() const; + + bool canRender(float multiplier) const { return !errorOccurred() && !imageSize(multiplier).isEmpty(); } + + // These are only used for SVGImage right now + void setImageContainerSize(const IntSize&); + bool usesImageContainerSize() const; + bool imageHasRelativeWidth() const; + bool imageHasRelativeHeight() const; + + // Both of these methods take a zoom multiplier that can be used to increase the natural size of the image by the + // zoom. + IntSize imageSize(float multiplier) const; // returns the size of the complete image. + IntRect imageRect(float multiplier) const; // The size of the currently decoded portion of the image. + + virtual void addClient(CachedResourceClient*); + + virtual void allClientsRemoved(); + virtual void destroyDecodedData(); + + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(); + + virtual bool schedule() const { return true; } + + void checkNotify(); + + virtual bool isImage() const { return true; } + + void clear(); + + bool stillNeedsLoad() const { return !m_errorOccurred && m_status == Unknown && m_loading == false; } + void load(); + + // ImageObserver + virtual void decodedSizeChanged(const Image* image, int delta); + virtual void didDraw(const Image*); + + virtual bool shouldPauseAnimation(const Image*); + virtual void animationAdvanced(const Image*); + +private: + void createImage(); + size_t maximumDecodedImageSize(); + void notifyObservers(); + void decodedDataDeletionTimerFired(Timer<CachedImage>*); + + RefPtr<Image> m_image; + Timer<CachedImage> m_decodedDataDeletionTimer; +}; + +} + +#endif diff --git a/WebCore/loader/CachedResource.cpp b/WebCore/loader/CachedResource.cpp new file mode 100644 index 0000000..6d0af9b --- /dev/null +++ b/WebCore/loader/CachedResource.cpp @@ -0,0 +1,316 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#include "CachedResource.h" + +#include "Cache.h" +#include "CachedResourceHandle.h" +#include "DocLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "KURL.h" +#include "Request.h" +#include "SystemTime.h" +#include <wtf/RefCountedLeakCounter.h> +#include <wtf/Vector.h> + +using namespace WTF; + +namespace WebCore { + +#ifndef NDEBUG +static RefCountedLeakCounter cachedResourceLeakCounter("CachedResource"); +#endif + +CachedResource::CachedResource(const String& url, Type type) + : m_url(url) + , m_lastDecodedAccessTime(0) + , m_sendResourceLoadCallbacks(true) + , m_preloadCount(0) + , m_preloadResult(PreloadNotReferenced) + , m_requestedFromNetworkingLayer(false) + , m_inCache(false) + , m_loading(false) + , m_docLoader(0) + , m_handleCount(0) + , m_resourceToRevalidate(0) + , m_isBeingRevalidated(false) + , m_expirationDate(0) +{ +#ifndef NDEBUG + cachedResourceLeakCounter.increment(); +#endif + + m_type = type; + m_status = Pending; + m_encodedSize = 0; + m_decodedSize = 0; + m_request = 0; + + m_accessCount = 0; + m_inLiveDecodedResourcesList = false; + + m_nextInAllResourcesList = 0; + m_prevInAllResourcesList = 0; + + m_nextInLiveResourcesList = 0; + m_prevInLiveResourcesList = 0; + +#ifndef NDEBUG + m_deleted = false; + m_lruIndex = 0; +#endif + m_errorOccurred = false; +} + +CachedResource::~CachedResource() +{ + ASSERT(!inCache()); + ASSERT(!m_deleted); + ASSERT(url().isNull() || cache()->resourceForURL(url()) != this); +#ifndef NDEBUG + m_deleted = true; + cachedResourceLeakCounter.decrement(); +#endif + + if (m_resourceToRevalidate) + m_resourceToRevalidate->m_isBeingRevalidated = false; + + if (m_docLoader) + m_docLoader->removeCachedResource(this); +} + +void CachedResource::load(DocLoader* docLoader, bool incremental, bool skipCanLoadCheck, bool sendResourceLoadCallbacks) +{ + m_sendResourceLoadCallbacks = sendResourceLoadCallbacks; + cache()->loader()->load(docLoader, this, incremental, skipCanLoadCheck, sendResourceLoadCallbacks); + m_loading = true; +} + +void CachedResource::finish() +{ + m_status = Cached; +} + +bool CachedResource::isExpired() const +{ + if (!m_expirationDate) + return false; + time_t now = time(0); + return difftime(now, m_expirationDate) >= 0; +} + +void CachedResource::setResponse(const ResourceResponse& response) +{ + m_response = response; + m_expirationDate = response.expirationDate(); +} + +void CachedResource::setRequest(Request* request) +{ + if (request && !m_request) + m_status = Pending; + m_request = request; + if (canDelete() && !inCache()) + delete this; +} + +void CachedResource::addClient(CachedResourceClient *c) +{ + if (m_preloadResult == PreloadNotReferenced) { + if (isLoaded()) + m_preloadResult = PreloadReferencedWhileComplete; + else if (m_requestedFromNetworkingLayer) + m_preloadResult = PreloadReferencedWhileLoading; + else + m_preloadResult = PreloadReferenced; + } + if (!hasClients() && inCache()) + cache()->addToLiveResourcesSize(this); + m_clients.add(c); +} + +void CachedResource::removeClient(CachedResourceClient *c) +{ + ASSERT(m_clients.contains(c)); + m_clients.remove(c); + if (canDelete() && !inCache()) + delete this; + else if (!hasClients() && inCache()) { + cache()->removeFromLiveResourcesSize(this); + cache()->removeFromLiveDecodedResourcesList(this); + allClientsRemoved(); + cache()->prune(); + } +} + +void CachedResource::deleteIfPossible() +{ + if (canDelete() && !inCache()) + delete this; +} + +void CachedResource::setDecodedSize(unsigned size) +{ + if (size == m_decodedSize) + return; + + int delta = size - m_decodedSize; + + // The object must now be moved to a different queue, since its size has been changed. + // We have to remove explicitly before updating m_decodedSize, so that we find the correct previous + // queue. + if (inCache()) + cache()->removeFromLRUList(this); + + m_decodedSize = size; + + if (inCache()) { + // Now insert into the new LRU list. + cache()->insertInLRUList(this); + + // Insert into or remove from the live decoded list if necessary. + if (m_decodedSize && !m_inLiveDecodedResourcesList && hasClients()) + cache()->insertInLiveDecodedResourcesList(this); + else if (!m_decodedSize && m_inLiveDecodedResourcesList) + cache()->removeFromLiveDecodedResourcesList(this); + + // Update the cache's size totals. + cache()->adjustSize(hasClients(), delta); + } +} + +void CachedResource::setEncodedSize(unsigned size) +{ + if (size == m_encodedSize) + return; + + // The size cannot ever shrink (unless it is being nulled out because of an error). If it ever does, assert. + ASSERT(size == 0 || size >= m_encodedSize); + + int delta = size - m_encodedSize; + + // The object must now be moved to a different queue, since its size has been changed. + // We have to remove explicitly before updating m_encodedSize, so that we find the correct previous + // queue. + if (inCache()) + cache()->removeFromLRUList(this); + + m_encodedSize = size; + + if (inCache()) { + // Now insert into the new LRU list. + cache()->insertInLRUList(this); + + // Update the cache's size totals. + cache()->adjustSize(hasClients(), delta); + } +} + +void CachedResource::didAccessDecodedData(double timeStamp) +{ + m_lastDecodedAccessTime = timeStamp; + + if (inCache()) { + if (m_inLiveDecodedResourcesList) { + cache()->removeFromLiveDecodedResourcesList(this); + cache()->insertInLiveDecodedResourcesList(this); + } + cache()->prune(); + } +} + +void CachedResource::setResourceToRevalidate(CachedResource* resource) +{ + ASSERT(resource); + ASSERT(!m_resourceToRevalidate); + ASSERT(resource != this); + ASSERT(!resource->m_isBeingRevalidated); + ASSERT(m_handlesToRevalidate.isEmpty()); + ASSERT(resource->type() == type()); + resource->m_isBeingRevalidated = true; + m_resourceToRevalidate = resource; +} + +void CachedResource::clearResourceToRevalidate() +{ + ASSERT(m_resourceToRevalidate); + ASSERT(m_resourceToRevalidate->m_isBeingRevalidated); + m_resourceToRevalidate->m_isBeingRevalidated = false; + m_resourceToRevalidate->deleteIfPossible(); + m_handlesToRevalidate.clear(); + m_resourceToRevalidate = 0; + deleteIfPossible(); +} + +void CachedResource::switchClientsToRevalidatedResource() +{ + ASSERT(m_resourceToRevalidate); + ASSERT(!inCache()); + + HashSet<CachedResourceHandleBase*>::iterator end = m_handlesToRevalidate.end(); + for (HashSet<CachedResourceHandleBase*>::iterator it = m_handlesToRevalidate.begin(); it != end; ++it) { + CachedResourceHandleBase* handle = *it; + handle->m_resource = m_resourceToRevalidate; + m_resourceToRevalidate->registerHandle(handle); + --m_handleCount; + } + ASSERT(!m_handleCount); + m_handlesToRevalidate.clear(); + + Vector<CachedResourceClient*> clientsToMove; + HashCountedSet<CachedResourceClient*>::iterator end2 = m_clients.end(); + for (HashCountedSet<CachedResourceClient*>::iterator it = m_clients.begin(); it != end2; ++it) { + CachedResourceClient* client = it->first; + unsigned count = it->second; + while (count) { + clientsToMove.append(client); + --count; + } + } + // Equivalent of calling removeClient() for all clients + m_clients.clear(); + + unsigned moveCount = clientsToMove.size(); + for (unsigned n = 0; n < moveCount; ++n) + m_resourceToRevalidate->addClient(clientsToMove[n]); +} + +bool CachedResource::canUseCacheValidator() const +{ + return !m_loading && (!m_response.httpHeaderField("Last-Modified").isEmpty() || !m_response.httpHeaderField("ETag").isEmpty()); +} + +bool CachedResource::mustRevalidate(CachePolicy cachePolicy) const +{ + if (m_loading) + return false; + String cacheControl = m_response.httpHeaderField("Cache-Control"); + // FIXME: It would be better to tokenize the field. + if (cachePolicy == CachePolicyCache) + return !cacheControl.isEmpty() && (cacheControl.contains("no-cache", false) || (isExpired() && cacheControl.contains("must-revalidate", false))); + return isExpired() || cacheControl.contains("no-cache", false); +} + +} diff --git a/WebCore/loader/CachedResource.h b/WebCore/loader/CachedResource.h new file mode 100644 index 0000000..c56a889 --- /dev/null +++ b/WebCore/loader/CachedResource.h @@ -0,0 +1,238 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef CachedResource_h +#define CachedResource_h + +#include "CachePolicy.h" +#include "PlatformString.h" +#include "ResourceResponse.h" +#include "SharedBuffer.h" +#include <wtf/HashCountedSet.h> +#include <wtf/HashSet.h> +#include <wtf/Vector.h> +#include <time.h> + +namespace WebCore { + +class Cache; +class CachedResourceClient; +class CachedResourceHandleBase; +class DocLoader; +class Request; + +// A resource that is held in the cache. Classes who want to use this object should derive +// from CachedResourceClient, to get the function calls in case the requested data has arrived. +// This class also does the actual communication with the loader to obtain the resource from the network. +class CachedResource { + friend class Cache; + +public: + enum Type { + ImageResource, + CSSStyleSheet, + Script, + FontResource +#if ENABLE(XSLT) + , XSLStyleSheet +#endif +#if ENABLE(XBL) + , XBL +#endif + }; + + enum Status { + NotCached, // this URL is not cached + Unknown, // let cache decide what to do with it + New, // inserting new item + Pending, // only partially loaded + Cached // regular case + }; + + CachedResource(const String& url, Type); + virtual ~CachedResource(); + + virtual void load(DocLoader* docLoader) { load(docLoader, false, false, true); } + void load(DocLoader*, bool incremental, bool skipCanLoadCheck, bool sendResourceLoadCallbacks); + + virtual void setEncoding(const String&) { } + virtual String encoding() const { return String(); } + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived) = 0; + virtual void error() = 0; + + const String &url() const { return m_url; } + Type type() const { return m_type; } + + virtual void addClient(CachedResourceClient*); + void removeClient(CachedResourceClient*); + bool hasClients() const { return !m_clients.isEmpty(); } + void deleteIfPossible(); + + enum PreloadResult { + PreloadNotReferenced, + PreloadReferenced, + PreloadReferencedWhileLoading, + PreloadReferencedWhileComplete + }; + PreloadResult preloadResult() const { return m_preloadResult; } + void setRequestedFromNetworkingLayer() { m_requestedFromNetworkingLayer = true; } + + virtual void allClientsRemoved() { }; + + unsigned count() const { return m_clients.size(); } + + Status status() const { return m_status; } + + unsigned size() const { return encodedSize() + decodedSize(); } + unsigned encodedSize() const { return m_encodedSize; } + unsigned decodedSize() const { return m_decodedSize; } + + bool isLoaded() const { return !m_loading; } + void setLoading(bool b) { m_loading = b; } + + virtual bool isImage() const { return false; } + + unsigned accessCount() const { return m_accessCount; } + void increaseAccessCount() { m_accessCount++; } + + // Computes the status of an object after loading. + // Updates the expire date on the cache entry file + void finish(); + + // Called by the cache if the object has been removed from the cache + // while still being referenced. This means the object should delete itself + // if the number of clients observing it ever drops to 0. + void setInCache(bool b) { m_inCache = b; } + bool inCache() const { return m_inCache; } + + void setInLiveDecodedResourcesList(bool b) { m_inLiveDecodedResourcesList = b; } + bool inLiveDecodedResourcesList() { return m_inLiveDecodedResourcesList; } + + void setRequest(Request*); + + SharedBuffer* data() const { return m_data.get(); } + + void setResponse(const ResourceResponse&); + const ResourceResponse& response() const { return m_response; } + + bool canDelete() const { return !hasClients() && !m_request && !m_preloadCount && !m_handleCount && !m_resourceToRevalidate && !m_isBeingRevalidated; } + + bool isExpired() const; + + virtual bool schedule() const { return false; } + + // List of acceptable MIME types seperated by ",". + // A MIME type may contain a wildcard, e.g. "text/*". + String accept() const { return m_accept; } + void setAccept(const String& accept) { m_accept = accept; } + + bool errorOccurred() const { return m_errorOccurred; } + bool sendResourceLoadCallbacks() const { return m_sendResourceLoadCallbacks; } + + virtual void destroyDecodedData() {}; + + void setDocLoader(DocLoader* docLoader) { m_docLoader = docLoader; } + + bool isPreloaded() const { return m_preloadCount; } + void increasePreloadCount() { ++m_preloadCount; } + void decreasePreloadCount() { ASSERT(m_preloadCount); --m_preloadCount; } + + void registerHandle(CachedResourceHandleBase* h) { ++m_handleCount; if (m_resourceToRevalidate) m_handlesToRevalidate.add(h); } + void unregisterHandle(CachedResourceHandleBase* h) { --m_handleCount; if (m_resourceToRevalidate) m_handlesToRevalidate.remove(h); if (!m_handleCount) deleteIfPossible(); } + + bool canUseCacheValidator() const; + bool mustRevalidate(CachePolicy) const; + bool isCacheValidator() const { return m_resourceToRevalidate; } + CachedResource* resourceToRevalidate() const { return m_resourceToRevalidate; } + +protected: + void setEncodedSize(unsigned); + void setDecodedSize(unsigned); + void didAccessDecodedData(double timeStamp); + + HashCountedSet<CachedResourceClient*> m_clients; + + String m_url; + String m_accept; + Request* m_request; + + ResourceResponse m_response; + RefPtr<SharedBuffer> m_data; + + Type m_type; + Status m_status; + + bool m_errorOccurred; + +private: + // These are called by the friendly Cache only + void setResourceToRevalidate(CachedResource*); + void switchClientsToRevalidatedResource(); + void clearResourceToRevalidate(); + void setExpirationDate(time_t expirationDate) { m_expirationDate = expirationDate; } + + unsigned m_encodedSize; + unsigned m_decodedSize; + unsigned m_accessCount; + unsigned m_inLiveDecodedResourcesList; + double m_lastDecodedAccessTime; // Used as a "thrash guard" in the cache + + bool m_sendResourceLoadCallbacks; + + unsigned m_preloadCount; + PreloadResult m_preloadResult; + bool m_requestedFromNetworkingLayer; + +protected: + bool m_inCache; + bool m_loading; + bool m_expireDateChanged; +#ifndef NDEBUG + bool m_deleted; + unsigned m_lruIndex; +#endif + +private: + CachedResource* m_nextInAllResourcesList; + CachedResource* m_prevInAllResourcesList; + + CachedResource* m_nextInLiveResourcesList; + CachedResource* m_prevInLiveResourcesList; + + DocLoader* m_docLoader; // only non-0 for resources that are not in the cache + + unsigned m_handleCount; + // If this field is non-null we are using the resource as a proxy for checking whether an existing resource is still up to date + // using HTTP If-Modified-Since/If-None-Match headers. If the response is 304 all clients of this resource are moved + // to to be clients of m_resourceToRevalidate and the resource is deleted. If not, the field is zeroed and this + // resources becomes normal resource load. + CachedResource* m_resourceToRevalidate; + bool m_isBeingRevalidated; + // These handles will need to be updated to point to the m_resourceToRevalidate in case we get 304 response. + HashSet<CachedResourceHandleBase*> m_handlesToRevalidate; + + time_t m_expirationDate; +}; + +} + +#endif diff --git a/WebCore/loader/CachedResourceClient.h b/WebCore/loader/CachedResourceClient.h new file mode 100644 index 0000000..1d8d45e --- /dev/null +++ b/WebCore/loader/CachedResourceClient.h @@ -0,0 +1,82 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style sheets and html + pages from the web. It has a memory cache for these objects. +*/ + +#ifndef CachedResourceClient_h +#define CachedResourceClient_h + +#if ENABLE(XBL) +namespace XBL { + class XBLDocument; +} +#endif + +namespace WebCore { + + class CachedCSSStyleSheet; + class CachedFont; + class CachedResource; + class CachedImage; + class String; + class Image; + class IntRect; + + /** + * @internal + * + * a client who wants to load stylesheets, images or scripts from the web has to + * inherit from this class and overload one of the 3 functions + * + */ + class CachedResourceClient + { + public: + virtual ~CachedResourceClient() { } + + // Called whenever a frame of an image changes, either because we got more data from the network or + // because we are animating. + virtual void imageChanged(CachedImage*) { }; + + // Called to find out if this client wants to actually display the image. Used to tell when we + // can halt animation. Content nodes that hold image refs for example would not render the image, + // but RenderImages would (assuming they have visibility: visible and their render tree isn't hidden + // e.g., in the b/f cache or in a background tab). + virtual bool willRenderImage(CachedImage*) { return false; } + + virtual void setCSSStyleSheet(const String& /*URL*/, const String& /*charset*/, const CachedCSSStyleSheet*) { } + virtual void setXSLStyleSheet(const String& /*URL*/, const String& /*sheet*/) { } + + virtual void fontLoaded(CachedFont*) {}; + +#if ENABLE(XBL) + virtual void setXBLDocument(const String& /*URL*/, XBL::XBLDocument*) { } +#endif + + virtual void notifyFinished(CachedResource*) { } + }; + +} + +#endif diff --git a/WebCore/loader/CachedResourceClientWalker.cpp b/WebCore/loader/CachedResourceClientWalker.cpp new file mode 100644 index 0000000..970b0e0 --- /dev/null +++ b/WebCore/loader/CachedResourceClientWalker.cpp @@ -0,0 +1,55 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style sheets and html + pages from the web. It has a memory cache for these objects. +*/ + +#include "config.h" +#include "CachedResourceClientWalker.h" + +namespace WebCore { + +CachedResourceClientWalker::CachedResourceClientWalker(const HashCountedSet<CachedResourceClient*>& set) + : m_clientSet(set), m_clientVector(set.size()), m_index(0) +{ + typedef HashCountedSet<CachedResourceClient*>::const_iterator Iterator; + Iterator end = set.end(); + size_t clientIndex = 0; + for (Iterator current = set.begin(); current != end; ++current) + m_clientVector[clientIndex++] = current->first; +} + +CachedResourceClient* CachedResourceClientWalker::next() +{ + size_t size = m_clientVector.size(); + while (m_index < size) { + CachedResourceClient* next = m_clientVector[m_index++]; + if (m_clientSet.contains(next)) + return next; + } + + return 0; +} + +} diff --git a/WebCore/loader/CachedResourceClientWalker.h b/WebCore/loader/CachedResourceClientWalker.h new file mode 100644 index 0000000..0bf98e4 --- /dev/null +++ b/WebCore/loader/CachedResourceClientWalker.h @@ -0,0 +1,51 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style sheets and html + pages from the web. It has a memory cache for these objects. +*/ + +#ifndef CachedResourceClientWalker_h +#define CachedResourceClientWalker_h + +#include <wtf/HashCountedSet.h> +#include <wtf/Vector.h> + +namespace WebCore { + + class CachedResourceClient; + + // Call this "walker" instead of iterator so people won't expect Qt or STL-style iterator interface. + // Just keep calling next() on this. It's safe from deletions of items. + class CachedResourceClientWalker { + public: + CachedResourceClientWalker(const HashCountedSet<CachedResourceClient*>&); + CachedResourceClient* next(); + private: + const HashCountedSet<CachedResourceClient*>& m_clientSet; + Vector<CachedResourceClient*> m_clientVector; + size_t m_index; + }; + +} + +#endif diff --git a/WebCore/loader/CachedResourceHandle.cpp b/WebCore/loader/CachedResourceHandle.cpp new file mode 100644 index 0000000..871292c --- /dev/null +++ b/WebCore/loader/CachedResourceHandle.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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. + */ + +#include "config.h" +#include "CachedResourceHandle.h" + +namespace WebCore { + +void CachedResourceHandleBase::setResource(CachedResource* resource) +{ + if (resource == m_resource) + return; + if (m_resource) + m_resource->unregisterHandle(this); + m_resource = resource; + if (m_resource) + m_resource->registerHandle(this); +} + +} diff --git a/WebCore/loader/CachedResourceHandle.h b/WebCore/loader/CachedResourceHandle.h new file mode 100644 index 0000000..13c03c7 --- /dev/null +++ b/WebCore/loader/CachedResourceHandle.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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 CachedResourceHandle_h +#define CachedResourceHandle_h + +#include "CachedResource.h" + +namespace WebCore { + + class CachedResourceHandleBase { + public: + ~CachedResourceHandleBase() { if (m_resource) m_resource->unregisterHandle(this); } + CachedResource* get() const { return m_resource; } + + bool operator!() const { return !m_resource; } + + // This conversion operator allows implicit conversion to bool but not to other integer types. + typedef CachedResource* CachedResourceHandleBase::*UnspecifiedBoolType; + operator UnspecifiedBoolType() const { return m_resource ? &CachedResourceHandleBase::m_resource : 0; } + + protected: + CachedResourceHandleBase() : m_resource(0) {} + CachedResourceHandleBase(CachedResource* res) { m_resource = res; if (m_resource) m_resource->registerHandle(this); } + CachedResourceHandleBase(const CachedResourceHandleBase& o) : m_resource(o.m_resource) { if (m_resource) m_resource->registerHandle(this); } + + void setResource(CachedResource*); + + private: + CachedResourceHandleBase& operator=(const CachedResourceHandleBase&) { return *this; } + + friend class CachedResource; + + CachedResource* m_resource; + }; + + template <class R> class CachedResourceHandle : public CachedResourceHandleBase { + public: + CachedResourceHandle() { } + CachedResourceHandle(R* res) : CachedResourceHandleBase(res) { } + CachedResourceHandle(const CachedResourceHandle<R>& o) : CachedResourceHandleBase(o) { } + + R* get() const { return reinterpret_cast<R*>(CachedResourceHandleBase::get()); } + R* operator->() const { return get(); } + + CachedResourceHandle& operator=(R* res) { setResource(res); return *this; } + CachedResourceHandle& operator=(const CachedResourceHandle& o) { setResource(o.get()); return *this; } + bool operator==(const CachedResourceHandleBase& o) const { return get() == o.get(); } + bool operator!=(const CachedResourceHandleBase& o) const { return get() != o.get(); } + }; + + template <class R, class RR> bool operator==(const CachedResourceHandle<R>& h, const RR* res) + { + return h.get() == res; + } + template <class R, class RR> bool operator==(const RR* res, const CachedResourceHandle<R>& h) + { + return h.get() == res; + } + template <class R, class RR> bool operator!=(const CachedResourceHandle<R>& h, const RR* res) + { + return h.get() != res; + } + template <class R, class RR> bool operator!=(const RR* res, const CachedResourceHandle<R>& h) + { + return h.get() != res; + } +} + +#endif diff --git a/WebCore/loader/CachedScript.cpp b/WebCore/loader/CachedScript.cpp new file mode 100644 index 0000000..c8caea8 --- /dev/null +++ b/WebCore/loader/CachedScript.cpp @@ -0,0 +1,107 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style sheets and html + pages from the web. It has a memory cache for these objects. +*/ + +#include "config.h" +#include "CachedScript.h" + +#include "CachedResourceClient.h" +#include "CachedResourceClientWalker.h" +#include <wtf/Vector.h> + +namespace WebCore { + +CachedScript::CachedScript(const String& url, const String& charset) + : CachedResource(url, Script) + , m_encoding(charset) +{ + // It's javascript we want. + // But some websites think their scripts are <some wrong mimetype here> + // and refuse to serve them if we only accept application/x-javascript. + setAccept("*/*"); + if (!m_encoding.isValid()) + m_encoding = Latin1Encoding(); +} + +CachedScript::~CachedScript() +{ +} + +void CachedScript::addClient(CachedResourceClient* c) +{ + CachedResource::addClient(c); + if (!m_loading) + c->notifyFinished(this); +} + +void CachedScript::setEncoding(const String& chs) +{ + TextEncoding encoding(chs); + if (encoding.isValid()) + m_encoding = encoding; +} + +String CachedScript::encoding() const +{ + return m_encoding.name(); +} + +void CachedScript::data(PassRefPtr<SharedBuffer> data, bool allDataReceived) +{ + if (!allDataReceived) + return; + + m_data = data; + setEncodedSize(m_data.get() ? m_data->size() : 0); + if (m_data.get()) + m_script = m_encoding.decode(m_data->data(), encodedSize()); +#ifdef ANDROID_FIX // FIXME Newer webkit calls setDecodedSize in CachedScript::script(); remove on webkit update + // report decoded size too + setDecodedSize(m_script.length() * sizeof(UChar)); +#endif + m_loading = false; + checkNotify(); +} + +void CachedScript::checkNotify() +{ + if (m_loading) + return; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient* c = w.next()) + c->notifyFinished(this); +} + +void CachedScript::error() +{ + m_loading = false; + m_errorOccurred = true; + checkNotify(); +} + +} diff --git a/WebCore/loader/CachedScript.h b/WebCore/loader/CachedScript.h new file mode 100644 index 0000000..4580cfe --- /dev/null +++ b/WebCore/loader/CachedScript.h @@ -0,0 +1,62 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style sheets and html + pages from the web. It has a memory cache for these objects. +*/ + +#ifndef CachedScript_h +#define CachedScript_h + +#include "CachedResource.h" +#include "TextEncoding.h" + +namespace WebCore { + + class DocLoader; + + class CachedScript : public CachedResource { + public: + CachedScript(const String& url, const String& charset); + virtual ~CachedScript(); + + const String& script() const { return m_script; } + + virtual void addClient(CachedResourceClient*); + + virtual void setEncoding(const String&); + virtual String encoding() const; + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(); + + virtual bool schedule() const { return false; } + + void checkNotify(); + + private: + String m_script; + TextEncoding m_encoding; + }; +} + +#endif diff --git a/WebCore/loader/CachedXBLDocument.cpp b/WebCore/loader/CachedXBLDocument.cpp new file mode 100644 index 0000000..1ef3bae --- /dev/null +++ b/WebCore/loader/CachedXBLDocument.cpp @@ -0,0 +1,112 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style sheets and html + pages from the web. It has a memory cache for these objects. +*/ + +#include "config.h" + +#if ENABLE(XBL) + +#include "CachedXBLDocument.h" + +#include "CachedResourceClientWalker.h" +#include "TextResourceDecoder.h" +#include <wtf/Vector.h> + +namespace WebCore { + +CachedXBLDocument::CachedXBLDocument(const String &url) +: CachedResource(url, XBL), m_document(0) +{ + // It's XML we want. + setAccept("text/xml, application/xml, application/xhtml+xml, text/xsl, application/rss+xml, application/atom+xml"); + + m_decoder = new TextResourceDecoder("application/xml"); +} + +CachedXBLDocument::~CachedXBLDocument() +{ + if (m_document) + m_document->deref(); +} + +void CachedXBLDocument::ref(CachedResourceClient *c) +{ + CachedResource::ref(c); + if (!m_loading) + c->setXBLDocument(m_url, m_document); +} + +void CachedXBLDocument::setEncoding(const String& chs) +{ + m_decoder->setEncoding(chs, TextResourceDecoder::EncodingFromHTTPHeader); +} + +String CachedXBLDocument::encoding() const +{ + return m_decoder->encoding().name(); +} + +void CachedXBLDocument::data(Vector<char>& data, bool ) +{ + if (!allDataReceived) + return; + + ASSERT(!m_document); + + m_document = new XBL::XBLDocument(); + m_document->ref(); + m_document->open(); + + m_document->write(m_decoder->decode(data.data(), data.size())); + setSize(data.size()); + + m_document->finishParsing(); + m_document->close(); + m_loading = false; + checkNotify(); +} + +void CachedXBLDocument::checkNotify() +{ + if (m_loading) + return; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient *c = w.next()) + c->setXBLDocument(m_url, m_document); +} + +void CachedXBLDocument::error() +{ + m_loading = false; + m_errorOccurred = true; + checkNotify(); +} + +} + +#endif diff --git a/WebCore/loader/CachedXBLDocument.h b/WebCore/loader/CachedXBLDocument.h new file mode 100644 index 0000000..a66a0cb --- /dev/null +++ b/WebCore/loader/CachedXBLDocument.h @@ -0,0 +1,69 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style sheets and html + pages from the web. It has a memory cache for these objects. +*/ + +#ifndef CachedXBLDocument_h +#define CachedXBLDocument_h + +#include "CachedResource.h" +#include <wtf/Vector.h> + +namespace WebCore { + class CachedResource; + class Request; + class DocLoader; + class TextResourceDecoder; + class CachedResourceClient; + +#if ENABLE(XBL) + class CachedXBLDocument : public CachedResource { + public: + CachedXBLDocument(const String& url); + virtual ~CachedXBLDocument(); + + XBL::XBLDocument* document() const { return m_document; } + + virtual void addClient(CachedResourceClient*); + + virtual void setEncoding(const String&); + virtual String encoding() const; + virtual void data(Vector<char>&, bool allDataReceived); + virtual void error(); + + virtual bool schedule() const { return true; } + + void checkNotify(); + + protected: + XBL::XBLDocument* m_document; + RefPtr<TextResourceDecoder> m_decoder; + }; + +#endif + +} + +#endif diff --git a/WebCore/loader/CachedXSLStyleSheet.cpp b/WebCore/loader/CachedXSLStyleSheet.cpp new file mode 100644 index 0000000..009b2af --- /dev/null +++ b/WebCore/loader/CachedXSLStyleSheet.cpp @@ -0,0 +1,103 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style sheets and html + pages from the web. It has a memory cache for these objects. +*/ + +#include "config.h" +#include "CachedXSLStyleSheet.h" + +#include "CachedResourceClient.h" +#include "CachedResourceClientWalker.h" +#include "TextResourceDecoder.h" +#include <wtf/Vector.h> + +namespace WebCore { + +#if ENABLE(XSLT) + +CachedXSLStyleSheet::CachedXSLStyleSheet(const String &url) + : CachedResource(url, XSLStyleSheet) + , m_decoder(TextResourceDecoder::create("text/xsl")) +{ + // It's XML we want. + // FIXME: This should accept more general xml formats */*+xml, image/svg+xml for example. + setAccept("text/xml, application/xml, application/xhtml+xml, text/xsl, application/rss+xml, application/atom+xml"); +} + +void CachedXSLStyleSheet::addClient(CachedResourceClient *c) +{ + CachedResource::addClient(c); + + if (!m_loading) + c->setXSLStyleSheet(m_url, m_sheet); +} + +void CachedXSLStyleSheet::setEncoding(const String& chs) +{ + m_decoder->setEncoding(chs, TextResourceDecoder::EncodingFromHTTPHeader); +} + +String CachedXSLStyleSheet::encoding() const +{ + return m_decoder->encoding().name(); +} + +void CachedXSLStyleSheet::data(PassRefPtr<SharedBuffer> data, bool allDataReceived) +{ + if (!allDataReceived) + return; + + m_data = data; + setEncodedSize(m_data.get() ? m_data->size() : 0); + if (m_data.get()) { + m_sheet = String(m_decoder->decode(m_data->data(), encodedSize())); + m_sheet += m_decoder->flush(); + } + m_loading = false; + checkNotify(); +} + +void CachedXSLStyleSheet::checkNotify() +{ + if (m_loading) + return; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient *c = w.next()) + c->setXSLStyleSheet(m_url, m_sheet); +} + + +void CachedXSLStyleSheet::error() +{ + m_loading = false; + m_errorOccurred = true; + checkNotify(); +} + +#endif + +} diff --git a/WebCore/loader/CachedXSLStyleSheet.h b/WebCore/loader/CachedXSLStyleSheet.h new file mode 100644 index 0000000..c5326fa --- /dev/null +++ b/WebCore/loader/CachedXSLStyleSheet.h @@ -0,0 +1,66 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style sheets and html + pages from the web. It has a memory cache for these objects. +*/ + +#ifndef CachedXSLStyleSheet_h +#define CachedXSLStyleSheet_h + +#include "CachedResource.h" +#include <wtf/Vector.h> + +namespace WebCore { + + class DocLoader; + class TextResourceDecoder; + +#if ENABLE(XSLT) + class CachedXSLStyleSheet : public CachedResource { + public: + CachedXSLStyleSheet(const String& url); + + const String& sheet() const { return m_sheet; } + + virtual void addClient(CachedResourceClient*); + + virtual void setEncoding(const String&); + virtual String encoding() const; + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(); + + virtual bool schedule() const { return true; } + + void checkNotify(); + + protected: + String m_sheet; + RefPtr<TextResourceDecoder> m_decoder; + }; + +#endif + +} + +#endif diff --git a/WebCore/loader/DocLoader.cpp b/WebCore/loader/DocLoader.cpp new file mode 100644 index 0000000..4dbc6a0 --- /dev/null +++ b/WebCore/loader/DocLoader.cpp @@ -0,0 +1,468 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2004, 2005, 2006, 2008 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style sheets and html + pages from the web. It has a memory cache for these objects. +*/ + +#include "config.h" +#include "DocLoader.h" + +#include "Cache.h" +#include "CachedCSSStyleSheet.h" +#include "CachedFont.h" +#include "CachedImage.h" +#include "CachedScript.h" +#include "CachedXSLStyleSheet.h" +#include "Console.h" +#include "CString.h" +#include "Document.h" +#include "DOMWindow.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "loader.h" +#include "SecurityOrigin.h" +#include "Settings.h" + +#define PRELOAD_DEBUG 0 + +namespace WebCore { + +DocLoader::DocLoader(Document* doc) + : m_cache(cache()) + , m_cachePolicy(CachePolicyVerify) + , m_doc(doc) + , m_requestCount(0) +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + , m_blockNetworkImage(false) +#endif + , m_autoLoadImages(true) + , m_loadInProgress(false) + , m_allowStaleResources(false) +{ + m_cache->addDocLoader(this); +} + +DocLoader::~DocLoader() +{ + clearPreloads(); + HashMap<String, CachedResource*>::iterator end = m_docResources.end(); + for (HashMap<String, CachedResource*>::iterator it = m_docResources.begin(); it != end; ++it) + it->second->setDocLoader(0); + m_cache->removeDocLoader(this); +} + +Frame* DocLoader::frame() const +{ + return m_doc->frame(); +} + +void DocLoader::checkForReload(const KURL& fullURL) +{ + if (m_allowStaleResources) + return; // Don't reload resources while pasting + + if (fullURL.isEmpty()) + return; + + if (m_cachePolicy == CachePolicyVerify || m_cachePolicy == CachePolicyCache) { + if (!m_reloadedURLs.contains(fullURL.string())) { + CachedResource* existing = cache()->resourceForURL(fullURL.string()); + if (existing && !existing->isPreloaded() && existing->mustRevalidate(m_cachePolicy)) { + cache()->revalidateResource(existing, this); + m_reloadedURLs.add(fullURL.string()); + } + } + } else if ((m_cachePolicy == CachePolicyReload) || (m_cachePolicy == CachePolicyRefresh)) { + if (!m_reloadedURLs.contains(fullURL.string())) { + CachedResource* existing = cache()->resourceForURL(fullURL.string()); + if (existing && !existing->isPreloaded()) { + // FIXME: Use revalidateResource() to implement HTTP 1.1 "Specific end-to-end revalidation" for regular reloading + cache()->remove(existing); + m_reloadedURLs.add(fullURL.string()); + } + } + } +} + +CachedImage* DocLoader::requestImage(const String& url) +{ + CachedImage* resource = static_cast<CachedImage*>(requestResource(CachedResource::ImageResource, url, String())); + if (autoLoadImages() && resource && resource->stillNeedsLoad()) { +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + if (shouldBlockNetworkImage(url)) { + return resource; + } +#endif + resource->setLoading(true); + cache()->loader()->load(this, resource, true); + } + return resource; +} + +CachedFont* DocLoader::requestFont(const String& url) +{ + return static_cast<CachedFont*>(requestResource(CachedResource::FontResource, url, String())); +} + +CachedCSSStyleSheet* DocLoader::requestCSSStyleSheet(const String& url, const String& charset) +{ + return static_cast<CachedCSSStyleSheet*>(requestResource(CachedResource::CSSStyleSheet, url, charset)); +} + +CachedCSSStyleSheet* DocLoader::requestUserCSSStyleSheet(const String& url, const String& charset) +{ + return cache()->requestUserCSSStyleSheet(this, url, charset); +} + +CachedScript* DocLoader::requestScript(const String& url, const String& charset) +{ + return static_cast<CachedScript*>(requestResource(CachedResource::Script, url, charset)); +} + +#if ENABLE(XSLT) +CachedXSLStyleSheet* DocLoader::requestXSLStyleSheet(const String& url) +{ + return static_cast<CachedXSLStyleSheet*>(requestResource(CachedResource::XSLStyleSheet, url, String())); +} +#endif + +#if ENABLE(XBL) +CachedXBLDocument* DocLoader::requestXBLDocument(const String& url) +{ + return static_cast<CachedXSLStyleSheet*>(requestResource(CachedResource::XBL, url, String())); +} +#endif + +bool DocLoader::canRequest(CachedResource::Type type, const KURL& url) +{ + // Some types of resources can be loaded only from the same origin. Other + // types of resources, like Images, Scripts, and CSS, can be loaded from + // any URL. + switch (type) { + case CachedResource::ImageResource: + case CachedResource::CSSStyleSheet: + case CachedResource::Script: + case CachedResource::FontResource: + // These types of resources can be loaded from any origin. + // FIXME: Are we sure about CachedResource::FontResource? + break; +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: +#endif +#if ENABLE(XBL) + case CachedResource::XBL: +#endif +#if ENABLE(XSLT) || ENABLE(XBL) + if (!m_doc->securityOrigin()->canRequest(url)) { + printAccessDeniedMessage(url); + return false; + } + break; +#endif + default: + ASSERT_NOT_REACHED(); + break; + } + return true; +} + +CachedResource* DocLoader::requestResource(CachedResource::Type type, const String& url, const String& charset, bool isPreload) +{ + KURL fullURL = m_doc->completeURL(url); + + if (!canRequest(type, fullURL)) + return 0; + + if (cache()->disabled()) { + HashMap<String, CachedResource*>::iterator it = m_docResources.find(fullURL.string()); + + if (it != m_docResources.end()) { + it->second->setDocLoader(0); + m_docResources.remove(it); + } + } + + if (frame() && frame()->loader()->isReloading()) + setCachePolicy(CachePolicyReload); + + checkForReload(fullURL); + CachedResource* resource = cache()->requestResource(this, type, fullURL, charset, isPreload); + if (resource) { + // Check final URL of resource to catch redirects. + // See <https://bugs.webkit.org/show_bug.cgi?id=21963>. + if (!canRequest(type, KURL(resource->url()))) + return 0; + + m_docResources.set(resource->url(), resource); + checkCacheObjectStatus(resource); + } + return resource; +} + +void DocLoader::printAccessDeniedMessage(const KURL& url) const +{ + if (url.isNull()) + return; + + if (!frame()) + return; + + Settings* settings = frame()->settings(); + if (!settings || settings->privateBrowsingEnabled()) + return; + + String message = m_doc->url().isNull() ? + String::format("Unsafe attempt to load URL %s.", + url.string().utf8().data()) : + String::format("Unsafe attempt to load URL %s from frame with URL %s. " + "Domains, protocols and ports must match.\n", + url.string().utf8().data(), + m_doc->url().string().utf8().data()); + + // FIXME: provide a real line number and source URL. + frame()->domWindow()->console()->addMessage(OtherMessageSource, ErrorMessageLevel, message, 1, String()); +} + +void DocLoader::setAutoLoadImages(bool enable) +{ + if (enable == m_autoLoadImages) + return; + + m_autoLoadImages = enable; + + if (!m_autoLoadImages) + return; + + HashMap<String, CachedResource*>::iterator end = m_docResources.end(); + for (HashMap<String, CachedResource*>::iterator it = m_docResources.begin(); it != end; ++it) { + CachedResource* resource = it->second; + if (resource->type() == CachedResource::ImageResource) { + CachedImage* image = const_cast<CachedImage*>(static_cast<const CachedImage*>(resource)); +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + if (shouldBlockNetworkImage(image->url())) + continue; +#endif + + if (image->stillNeedsLoad()) + cache()->loader()->load(this, image, true); + } + } +} + +#ifdef ANDROID_BLOCK_NETWORK_IMAGE +bool DocLoader::shouldBlockNetworkImage(const String& url) const +{ + if (!m_blockNetworkImage) + return false; + + KURL kurl(url); + if (kurl.protocolIs("http") || kurl.protocolIs("https")) + return true; + + return false; +} + +void DocLoader::setBlockNetworkImage(bool block) +{ + if (block == m_blockNetworkImage) + return; + + m_blockNetworkImage = block; + + if (!m_autoLoadImages || m_blockNetworkImage) + return; + + HashMap<String, CachedResource*>::iterator end = m_docResources.end(); + for (HashMap<String, CachedResource*>::iterator it = m_docResources.begin(); it != end; ++it) { + CachedResource* resource = it->second; + if (resource->type() == CachedResource::ImageResource) { + CachedImage* image = const_cast<CachedImage*>(static_cast<const CachedImage*>(resource)); + if (image->stillNeedsLoad()) + cache()->loader()->load(this, image, true); + } + } +} +#endif + +void DocLoader::setCachePolicy(CachePolicy cachePolicy) +{ + m_cachePolicy = cachePolicy; +} + +void DocLoader::removeCachedResource(CachedResource* resource) const +{ + m_docResources.remove(resource->url()); +} + +void DocLoader::setLoadInProgress(bool load) +{ + m_loadInProgress = load; + if (!load && frame()) + frame()->loader()->loadDone(); +} + +void DocLoader::checkCacheObjectStatus(CachedResource* resource) +{ + // Return from the function for objects that we didn't load from the cache or if we don't have a frame. + if (!resource || !frame()) + return; + + switch (resource->status()) { + case CachedResource::Cached: + break; + case CachedResource::NotCached: + case CachedResource::Unknown: + case CachedResource::New: + case CachedResource::Pending: + return; + } + + // FIXME: If the WebKit client changes or cancels the request, WebCore does not respect this and continues the load. + frame()->loader()->loadedResourceFromMemoryCache(resource); +} + +void DocLoader::incrementRequestCount() +{ + ++m_requestCount; +} + +void DocLoader::decrementRequestCount() +{ + --m_requestCount; + ASSERT(m_requestCount > -1); +} + +int DocLoader::requestCount() +{ + if (loadInProgress()) + return m_requestCount + 1; + return m_requestCount; +} + +void DocLoader::preload(CachedResource::Type type, const String& url, const String& charset, bool referencedFromBody) +{ + bool hasRendering = m_doc->body() && m_doc->body()->renderer(); + if (!hasRendering && (referencedFromBody || type == CachedResource::ImageResource)) { + // Don't preload images or body resources before we have something to draw. This prevents + // preloads from body delaying first display when bandwidth is limited. + PendingPreload pendingPreload = { type, url, charset }; + m_pendingPreloads.append(pendingPreload); + return; + } + requestPreload(type, url, charset); +} + +void DocLoader::checkForPendingPreloads() +{ + unsigned count = m_pendingPreloads.size(); + if (!count || !m_doc->body() || !m_doc->body()->renderer()) + return; + for (unsigned i = 0; i < count; ++i) { + PendingPreload& preload = m_pendingPreloads[i]; + requestPreload(preload.m_type, preload.m_url, preload.m_charset); + } + m_pendingPreloads.clear(); +} + +void DocLoader::requestPreload(CachedResource::Type type, const String& url, const String& charset) +{ + String encoding; + if (type == CachedResource::Script || type == CachedResource::CSSStyleSheet) + encoding = charset.isEmpty() ? m_doc->frame()->loader()->encoding() : charset; + + CachedResource* resource = requestResource(type, url, encoding, true); + if (!resource || m_preloads.contains(resource)) + return; + resource->increasePreloadCount(); + m_preloads.add(resource); +#if PRELOAD_DEBUG + printf("PRELOADING %s\n", resource->url().latin1().data()); +#endif +} + +void DocLoader::clearPreloads() +{ +#if PRELOAD_DEBUG + printPreloadStats(); +#endif + ListHashSet<CachedResource*>::iterator end = m_preloads.end(); + for (ListHashSet<CachedResource*>::iterator it = m_preloads.begin(); it != end; ++it) { + CachedResource* res = *it; + res->decreasePreloadCount(); + if (res->canDelete() && !res->inCache()) + delete res; + else if (res->preloadResult() == CachedResource::PreloadNotReferenced) + cache()->remove(res); + } + m_preloads.clear(); +} + +#if PRELOAD_DEBUG +void DocLoader::printPreloadStats() +{ + unsigned scripts = 0; + unsigned scriptMisses = 0; + unsigned stylesheets = 0; + unsigned stylesheetMisses = 0; + unsigned images = 0; + unsigned imageMisses = 0; + ListHashSet<CachedResource*>::iterator end = m_preloads.end(); + for (ListHashSet<CachedResource*>::iterator it = m_preloads.begin(); it != end; ++it) { + CachedResource* res = *it; + if (res->preloadResult() == CachedResource::PreloadNotReferenced) + printf("!! UNREFERENCED PRELOAD %s\n", res->url().latin1().data()); + else if (res->preloadResult() == CachedResource::PreloadReferencedWhileComplete) + printf("HIT COMPLETE PRELOAD %s\n", res->url().latin1().data()); + else if (res->preloadResult() == CachedResource::PreloadReferencedWhileLoading) + printf("HIT LOADING PRELOAD %s\n", res->url().latin1().data()); + + if (res->type() == CachedResource::Script) { + scripts++; + if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) + scriptMisses++; + } else if (res->type() == CachedResource::CSSStyleSheet) { + stylesheets++; + if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) + stylesheetMisses++; + } else { + images++; + if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) + imageMisses++; + } + + if (res->errorOccurred()) + cache()->remove(res); + + res->decreasePreloadCount(); + } + m_preloads.clear(); + + if (scripts) + printf("SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts); + if (stylesheets) + printf("STYLESHEETS: %d (%d hits, hit rate %d%%)\n", stylesheets, stylesheets - stylesheetMisses, (stylesheets - stylesheetMisses) * 100 / stylesheets); + if (images) + printf("IMAGES: %d (%d hits, hit rate %d%%)\n", images, images - imageMisses, (images - imageMisses) * 100 / images); +} +#endif + +} diff --git a/WebCore/loader/DocLoader.h b/WebCore/loader/DocLoader.h new file mode 100644 index 0000000..407d85a --- /dev/null +++ b/WebCore/loader/DocLoader.h @@ -0,0 +1,146 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + This class provides all functionality needed for loading images, style sheets and html + pages from the web. It has a memory cache for these objects. +*/ + +#ifndef DocLoader_h +#define DocLoader_h + +#include "CachedResource.h" +#include "CachePolicy.h" +#include "StringHash.h" +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/ListHashSet.h> + +namespace WebCore { + +class CachedCSSStyleSheet; +class CachedFont; +class CachedImage; +class CachedScript; +class CachedXSLStyleSheet; +class Document; +class Frame; +class ImageLoader; +class KURL; + +// The DocLoader manages the loading of scripts/images/stylesheets for a single document. +class DocLoader +{ +friend class Cache; +friend class ImageLoader; + +public: + DocLoader(Document*); + ~DocLoader(); + + CachedImage* requestImage(const String& url); + CachedCSSStyleSheet* requestCSSStyleSheet(const String& url, const String& charset); + CachedCSSStyleSheet* requestUserCSSStyleSheet(const String& url, const String& charset); + CachedScript* requestScript(const String& url, const String& charset); + CachedFont* requestFont(const String& url); + +#if ENABLE(XSLT) + CachedXSLStyleSheet* requestXSLStyleSheet(const String& url); +#endif +#if ENABLE(XBL) + CachedXBLDocument* requestXBLDocument(const String &url); +#endif + + // Logs an access denied message to the console for the specified URL. + void printAccessDeniedMessage(const KURL& url) const; + + CachedResource* cachedResource(const String& url) const { return m_docResources.get(url); } + const HashMap<String, CachedResource*>& allCachedResources() const { return m_docResources; } + + bool autoLoadImages() const { return m_autoLoadImages; } + void setAutoLoadImages(bool); + +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + bool blockNetworkImage() const { return m_blockNetworkImage; } + void setBlockNetworkImage(bool); + bool shouldBlockNetworkImage(const String& url) const; +#endif + + CachePolicy cachePolicy() const { return m_cachePolicy; } + void setCachePolicy(CachePolicy); + + Frame* frame() const; // Can be NULL + Document* doc() const { return m_doc; } + + void removeCachedResource(CachedResource*) const; + + void setLoadInProgress(bool); + bool loadInProgress() const { return m_loadInProgress; } + + void setAllowStaleResources(bool allowStaleResources) { m_allowStaleResources = allowStaleResources; } + +#if USE(LOW_BANDWIDTH_DISPLAY) + void replaceDocument(Document* doc) { m_doc = doc; } +#endif + + void incrementRequestCount(); + void decrementRequestCount(); + int requestCount(); + + void clearPreloads(); + void preload(CachedResource::Type, const String& url, const String& charset, bool referencedFromBody); + void checkForPendingPreloads(); + void printPreloadStats(); + +private: + CachedResource* requestResource(CachedResource::Type, const String& url, const String& charset, bool isPreload = false); + void requestPreload(CachedResource::Type, const String& url, const String& charset); + + void checkForReload(const KURL&); + void checkCacheObjectStatus(CachedResource*); + bool canRequest(CachedResource::Type, const KURL&); + + Cache* m_cache; + HashSet<String> m_reloadedURLs; + mutable HashMap<String, CachedResource*> m_docResources; + CachePolicy m_cachePolicy; + Document* m_doc; + + int m_requestCount; + + ListHashSet<CachedResource*> m_preloads; + struct PendingPreload { + CachedResource::Type m_type; + String m_url; + String m_charset; + }; + Vector<PendingPreload> m_pendingPreloads; + + //29 bits left +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + bool m_blockNetworkImage : 1; +#endif + bool m_autoLoadImages : 1; + bool m_loadInProgress : 1; + bool m_allowStaleResources : 1; +}; + +} + +#endif diff --git a/WebCore/loader/DocumentLoader.cpp b/WebCore/loader/DocumentLoader.cpp new file mode 100644 index 0000000..d9d99ea --- /dev/null +++ b/WebCore/loader/DocumentLoader.cpp @@ -0,0 +1,924 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "DocumentLoader.h" + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) +#include "ApplicationCache.h" +#include "ApplicationCacheGroup.h" +#include "ApplicationCacheResource.h" +#endif +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size +#include "ArchiveFactory.h" +#include "ArchiveResourceCollection.h" +#else +#include "SubstituteResource.h" +#endif +#include "CachedPage.h" +#include "DocLoader.h" +#include "Document.h" +#include "Event.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameTree.h" +#include "HistoryItem.h" +#include "Logging.h" +#include "MainResourceLoader.h" +#include "Page.h" +#include "PlatformString.h" +#include "Settings.h" +#include "SharedBuffer.h" +#include "StringBuffer.h" +#include "XMLTokenizer.h" + +#include <wtf/Assertions.h> +#include <wtf/unicode/Unicode.h> + +namespace WebCore { + +/* + * Performs four operations: + * 1. Convert backslashes to currency symbols + * 2. Convert control characters to spaces + * 3. Trim leading and trailing spaces + * 4. Collapse internal whitespace. + */ +static inline String canonicalizedTitle(const String& title, Frame* frame) +{ + ASSERT(!title.isEmpty()); + + const UChar* characters = title.characters(); + unsigned length = title.length(); + unsigned i; + + StringBuffer buffer(length); + unsigned builderIndex = 0; + + // Skip leading spaces and leading characters that would convert to spaces + for (i = 0; i < length; ++i) { + UChar c = characters[i]; + if (!(c <= 0x20 || c == 0x7F)) + break; + } + + if (i == length) + return ""; + + // Replace control characters with spaces, and backslashes with currency symbols, and collapse whitespace. + bool previousCharWasWS = false; + for (; i < length; ++i) { + UChar c = characters[i]; + if (c <= 0x20 || c == 0x7F || (WTF::Unicode::category(c) & (WTF::Unicode::Separator_Line | WTF::Unicode::Separator_Paragraph))) { + if (previousCharWasWS) + continue; + buffer[builderIndex++] = ' '; + previousCharWasWS = true; + } else if (c == '\\') { + buffer[builderIndex++] = frame->backslashAsCurrencySymbol(); + previousCharWasWS = false; + } else { + buffer[builderIndex++] = c; + previousCharWasWS = false; + } + } + + // Strip trailing spaces + while (builderIndex > 0) { + --builderIndex; + if (buffer[builderIndex] != ' ') + break; + } + + if (!builderIndex && buffer[builderIndex] == ' ') + return ""; + + buffer.shrink(builderIndex + 1); + return String::adopt(buffer); +} + +static void cancelAll(const ResourceLoaderSet& loaders) +{ + const ResourceLoaderSet copy = loaders; + ResourceLoaderSet::const_iterator end = copy.end(); + for (ResourceLoaderSet::const_iterator it = copy.begin(); it != end; ++it) + (*it)->cancel(); +} + +static void setAllDefersLoading(const ResourceLoaderSet& loaders, bool defers) +{ + const ResourceLoaderSet copy = loaders; + ResourceLoaderSet::const_iterator end = copy.end(); + for (ResourceLoaderSet::const_iterator it = copy.begin(); it != end; ++it) + (*it)->setDefersLoading(defers); +} + +DocumentLoader::DocumentLoader(const ResourceRequest& req, const SubstituteData& substituteData) + : m_deferMainResourceDataLoad(true) + , m_frame(0) + , m_originalRequest(req) + , m_substituteData(substituteData) + , m_originalRequestCopy(req) + , m_request(req) + , m_committed(false) + , m_isStopping(false) + , m_loading(false) + , m_gotFirstByte(false) + , m_primaryLoadComplete(false) + , m_isClientRedirect(false) + , m_loadingFromCachedPage(false) + , m_stopRecordingResponses(false) + , m_substituteResourceDeliveryTimer(this, &DocumentLoader::substituteResourceDeliveryTimerFired) +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + , m_candidateApplicationCacheGroup(0) +#endif +{ +} + +FrameLoader* DocumentLoader::frameLoader() const +{ + if (!m_frame) + return 0; + return m_frame->loader(); +} + +DocumentLoader::~DocumentLoader() +{ + ASSERT(!m_frame || frameLoader()->activeDocumentLoader() != this || !frameLoader()->isLoading()); + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + if (m_applicationCache) + m_applicationCache->group()->documentLoaderDestroyed(this); + else if (m_candidateApplicationCacheGroup) + m_candidateApplicationCacheGroup->documentLoaderDestroyed(this); +#endif +} + +PassRefPtr<SharedBuffer> DocumentLoader::mainResourceData() const +{ + if (m_mainResourceData) + return m_mainResourceData; + if (m_mainResourceLoader) + return m_mainResourceLoader->resourceData(); + return 0; +} + +const ResourceRequest& DocumentLoader::originalRequest() const +{ + return m_originalRequest; +} + +const ResourceRequest& DocumentLoader::originalRequestCopy() const +{ + return m_originalRequestCopy; +} + +const ResourceRequest& DocumentLoader::request() const +{ + return m_request; +} + +ResourceRequest& DocumentLoader::request() +{ + return m_request; +} + +const KURL& DocumentLoader::url() const +{ + return request().url(); +} + +void DocumentLoader::replaceRequestURLForAnchorScroll(const KURL& url) +{ + m_originalRequestCopy.setURL(url); + m_request.setURL(url); +} + +void DocumentLoader::setRequest(const ResourceRequest& req) +{ + // Replacing an unreachable URL with alternate content looks like a server-side + // redirect at this point, but we can replace a committed dataSource. + bool handlingUnreachableURL = false; + + handlingUnreachableURL = m_substituteData.isValid() && !m_substituteData.failingURL().isEmpty(); + + if (handlingUnreachableURL) + m_committed = false; + + // We should never be getting a redirect callback after the data + // source is committed, except in the unreachable URL case. It + // would be a WebFoundation bug if it sent a redirect callback after commit. + ASSERT(!m_committed); + + KURL oldURL = m_request.url(); + m_request = req; + + // Only send webView:didReceiveServerRedirectForProvisionalLoadForFrame: if URL changed. + // Also, don't send it when replacing unreachable URLs with alternate content. + if (!handlingUnreachableURL && oldURL != req.url()) + frameLoader()->didReceiveServerRedirectForProvisionalLoadForFrame(); +} + +void DocumentLoader::setMainDocumentError(const ResourceError& error) +{ + m_mainDocumentError = error; + frameLoader()->setMainDocumentError(this, error); + } + +void DocumentLoader::clearErrors() +{ + m_mainDocumentError = ResourceError(); +} + +void DocumentLoader::mainReceivedError(const ResourceError& error, bool isComplete) +{ +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + ApplicationCacheGroup* group = m_candidateApplicationCacheGroup; + if (!group && m_applicationCache && !mainResourceApplicationCache()) + group = m_applicationCache->group(); + + if (group) + group->failedLoadingMainResource(this); +#endif + + if (!frameLoader()) + return; + setMainDocumentError(error); + if (isComplete) + frameLoader()->mainReceivedCompleteError(this, error); +} + +// Cancels the data source's pending loads. Conceptually, a data source only loads +// one document at a time, but one document may have many related resources. +// stopLoading will stop all loads initiated by the data source, +// but not loads initiated by child frames' data sources -- that's the WebFrame's job. +void DocumentLoader::stopLoading() +{ + // In some rare cases, calling FrameLoader::stopLoading could set m_loading to false. + // (This can happen when there's a single XMLHttpRequest currently loading and stopLoading causes it + // to stop loading. Because of this, we need to save it so we don't return early. + bool loading = m_loading; + + if (m_committed) { + // Attempt to stop the frame if the document loader is loading, or if it is done loading but + // still parsing. Failure to do so can cause a world leak. + Document* doc = m_frame->document(); + + if (loading || (doc && doc->parsing())) + m_frame->loader()->stopLoading(false); + } + + // Always cancel multipart loaders + cancelAll(m_multipartSubresourceLoaders); + + if (!loading) + return; + + RefPtr<Frame> protectFrame(m_frame); + RefPtr<DocumentLoader> protectLoader(this); + + m_isStopping = true; + + FrameLoader* frameLoader = DocumentLoader::frameLoader(); + + if (m_mainResourceLoader) + // Stop the main resource loader and let it send the cancelled message. + m_mainResourceLoader->cancel(); + else if (!m_subresourceLoaders.isEmpty()) + // The main resource loader already finished loading. Set the cancelled error on the + // document and let the subresourceLoaders send individual cancelled messages below. + setMainDocumentError(frameLoader->cancelledError(m_request)); + else + // If there are no resource loaders, we need to manufacture a cancelled message. + // (A back/forward navigation has no resource loaders because its resources are cached.) + mainReceivedError(frameLoader->cancelledError(m_request), true); + + stopLoadingSubresources(); + stopLoadingPlugIns(); + + m_isStopping = false; +} + +void DocumentLoader::setupForReplace() +{ + frameLoader()->setupForReplace(); + m_committed = false; +} + +void DocumentLoader::commitIfReady() +{ + if (m_gotFirstByte && !m_committed) { + m_committed = true; + frameLoader()->commitProvisionalLoad(0); + } +} + +void DocumentLoader::finishedLoading() +{ + m_gotFirstByte = true; + commitIfReady(); + if (FrameLoader* loader = frameLoader()) { + loader->finishedLoadingDocument(this); + loader->end(); + } +} + +void DocumentLoader::commitLoad(const char* data, int length) +{ + // Both unloading the old page and parsing the new page may execute JavaScript which destroys the datasource + // by starting a new load, so retain temporarily. + RefPtr<DocumentLoader> protect(this); + + commitIfReady(); + if (FrameLoader* frameLoader = DocumentLoader::frameLoader()) + frameLoader->committedLoad(this, data, length); +} + +bool DocumentLoader::doesProgressiveLoad(const String& MIMEType) const +{ + return !frameLoader()->isReplacing() || MIMEType == "text/html"; +} + +void DocumentLoader::receivedData(const char* data, int length) +{ + m_gotFirstByte = true; + if (doesProgressiveLoad(m_response.mimeType())) + commitLoad(data, length); +} + +void DocumentLoader::setupForReplaceByMIMEType(const String& newMIMEType) +{ + if (!m_gotFirstByte) + return; + + String oldMIMEType = m_response.mimeType(); + + if (!doesProgressiveLoad(oldMIMEType)) { + frameLoader()->revertToProvisional(this); + setupForReplace(); + RefPtr<SharedBuffer> resourceData = mainResourceData(); + commitLoad(resourceData->data(), resourceData->size()); + } + + frameLoader()->finishedLoadingDocument(this); + m_frame->loader()->end(); + + frameLoader()->setReplacing(); + m_gotFirstByte = false; + + if (doesProgressiveLoad(newMIMEType)) { + frameLoader()->revertToProvisional(this); + setupForReplace(); + } + + stopLoadingSubresources(); + stopLoadingPlugIns(); +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + clearArchiveResources(); +#endif +} + +void DocumentLoader::updateLoading() +{ + ASSERT(this == frameLoader()->activeDocumentLoader()); + setLoading(frameLoader()->isLoading()); +} + +void DocumentLoader::setFrame(Frame* frame) +{ + if (m_frame == frame) + return; + ASSERT(frame && !m_frame); + m_frame = frame; + attachToFrame(); +} + +void DocumentLoader::attachToFrame() +{ + ASSERT(m_frame); +} + +void DocumentLoader::detachFromFrame() +{ + ASSERT(m_frame); + m_frame = 0; +} + +void DocumentLoader::prepareForLoadStart() +{ + ASSERT(!m_isStopping); + setPrimaryLoadComplete(false); + ASSERT(frameLoader()); + clearErrors(); + + setLoading(true); + + frameLoader()->prepareForLoadStart(); +} + +void DocumentLoader::setPrimaryLoadComplete(bool flag) +{ + m_primaryLoadComplete = flag; + if (flag) { + if (m_mainResourceLoader) { + m_mainResourceData = m_mainResourceLoader->resourceData(); +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + m_mainResourceApplicationCache = m_mainResourceLoader->applicationCache(); +#endif + m_mainResourceLoader = 0; + } + updateLoading(); + } +} + +bool DocumentLoader::isLoadingInAPISense() const +{ + // Once a frame has loaded, we no longer need to consider subresources, + // but we still need to consider subframes. + if (frameLoader()->state() != FrameStateComplete) { + if (!m_primaryLoadComplete && isLoading()) + return true; + if (!m_subresourceLoaders.isEmpty()) + return true; + if (Document* doc = m_frame->document()) { + if (doc->docLoader()->requestCount()) + return true; + if (Tokenizer* tok = doc->tokenizer()) + if (tok->processingData()) + return true; + } + } + return frameLoader()->subframeIsLoading(); +} + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size +void DocumentLoader::addAllArchiveResources(Archive* archive) +{ + if (!m_archiveResourceCollection) + m_archiveResourceCollection.set(new ArchiveResourceCollection); + + ASSERT(archive); + if (!archive) + return; + + m_archiveResourceCollection->addAllResources(archive); +} + +// FIXME: Adding a resource directly to a DocumentLoader/ArchiveResourceCollection seems like bad design, but is API some apps rely on. +// Can we change the design in a manner that will let us deprecate that API without reducing functionality of those apps? +void DocumentLoader::addArchiveResource(PassRefPtr<ArchiveResource> resource) +{ + if (!m_archiveResourceCollection) + m_archiveResourceCollection.set(new ArchiveResourceCollection); + + ASSERT(resource); + if (!resource) + return; + + m_archiveResourceCollection->addResource(resource); +} + +ArchiveResource* DocumentLoader::archiveResourceForURL(const KURL& url) const +{ + if (!m_archiveResourceCollection) + return 0; + + ArchiveResource* resource = m_archiveResourceCollection->archiveResourceForURL(url); + + return resource && !resource->shouldIgnoreWhenUnarchiving() ? resource : 0; +} + +PassRefPtr<Archive> DocumentLoader::popArchiveForSubframe(const String& frameName) +{ + return m_archiveResourceCollection ? m_archiveResourceCollection->popSubframeArchive(frameName) : 0; +} + +void DocumentLoader::clearArchiveResources() +{ + m_archiveResourceCollection.clear(); + m_substituteResourceDeliveryTimer.stop(); +} + +void DocumentLoader::setParsedArchiveData(PassRefPtr<SharedBuffer> data) +{ + m_parsedArchiveData = data; +} + +SharedBuffer* DocumentLoader::parsedArchiveData() const +{ + return m_parsedArchiveData.get(); +} + +PassRefPtr<ArchiveResource> DocumentLoader::mainResource() const +{ + const ResourceResponse& r = response(); + RefPtr<SharedBuffer> mainResourceBuffer = mainResourceData(); + if (!mainResourceBuffer) + mainResourceBuffer = SharedBuffer::create(); + + return ArchiveResource::create(mainResourceBuffer, r.url(), r.mimeType(), r.textEncodingName(), frame()->tree()->name()); +} + +PassRefPtr<ArchiveResource> DocumentLoader::subresource(const KURL& url) const +{ + if (!isCommitted()) + return 0; + + Document* doc = m_frame->document(); + if (!doc) + return archiveResourceForURL(url); + + CachedResource* resource = doc->docLoader()->cachedResource(url); + if (!resource || resource->preloadResult() == CachedResource::PreloadReferenced) + return archiveResourceForURL(url); + + return ArchiveResource::create(resource->data(), url, resource->response()); +} + +void DocumentLoader::getSubresources(Vector<PassRefPtr<ArchiveResource> >& subresources) const +{ + if (!isCommitted()) + return; + + Document* document = m_frame->document(); + if (!document) + return; + + const HashMap<String, CachedResource*>& allResources = document->docLoader()->allCachedResources(); + HashMap<String, CachedResource*>::const_iterator end = allResources.end(); + for (HashMap<String, CachedResource*>::const_iterator it = allResources.begin(); it != end; ++it) { + RefPtr<ArchiveResource> subresource = this->subresource(KURL(it->second->url())); + if (subresource) + subresources.append(subresource.release()); + } + + return; +} +#endif + +void DocumentLoader::deliverSubstituteResourcesAfterDelay() +{ + if (m_pendingSubstituteResources.isEmpty()) + return; + ASSERT(m_frame && m_frame->page()); + if (m_frame->page()->defersLoading()) + return; + if (!m_substituteResourceDeliveryTimer.isActive()) + m_substituteResourceDeliveryTimer.startOneShot(0); +} + +void DocumentLoader::substituteResourceDeliveryTimerFired(Timer<DocumentLoader>*) +{ + if (m_pendingSubstituteResources.isEmpty()) + return; + ASSERT(m_frame && m_frame->page()); + if (m_frame->page()->defersLoading()) + return; + + SubstituteResourceMap copy; + copy.swap(m_pendingSubstituteResources); + + SubstituteResourceMap::const_iterator end = copy.end(); + for (SubstituteResourceMap::const_iterator it = copy.begin(); it != end; ++it) { + RefPtr<ResourceLoader> loader = it->first; + SubstituteResource* resource = it->second.get(); + + if (resource) { + SharedBuffer* data = resource->data(); + + loader->didReceiveResponse(resource->response()); + loader->didReceiveData(data->data(), data->size(), data->size(), true); + loader->didFinishLoading(); + } else { + // A null resource means that we should fail the load. + // FIXME: Maybe we should use another error here - something like "not in cache". + loader->didFail(loader->cannotShowURLError()); + } + } +} + +#ifndef NDEBUG +bool DocumentLoader::isSubstituteLoadPending(ResourceLoader* loader) const +{ + return m_pendingSubstituteResources.contains(loader); +} +#endif + +void DocumentLoader::cancelPendingSubstituteLoad(ResourceLoader* loader) +{ + if (m_pendingSubstituteResources.isEmpty()) + return; + m_pendingSubstituteResources.remove(loader); + if (m_pendingSubstituteResources.isEmpty()) + m_substituteResourceDeliveryTimer.stop(); +} + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size +bool DocumentLoader::scheduleArchiveLoad(ResourceLoader* loader, const ResourceRequest& request, const KURL& originalURL) +{ + ArchiveResource* resource = 0; + + if (request.url() == originalURL) + resource = archiveResourceForURL(originalURL); + + if (!resource) { + // WebArchiveDebugMode means we fail loads instead of trying to fetch them from the network if they're not in the archive. + bool shouldFailLoad = m_frame->settings()->webArchiveDebugModeEnabled() && ArchiveFactory::isArchiveMimeType(responseMIMEType()); + + if (!shouldFailLoad) + return false; + } + + m_pendingSubstituteResources.set(loader, resource); + deliverSubstituteResourcesAfterDelay(); + + return true; +} +#endif + +void DocumentLoader::addResponse(const ResourceResponse& r) +{ + if (!m_stopRecordingResponses) + m_responses.append(r); +} + +void DocumentLoader::stopRecordingResponses() +{ + m_stopRecordingResponses = true; +} + +void DocumentLoader::setTitle(const String& title) +{ + if (title.isEmpty()) + return; + + String trimmed = canonicalizedTitle(title, m_frame); + if (!trimmed.isEmpty() && m_pageTitle != trimmed) { + frameLoader()->willChangeTitle(this); + m_pageTitle = trimmed; + frameLoader()->didChangeTitle(this); + } +} + +KURL DocumentLoader::urlForHistory() const +{ + // Return the URL to be used for history and B/F list. + // Returns nil for WebDataProtocol URLs that aren't alternates + // for unreachable URLs, because these can't be stored in history. + if (m_substituteData.isValid()) + return unreachableURL(); + + return m_originalRequestCopy.url(); +} + +void DocumentLoader::loadFromCachedPage(PassRefPtr<CachedPage> cachedPage) +{ + LOG(PageCache, "WebCorePageCache: DocumentLoader %p loading from cached page %p", this, cachedPage.get()); + + prepareForLoadStart(); + setLoadingFromCachedPage(true); + setCommitted(true); + frameLoader()->commitProvisionalLoad(cachedPage); +} + +const KURL& DocumentLoader::originalURL() const +{ + return m_originalRequestCopy.url(); +} + +const KURL& DocumentLoader::requestURL() const +{ + return request().url(); +} + +const KURL& DocumentLoader::responseURL() const +{ + return m_response.url(); +} + +const String& DocumentLoader::responseMIMEType() const +{ + return m_response.mimeType(); +} + +const KURL& DocumentLoader::unreachableURL() const +{ + return m_substituteData.failingURL(); +} + +void DocumentLoader::setDefersLoading(bool defers) +{ + if (m_mainResourceLoader) + m_mainResourceLoader->setDefersLoading(defers); + setAllDefersLoading(m_subresourceLoaders, defers); + setAllDefersLoading(m_plugInStreamLoaders, defers); + if (!defers) + deliverSubstituteResourcesAfterDelay(); +} + +void DocumentLoader::stopLoadingPlugIns() +{ + cancelAll(m_plugInStreamLoaders); +} + +void DocumentLoader::stopLoadingSubresources() +{ + cancelAll(m_subresourceLoaders); +} + +void DocumentLoader::addSubresourceLoader(ResourceLoader* loader) +{ + m_subresourceLoaders.add(loader); + setLoading(true); +} + +void DocumentLoader::removeSubresourceLoader(ResourceLoader* loader) +{ + m_subresourceLoaders.remove(loader); + updateLoading(); + if (Frame* frame = m_frame) + frame->loader()->checkLoadComplete(); +} + +void DocumentLoader::addPlugInStreamLoader(ResourceLoader* loader) +{ + m_plugInStreamLoaders.add(loader); + setLoading(true); +} + +void DocumentLoader::removePlugInStreamLoader(ResourceLoader* loader) +{ + m_plugInStreamLoaders.remove(loader); + updateLoading(); +} + +bool DocumentLoader::isLoadingMainResource() const +{ + return !!m_mainResourceLoader; +} + +bool DocumentLoader::isLoadingSubresources() const +{ + return !m_subresourceLoaders.isEmpty(); +} + +bool DocumentLoader::isLoadingPlugIns() const +{ + return !m_plugInStreamLoaders.isEmpty(); +} + +bool DocumentLoader::isLoadingMultipartContent() const +{ + return m_mainResourceLoader && m_mainResourceLoader->isLoadingMultipartContent(); +} + +bool DocumentLoader::startLoadingMainResource(unsigned long identifier) +{ + ASSERT(!m_mainResourceLoader); + m_mainResourceLoader = MainResourceLoader::create(m_frame); + m_mainResourceLoader->setIdentifier(identifier); + + // FIXME: Is there any way the extra fields could have not been added by now? + // If not, it would be great to remove this line of code. + frameLoader()->addExtraFieldsToRequest(m_request, true, false); + + if (!m_mainResourceLoader->load(m_request, m_substituteData)) { + // FIXME: If this should really be caught, we should just ASSERT this doesn't happen; + // should it be caught by other parts of WebKit or other parts of the app? + LOG_ERROR("could not create WebResourceHandle for URL %s -- should be caught by policy handler level", m_request.url().string().ascii().data()); + m_mainResourceLoader = 0; + return false; + } + + return true; +} + +void DocumentLoader::cancelMainResourceLoad(const ResourceError& error) +{ + m_mainResourceLoader->cancel(error); +} + +void DocumentLoader::subresourceLoaderFinishedLoadingOnePart(ResourceLoader* loader) +{ + m_multipartSubresourceLoaders.add(loader); + m_subresourceLoaders.remove(loader); + updateLoading(); + if (Frame* frame = m_frame) + frame->loader()->checkLoadComplete(); +} + +void DocumentLoader::iconLoadDecisionAvailable() +{ + if (m_frame) + m_frame->loader()->iconLoadDecisionAvailable(); +} + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) +void DocumentLoader::setCandidateApplicationCacheGroup(ApplicationCacheGroup* group) +{ + ASSERT(!m_applicationCache); + m_candidateApplicationCacheGroup = group; +} + +void DocumentLoader::setApplicationCache(PassRefPtr<ApplicationCache> applicationCache) +{ + if (m_candidateApplicationCacheGroup) { + ASSERT(!m_applicationCache); + m_candidateApplicationCacheGroup = 0; + } + + m_applicationCache = applicationCache; +} + +ApplicationCache* DocumentLoader::topLevelApplicationCache() const +{ + if (!m_frame) + return 0; + + if (m_applicationCache) + return m_applicationCache.get(); + + if (Page* page = m_frame->page()) + return page->mainFrame()->loader()->documentLoader()->applicationCache(); + + return 0; +} + +ApplicationCache* DocumentLoader::mainResourceApplicationCache() const +{ + if (m_mainResourceApplicationCache) + return m_mainResourceApplicationCache.get(); + if (m_mainResourceLoader) + return m_mainResourceLoader->applicationCache(); + return 0; +} + +bool DocumentLoader::shouldLoadResourceFromApplicationCache(const ResourceRequest& request, ApplicationCacheResource*& resource) +{ + ApplicationCache* cache = topLevelApplicationCache(); + if (!cache) + return false; + + // If the resource is not a HTTP/HTTPS GET, then abort + if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request)) + return false; + + if (cache->isURLInOnlineWhitelist(request.url())) + return false; + + resource = cache->resourceForURL(request.url()); + + // Don't load foreign resources. + if (resource && (resource->type() & ApplicationCacheResource::Foreign)) + resource = 0; + + return true; +} + +bool DocumentLoader::scheduleApplicationCacheLoad(ResourceLoader* loader, const ResourceRequest& request, const KURL& originalURL) +{ + if (!frameLoader()->frame()->settings() || !frameLoader()->frame()->settings()->offlineWebApplicationCacheEnabled()) + return false; + + if (request.url() != originalURL) + return false; + + ApplicationCacheResource* resource; + if (!shouldLoadResourceFromApplicationCache(request, resource)) + // FIXME: Handle opportunistic caching namespaces + return false; + + m_pendingSubstituteResources.set(loader, resource); + deliverSubstituteResourcesAfterDelay(); + + return true; +} + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +} diff --git a/WebCore/loader/DocumentLoader.h b/WebCore/loader/DocumentLoader.h new file mode 100644 index 0000000..aef4f49 --- /dev/null +++ b/WebCore/loader/DocumentLoader.h @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 DocumentLoader_h +#define DocumentLoader_h + +#include "IconDatabase.h" +#include "NavigationAction.h" +#include <wtf/RefCounted.h> +#include "PlatformString.h" +#include "ResourceError.h" +#include "ResourceRequest.h" +#include "ResourceResponse.h" +#include "SubstituteData.h" +#include <wtf/HashSet.h> +#include <wtf/RefPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + class ApplicationCache; + class ApplicationCacheGroup; + class ApplicationCacheResource; +#endif +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + class Archive; + class ArchiveResource; + class ArchiveResourceCollection; +#endif + class CachedPage; + class Frame; + class FrameLoader; + class HistoryItem; + class KURL; + class MainResourceLoader; + class ResourceLoader; + class SchedulePair; + class SharedBuffer; + class SubstituteData; + class SubstituteResource; + + typedef HashSet<RefPtr<ResourceLoader> > ResourceLoaderSet; + typedef Vector<ResourceResponse> ResponseVector; + + class DocumentLoader : public RefCounted<DocumentLoader> { + public: + static PassRefPtr<DocumentLoader> create(const ResourceRequest& request, const SubstituteData& data) + { + return adoptRef(new DocumentLoader(request, data)); + } + virtual ~DocumentLoader(); + + void setFrame(Frame*); + Frame* frame() const { return m_frame; } + + virtual void attachToFrame(); + virtual void detachFromFrame(); + + FrameLoader* frameLoader() const; + MainResourceLoader* mainResourceLoader() const { return m_mainResourceLoader.get(); } + PassRefPtr<SharedBuffer> mainResourceData() const; + + const ResourceRequest& originalRequest() const; + const ResourceRequest& originalRequestCopy() const; + + const ResourceRequest& request() const; + ResourceRequest& request(); + void setRequest(const ResourceRequest&); + + const SubstituteData& substituteData() const { return m_substituteData; } + + const KURL& url() const; + const KURL& unreachableURL() const; + + const KURL& originalURL() const; + const KURL& requestURL() const; + const KURL& responseURL() const; + const String& responseMIMEType() const; + + void replaceRequestURLForAnchorScroll(const KURL&); + bool isStopping() const { return m_isStopping; } + void stopLoading(); + void setCommitted(bool committed) { m_committed = committed; } + bool isCommitted() const { return m_committed; } + bool isLoading() const { return m_loading; } + void setLoading(bool loading) { m_loading = loading; } + void updateLoading(); + void receivedData(const char*, int); + void setupForReplaceByMIMEType(const String& newMIMEType); + void finishedLoading(); + const ResourceResponse& response() const { return m_response; } + const ResourceError& mainDocumentError() const { return m_mainDocumentError; } + void mainReceivedError(const ResourceError&, bool isComplete); + void setResponse(const ResourceResponse& response) { m_response = response; } + void prepareForLoadStart(); + bool isClientRedirect() const { return m_isClientRedirect; } + void setIsClientRedirect(bool isClientRedirect) { m_isClientRedirect = isClientRedirect; } + bool isLoadingInAPISense() const; + void setPrimaryLoadComplete(bool); + void setTitle(const String&); + const String& overrideEncoding() const { return m_overrideEncoding; } + +#if PLATFORM(MAC) + void schedule(SchedulePair*); + void unschedule(SchedulePair*); +#endif + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + void addAllArchiveResources(Archive*); + void addArchiveResource(PassRefPtr<ArchiveResource>); + + // Return an ArchiveResource for the URL, either creating from live data or + // pulling from the ArchiveResourceCollection + PassRefPtr<ArchiveResource> subresource(const KURL&) const; + // Return the ArchiveResource for the URL only when loading an Archive + ArchiveResource* archiveResourceForURL(const KURL&) const; + + PassRefPtr<Archive> popArchiveForSubframe(const String& frameName); + void clearArchiveResources(); + void setParsedArchiveData(PassRefPtr<SharedBuffer>); + SharedBuffer* parsedArchiveData() const; + + PassRefPtr<ArchiveResource> mainResource() const; + void getSubresources(Vector<PassRefPtr<ArchiveResource> >&) const; + + bool scheduleArchiveLoad(ResourceLoader*, const ResourceRequest&, const KURL&); +#endif +#ifndef NDEBUG + bool isSubstituteLoadPending(ResourceLoader*) const; +#endif + void cancelPendingSubstituteLoad(ResourceLoader*); + + void addResponse(const ResourceResponse&); + const ResponseVector& responses() const { return m_responses; } + + const NavigationAction& triggeringAction() const { return m_triggeringAction; } + void setTriggeringAction(const NavigationAction& action) { m_triggeringAction = action; } + void setOverrideEncoding(const String& encoding) { m_overrideEncoding = encoding; } + void setLastCheckedRequest(const ResourceRequest& request) { m_lastCheckedRequest = request; } + const ResourceRequest& lastCheckedRequest() { return m_lastCheckedRequest; } + + void stopRecordingResponses(); + const String& title() const { return m_pageTitle; } + KURL urlForHistory() const; + + void loadFromCachedPage(PassRefPtr<CachedPage>); + void setLoadingFromCachedPage(bool loading) { m_loadingFromCachedPage = loading; } + bool isLoadingFromCachedPage() const { return m_loadingFromCachedPage; } + + void setDefersLoading(bool); + + bool startLoadingMainResource(unsigned long identifier); + void cancelMainResourceLoad(const ResourceError&); + + void iconLoadDecisionAvailable(); + + bool isLoadingMainResource() const; + bool isLoadingSubresources() const; + bool isLoadingPlugIns() const; + bool isLoadingMultipartContent() const; + + void stopLoadingPlugIns(); + void stopLoadingSubresources(); + + void addSubresourceLoader(ResourceLoader*); + void removeSubresourceLoader(ResourceLoader*); + void addPlugInStreamLoader(ResourceLoader*); + void removePlugInStreamLoader(ResourceLoader*); + + void subresourceLoaderFinishedLoadingOnePart(ResourceLoader*); + + void setDeferMainResourceDataLoad(bool defer) { m_deferMainResourceDataLoad = defer; } + bool deferMainResourceDataLoad() const { return m_deferMainResourceDataLoad; } + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + bool scheduleApplicationCacheLoad(ResourceLoader*, const ResourceRequest&, const KURL& originalURL); + bool shouldLoadResourceFromApplicationCache(const ResourceRequest&, ApplicationCacheResource*&); + + void setCandidateApplicationCacheGroup(ApplicationCacheGroup* group); + ApplicationCacheGroup* candidateApplicationCacheGroup() const { return m_candidateApplicationCacheGroup; } + + void setApplicationCache(PassRefPtr<ApplicationCache> applicationCache); + ApplicationCache* applicationCache() const { return m_applicationCache.get(); } + ApplicationCache* topLevelApplicationCache() const; + + ApplicationCache* mainResourceApplicationCache() const; +#endif + + protected: + DocumentLoader(const ResourceRequest&, const SubstituteData&); + + bool m_deferMainResourceDataLoad; + + private: + void setupForReplace(); + void commitIfReady(); + void clearErrors(); + void setMainDocumentError(const ResourceError&); + void commitLoad(const char*, int); + bool doesProgressiveLoad(const String& MIMEType) const; + + void deliverSubstituteResourcesAfterDelay(); + void substituteResourceDeliveryTimerFired(Timer<DocumentLoader>*); + + Frame* m_frame; + + RefPtr<MainResourceLoader> m_mainResourceLoader; + ResourceLoaderSet m_subresourceLoaders; + ResourceLoaderSet m_multipartSubresourceLoaders; + ResourceLoaderSet m_plugInStreamLoaders; + + RefPtr<SharedBuffer> m_mainResourceData; + + // A reference to actual request used to create the data source. + // This should only be used by the resourceLoadDelegate's + // identifierForInitialRequest:fromDatasource: method. It is + // not guaranteed to remain unchanged, as requests are mutable. + ResourceRequest m_originalRequest; + + SubstituteData m_substituteData; + + // A copy of the original request used to create the data source. + // We have to copy the request because requests are mutable. + ResourceRequest m_originalRequestCopy; + + // The 'working' request. It may be mutated + // several times from the original request to include additional + // headers, cookie information, canonicalization and redirects. + ResourceRequest m_request; + + ResourceResponse m_response; + + ResourceError m_mainDocumentError; + + bool m_committed; + bool m_isStopping; + bool m_loading; + bool m_gotFirstByte; + bool m_primaryLoadComplete; + bool m_isClientRedirect; + bool m_loadingFromCachedPage; + + String m_pageTitle; + + String m_overrideEncoding; + + // The action that triggered loading - we keep this around for the + // benefit of the various policy handlers. + NavigationAction m_triggeringAction; + + // The last request that we checked click policy for - kept around + // so we can avoid asking again needlessly. + ResourceRequest m_lastCheckedRequest; + + // We retain all the received responses so we can play back the + // WebResourceLoadDelegate messages if the item is loaded from the + // page cache. + ResponseVector m_responses; + bool m_stopRecordingResponses; + + typedef HashMap<RefPtr<ResourceLoader>, RefPtr<SubstituteResource> > SubstituteResourceMap; + SubstituteResourceMap m_pendingSubstituteResources; + Timer<DocumentLoader> m_substituteResourceDeliveryTimer; + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + OwnPtr<ArchiveResourceCollection> m_archiveResourceCollection; + RefPtr<SharedBuffer> m_parsedArchiveData; +#endif + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + // The application cache that the document loader is associated with (if any). + RefPtr<ApplicationCache> m_applicationCache; + + // Before an application cache has finished loading, this will be the candidate application + // group that the document loader is associated with. + ApplicationCacheGroup* m_candidateApplicationCacheGroup; + + // Once the main resource has finished loading, this is the application cache it was loaded from (if any). + RefPtr<ApplicationCache> m_mainResourceApplicationCache; +#endif + }; + +} + +#endif // DocumentLoader_h diff --git a/WebCore/loader/EmptyClients.h b/WebCore/loader/EmptyClients.h new file mode 100644 index 0000000..8cab747 --- /dev/null +++ b/WebCore/loader/EmptyClients.h @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2006 Eric Seidel (eric@webkit.org) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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 EmptyClients_h +#define EmptyClients_h + +#include "ChromeClient.h" +#include "ContextMenuClient.h" +#include "DragClient.h" +#include "DocumentLoader.h" +#include "EditCommand.h" +#include "EditorClient.h" +#include "FocusDirection.h" +#include "FloatRect.h" +#include "FrameLoaderClient.h" +#include "InspectorClient.h" +#include "ResourceError.h" +#include "SharedBuffer.h" + +/* + This file holds empty Client stubs for use by WebCore. + Viewless element needs to create a dummy Page->Frame->FrameView tree for use in parsing or executing JavaScript. + This tree depends heavily on Clients (usually provided by WebKit classes). + + This file was first created for SVGImage as it had no way to access the current Page (nor should it, + since Images are not tied to a page). + See http://bugs.webkit.org/show_bug.cgi?id=5971 for the original discussion about this file. + + Ideally, whenever you change a Client class, you should add a stub here. + Brittle, yes. Unfortunate, yes. Hopefully temporary. +*/ + +namespace WebCore { + +class EmptyChromeClient : public ChromeClient { +public: + virtual ~EmptyChromeClient() { } + virtual void chromeDestroyed() { } + + virtual void setWindowRect(const FloatRect&) { } + virtual FloatRect windowRect() { return FloatRect(); } + + virtual FloatRect pageRect() { return FloatRect(); } + + virtual float scaleFactor() { return 1.f; } + + virtual void focus() { } + virtual void unfocus() { } + + virtual bool canTakeFocus(FocusDirection) { return false; } + virtual void takeFocus(FocusDirection) { } + + virtual Page* createWindow(Frame*, const FrameLoadRequest&, const WindowFeatures&) { return 0; } + virtual void show() { } + + virtual bool canRunModal() { return false; } + virtual void runModal() { } + + virtual void setToolbarsVisible(bool) { } + virtual bool toolbarsVisible() { return false; } + + virtual void setStatusbarVisible(bool) { } + virtual bool statusbarVisible() { return false; } + + virtual void setScrollbarsVisible(bool) { } + virtual bool scrollbarsVisible() { return false; } + + virtual void setMenubarVisible(bool) { } + virtual bool menubarVisible() { return false; } + + virtual void setResizable(bool) { } + + virtual void addMessageToConsole(const String& message, unsigned int lineNumber, const String& sourceID) { } + + virtual bool canRunBeforeUnloadConfirmPanel() { return false; } + virtual bool runBeforeUnloadConfirmPanel(const String& message, Frame* frame) { return true; } + + virtual void closeWindowSoon() { } + + virtual void runJavaScriptAlert(Frame*, const String&) { } + virtual bool runJavaScriptConfirm(Frame*, const String&) { return false; } + virtual bool runJavaScriptPrompt(Frame*, const String& message, const String& defaultValue, String& result) { return false; } + virtual bool shouldInterruptJavaScript() { return false; } + + virtual void setStatusbarText(const String&) { } + + virtual bool tabsToLinks() const { return false; } + + virtual IntRect windowResizerRect() const { return IntRect(); } + virtual void addToDirtyRegion(const IntRect&) { } + virtual void scrollBackingStore(int dx, int dy, const IntRect& scrollViewRect, const IntRect& clipRect) { } + virtual void updateBackingStore() { } + + virtual void repaint(const IntRect&, bool contentChanged, bool immediate = false, bool repaintContentOnly = false) { } + virtual void scroll(const IntSize& scrollDelta, const IntRect& rectToScroll, const IntRect& clipRect) { } + virtual IntPoint screenToWindow(const IntPoint& p) const { return p; } + virtual IntRect windowToScreen(const IntRect& r) const { return r; } + virtual PlatformWidget platformWindow() const { return 0; } + + virtual void mouseDidMoveOverElement(const HitTestResult&, unsigned modifierFlags) { } + + virtual void setToolTip(const String&) { } + + virtual void print(Frame*) { } + + virtual void exceededDatabaseQuota(Frame*, const String&) { } + + virtual void runOpenPanel(Frame*, PassRefPtr<FileChooser>) { } +}; + +class EmptyFrameLoaderClient : public FrameLoaderClient { +public: + virtual ~EmptyFrameLoaderClient() { } + virtual void frameLoaderDestroyed() { } + + virtual bool hasWebView() const { return true; } // mainly for assertions + + virtual void makeRepresentation(DocumentLoader*) { } + virtual void forceLayout() { } + virtual void forceLayoutForNonHTML() { } + + virtual void updateHistoryForCommit() { } + + virtual void updateHistoryForBackForwardNavigation() { } + virtual void updateHistoryForReload() { } + virtual void updateHistoryForStandardLoad() { } + virtual void updateHistoryForInternalLoad() { } + + virtual void updateHistoryAfterClientRedirect() { } + + virtual void setCopiesOnScroll() { } + + virtual void detachedFromParent2() { } + virtual void detachedFromParent3() { } + + virtual void download(ResourceHandle*, const ResourceRequest&, const ResourceRequest&, const ResourceResponse&) { } + + virtual void assignIdentifierToInitialRequest(unsigned long identifier, DocumentLoader*, const ResourceRequest&) { } + virtual void dispatchWillSendRequest(DocumentLoader*, unsigned long identifier, ResourceRequest&, const ResourceResponse& redirectResponse) { } + virtual void dispatchDidReceiveAuthenticationChallenge(DocumentLoader*, unsigned long identifier, const AuthenticationChallenge&) { } + virtual void dispatchDidCancelAuthenticationChallenge(DocumentLoader*, unsigned long identifier, const AuthenticationChallenge&) { } + virtual void dispatchDidReceiveResponse(DocumentLoader*, unsigned long identifier, const ResourceResponse&) { } + virtual void dispatchDidReceiveContentLength(DocumentLoader*, unsigned long identifier, int lengthReceived) { } + virtual void dispatchDidFinishLoading(DocumentLoader*, unsigned long identifier) { } + virtual void dispatchDidFailLoading(DocumentLoader*, unsigned long identifier, const ResourceError&) { } + virtual bool dispatchDidLoadResourceFromMemoryCache(DocumentLoader*, const ResourceRequest&, const ResourceResponse&, int length) { return false; } + + virtual void dispatchDidHandleOnloadEvents() { } + virtual void dispatchDidReceiveServerRedirectForProvisionalLoad() { } + virtual void dispatchDidCancelClientRedirect() { } + virtual void dispatchWillPerformClientRedirect(const KURL&, double interval, double fireDate) { } + virtual void dispatchDidChangeLocationWithinPage() { } + virtual void dispatchWillClose() { } + virtual void dispatchDidReceiveIcon() { } + virtual void dispatchDidStartProvisionalLoad() { } + virtual void dispatchDidReceiveTitle(const String& title) { } + virtual void dispatchDidCommitLoad() { } + virtual void dispatchDidFailProvisionalLoad(const ResourceError&) { } + virtual void dispatchDidFailLoad(const ResourceError&) { } + virtual void dispatchDidFinishDocumentLoad() { } + virtual void dispatchDidFinishLoad() { } + virtual void dispatchDidFirstLayout() { } + + virtual Frame* dispatchCreatePage() { return 0; } + virtual void dispatchShow() { } + + virtual void dispatchDecidePolicyForMIMEType(FramePolicyFunction, const String& MIMEType, const ResourceRequest&) { } + virtual void dispatchDecidePolicyForNewWindowAction(FramePolicyFunction, const NavigationAction&, const ResourceRequest&, PassRefPtr<FormState>, const String& frameName) { } + virtual void dispatchDecidePolicyForNavigationAction(FramePolicyFunction, const NavigationAction&, const ResourceRequest&, PassRefPtr<FormState>) { } + virtual void cancelPolicyCheck() { } + + virtual void dispatchUnableToImplementPolicy(const ResourceError&) { } + + virtual void dispatchWillSubmitForm(FramePolicyFunction, PassRefPtr<FormState>) { } + + virtual void dispatchDidLoadMainResource(DocumentLoader*) { } + virtual void revertToProvisionalState(DocumentLoader*) { } + virtual void setMainDocumentError(DocumentLoader*, const ResourceError&) { } + + virtual void willChangeEstimatedProgress() { } + virtual void didChangeEstimatedProgress() { } + virtual void postProgressStartedNotification() { } + virtual void postProgressEstimateChangedNotification() { } + virtual void postProgressFinishedNotification() { } + + virtual void setMainFrameDocumentReady(bool) { } + + virtual void startDownload(const ResourceRequest&) { } + + virtual void willChangeTitle(DocumentLoader*) { } + virtual void didChangeTitle(DocumentLoader*) { } + + virtual void committedLoad(DocumentLoader*, const char*, int) { } + virtual void finishedLoading(DocumentLoader*) { } + + virtual ResourceError cancelledError(const ResourceRequest&) { return ResourceError(); } + virtual ResourceError blockedError(const ResourceRequest&) { return ResourceError(); } + virtual ResourceError cannotShowURLError(const ResourceRequest&) { return ResourceError(); } + virtual ResourceError interruptForPolicyChangeError(const ResourceRequest&) { return ResourceError(); } + + virtual ResourceError cannotShowMIMETypeError(const ResourceResponse&) { return ResourceError(); } + virtual ResourceError fileDoesNotExistError(const ResourceResponse&) { return ResourceError(); } + virtual ResourceError pluginWillHandleLoadError(const ResourceResponse&) { return ResourceError(); } + + virtual bool shouldFallBack(const ResourceError&) { return false; } + + virtual bool canHandleRequest(const ResourceRequest&) const { return false; } + virtual bool canShowMIMEType(const String& MIMEType) const { return false; } + virtual bool representationExistsForURLScheme(const String& URLScheme) const { return false; } + virtual String generatedMIMETypeForURLScheme(const String& URLScheme) const { return ""; } + + virtual void frameLoadCompleted() { } + virtual void restoreViewState() { } + virtual void provisionalLoadStarted() { } + virtual bool shouldTreatURLAsSameAsCurrent(const KURL&) const { return false; } + virtual void addHistoryItemForFragmentScroll() { } + virtual void didFinishLoad() { } + virtual void prepareForDataSourceReplacement() { } + + virtual PassRefPtr<DocumentLoader> createDocumentLoader(const ResourceRequest& request, const SubstituteData& substituteData) { return DocumentLoader::create(request, substituteData); } + virtual void setTitle(const String& title, const KURL&) { } + + virtual String userAgent(const KURL&) { return ""; } + + virtual void savePlatformDataToCachedPage(CachedPage*) { } + virtual void transitionToCommittedFromCachedPage(CachedPage*) { } + virtual void transitionToCommittedForNewPage() { } + + virtual void updateGlobalHistory(const KURL&) { } + virtual bool shouldGoToHistoryItem(HistoryItem*) const { return false; } + virtual void saveViewStateToItem(HistoryItem*) { } + virtual bool canCachePage() const { return false; } + + virtual PassRefPtr<Frame> createFrame(const KURL& url, const String& name, HTMLFrameOwnerElement* ownerElement, + const String& referrer, bool allowsScrolling, int marginWidth, int marginHeight) { return 0; } + virtual Widget* createPlugin(const IntSize&,Element*, const KURL&, const Vector<String>&, const Vector<String>&, const String&, bool) { return 0; } + virtual Widget* createJavaAppletWidget(const IntSize&, Element*, const KURL&, const Vector<String>&, const Vector<String>&) { return 0; } + + virtual ObjectContentType objectContentType(const KURL& url, const String& mimeType) { return ObjectContentType(); } + virtual String overrideMediaType() const { return String(); } + + virtual void redirectDataToPlugin(Widget*) {} + virtual void windowObjectCleared() {} + virtual void didPerformFirstNavigation() const {} + + virtual void registerForIconNotification(bool listen) {} + +#if PLATFORM(MAC) + virtual NSCachedURLResponse* willCacheResponse(DocumentLoader*, unsigned long identifier, NSCachedURLResponse* response) const { return response; } +#endif + +}; + +class EmptyEditorClient : public EditorClient { +public: + virtual ~EmptyEditorClient() { } + virtual void pageDestroyed() { } + + virtual bool shouldDeleteRange(Range*) { return false; } + virtual bool shouldShowDeleteInterface(HTMLElement*) { return false; } + virtual bool smartInsertDeleteEnabled() { return false; } + virtual bool isContinuousSpellCheckingEnabled() { return false; } + virtual void toggleContinuousSpellChecking() { } + virtual bool isGrammarCheckingEnabled() { return false; } + virtual void toggleGrammarChecking() { } + virtual int spellCheckerDocumentTag() { return -1; } + + virtual bool selectWordBeforeMenuEvent() { return false; } + virtual bool isEditable() { return false; } + + virtual bool shouldBeginEditing(Range*) { return false; } + virtual bool shouldEndEditing(Range*) { return false; } + virtual bool shouldInsertNode(Node*, Range*, EditorInsertAction) { return false; } + // virtual bool shouldInsertNode(Node*, Range* replacingRange, WebViewInsertAction) { return false; } + virtual bool shouldInsertText(const String&, Range*, EditorInsertAction) { return false; } + virtual bool shouldChangeSelectedRange(Range* fromRange, Range* toRange, EAffinity, bool stillSelecting) { return false; } + + virtual bool shouldApplyStyle(CSSStyleDeclaration*, Range*) { return false; } + virtual bool shouldMoveRangeAfterDelete(Range*, Range*) { return false; } + // virtual bool shouldChangeTypingStyle(CSSStyleDeclaration* fromStyle, CSSStyleDeclaration* toStyle) { return false; } + // virtual bool doCommandBySelector(SEL selector) { return false; } + // + virtual void didBeginEditing() { } + virtual void respondToChangedContents() { } + virtual void respondToChangedSelection() { } + virtual void didEndEditing() { } + virtual void didWriteSelectionToPasteboard() { } + virtual void didSetSelectionTypesForPasteboard() { } + // virtual void webViewDidChangeTypingStyle:(NSNotification *)notification { } + // virtual void webViewDidChangeSelection:(NSNotification *)notification { } + // virtual NSUndoManager* undoManagerForWebView:(WebView *)webView { return 0; } + + virtual void registerCommandForUndo(PassRefPtr<EditCommand>) { } + virtual void registerCommandForRedo(PassRefPtr<EditCommand>) { } + virtual void clearUndoRedoOperations() { } + + virtual bool canUndo() const { return false; } + virtual bool canRedo() const { return false; } + + virtual void undo() { } + virtual void redo() { } + + virtual void handleKeyboardEvent(KeyboardEvent*) { } + virtual void handleInputMethodKeydown(KeyboardEvent*) { } + + virtual void textFieldDidBeginEditing(Element*) { } + virtual void textFieldDidEndEditing(Element*) { } + virtual void textDidChangeInTextField(Element*) { } + virtual bool doTextFieldCommandFromEvent(Element*, KeyboardEvent*) { return false; } + virtual void textWillBeDeletedInTextField(Element*) { } + virtual void textDidChangeInTextArea(Element*) { } + +#if PLATFORM(MAC) + virtual void markedTextAbandoned(Frame*) { } + + virtual NSString* userVisibleString(NSURL*) { return 0; } +#ifdef BUILDING_ON_TIGER + virtual NSArray* pasteboardTypesForSelection(Frame*) { return 0; } +#endif +#endif + virtual void ignoreWordInSpellDocument(const String&) { } + virtual void learnWord(const String&) { } + virtual void checkSpellingOfString(const UChar*, int length, int* misspellingLocation, int* misspellingLength) { } + virtual void checkGrammarOfString(const UChar*, int length, Vector<GrammarDetail>&, int* badGrammarLocation, int* badGrammarLength) { } + virtual void updateSpellingUIWithGrammarString(const String&, const GrammarDetail&) { } + virtual void updateSpellingUIWithMisspelledWord(const String&) { } + virtual void showSpellingUI(bool show) { } + virtual bool spellingUIIsShowing() { return false; } + virtual void getGuessesForWord(const String&, Vector<String>& guesses) { } + virtual void setInputMethodState(bool enabled) { } + + +}; + +class EmptyContextMenuClient : public ContextMenuClient { +public: + virtual ~EmptyContextMenuClient() { } + virtual void contextMenuDestroyed() { } + + virtual PlatformMenuDescription getCustomMenuFromDefaultItems(ContextMenu*) { return 0; } + virtual void contextMenuItemSelected(ContextMenuItem*, const ContextMenu*) { } + + virtual void downloadURL(const KURL& url) { } + virtual void copyImageToClipboard(const HitTestResult&) { } + virtual void searchWithGoogle(const Frame*) { } + virtual void lookUpInDictionary(Frame*) { } + virtual void speak(const String&) { } + virtual void stopSpeaking() { } + +#if PLATFORM(MAC) + virtual void searchWithSpotlight() { } +#endif +}; + +class EmptyDragClient : public DragClient { +public: + virtual ~EmptyDragClient() {} + virtual void willPerformDragDestinationAction(DragDestinationAction, DragData*) { } + virtual void willPerformDragSourceAction(DragSourceAction, const IntPoint&, Clipboard*) { } + virtual DragDestinationAction actionMaskForDrag(DragData*) { return DragDestinationActionNone; } + virtual DragSourceAction dragSourceActionMaskForPoint(const IntPoint&) { return DragSourceActionNone; } + virtual void startDrag(DragImageRef, const IntPoint&, const IntPoint&, Clipboard*, Frame*, bool) { } + virtual DragImageRef createDragImageForLink(KURL&, const String& label, Frame*) { return 0; } + virtual void dragControllerDestroyed() { } +}; + +class EmptyInspectorClient : public InspectorClient { +public: + virtual ~EmptyInspectorClient() { } + + virtual void inspectorDestroyed() { } + + virtual Page* createPage() { return 0; }; + + virtual String localizedStringsURL() { return String(); } + + virtual void showWindow() { } + virtual void closeWindow() { } + + virtual void attachWindow() { } + virtual void detachWindow() { } + + virtual void setAttachedWindowHeight(unsigned) { } + + virtual void highlight(Node*) { } + virtual void hideHighlight() { } + virtual void inspectedURLChanged(const String& newURL) { } + + virtual void populateSetting(const String& key, InspectorController::Setting&) { } + virtual void storeSetting(const String& key, const InspectorController::Setting&) { } + virtual void removeSetting(const String& key) { } +}; + +} + +#endif // EmptyClients_h diff --git a/WebCore/loader/FTPDirectoryDocument.cpp b/WebCore/loader/FTPDirectoryDocument.cpp new file mode 100644 index 0000000..0a7e91e --- /dev/null +++ b/WebCore/loader/FTPDirectoryDocument.cpp @@ -0,0 +1,493 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * 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. + */ + +#include "config.h" +#if ENABLE(FTPDIR) +#include "FTPDirectoryDocument.h" + +#include "CharacterNames.h" +#include "CString.h" +#include "HTMLNames.h" +#include "HTMLTableElement.h" +#include "HTMLTokenizer.h" +#include "LocalizedStrings.h" +#include "Logging.h" +#include "FTPDirectoryParser.h" +#include "SegmentedString.h" +#include "Settings.h" +#include "SharedBuffer.h" +#include "Text.h" + +#if PLATFORM(QT) +#include <QDateTime> +// On Windows, use the threadsafe *_r functions provided by pthread. +#elif PLATFORM(WIN_OS) && (USE(PTHREADS) || HAVE(PTHREAD_H)) +#include <pthread.h> +#endif + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +class FTPDirectoryTokenizer : public HTMLTokenizer { +public: + FTPDirectoryTokenizer(HTMLDocument*); + + virtual bool write(const SegmentedString&, bool appendData); + virtual void finish(); + + virtual bool isWaitingForScripts() const { return false; } + + inline void checkBuffer(int len = 10) + { + if ((m_dest - m_buffer) > m_size - len) { + // Enlarge buffer + int newSize = max(m_size * 2, m_size + len); + int oldOffset = m_dest - m_buffer; + m_buffer = static_cast<UChar*>(fastRealloc(m_buffer, newSize * sizeof(UChar))); + m_dest = m_buffer + oldOffset; + m_size = newSize; + } + } + +private: + // The tokenizer will attempt to load the document template specified via the preference + // Failing that, it will fall back and create the basic document which will have a minimal + // table for presenting the FTP directory in a useful manner + bool loadDocumentTemplate(); + void createBasicDocument(); + + void parseAndAppendOneLine(const String&); + void appendEntry(const String& name, const String& size, const String& date, bool isDirectory); + PassRefPtr<Element> createTDForFilename(const String&); + + Document* m_doc; + RefPtr<HTMLTableElement> m_tableElement; + + bool m_skipLF; + bool m_parsedTemplate; + + int m_size; + UChar* m_buffer; + UChar* m_dest; + String m_carryOver; + + ListState m_listState; +}; + +FTPDirectoryTokenizer::FTPDirectoryTokenizer(HTMLDocument* doc) + : HTMLTokenizer(doc, false) + , m_doc(doc) + , m_skipLF(false) + , m_parsedTemplate(false) + , m_size(254) + , m_buffer(static_cast<UChar*>(fastMalloc(sizeof(UChar) * m_size))) + , m_dest(m_buffer) +{ +} + +void FTPDirectoryTokenizer::appendEntry(const String& filename, const String& size, const String& date, bool isDirectory) +{ + ExceptionCode ec; + + RefPtr<Element> rowElement = m_tableElement->insertRow(-1, ec); + rowElement->setAttribute("class", "ftpDirectoryEntryRow", ec); + + RefPtr<Element> element = m_doc->createElementNS(xhtmlNamespaceURI, "td", ec); + element->appendChild(new Text(m_doc, String(&noBreakSpace, 1)), ec); + if (isDirectory) + element->setAttribute("class", "ftpDirectoryIcon ftpDirectoryTypeDirectory", ec); + else + element->setAttribute("class", "ftpDirectoryIcon ftpDirectoryTypeFile", ec); + rowElement->appendChild(element, ec); + + element = createTDForFilename(filename); + element->setAttribute("class", "ftpDirectoryFileName", ec); + rowElement->appendChild(element, ec); + + element = m_doc->createElementNS(xhtmlNamespaceURI, "td", ec); + element->appendChild(new Text(m_doc, date), ec); + element->setAttribute("class", "ftpDirectoryFileDate", ec); + rowElement->appendChild(element, ec); + + element = m_doc->createElementNS(xhtmlNamespaceURI, "td", ec); + element->appendChild(new Text(m_doc, size), ec); + element->setAttribute("class", "ftpDirectoryFileSize", ec); + rowElement->appendChild(element, ec); +} + +PassRefPtr<Element> FTPDirectoryTokenizer::createTDForFilename(const String& filename) +{ + ExceptionCode ec; + + String fullURL = m_doc->baseURL().string(); + if (fullURL[fullURL.length() - 1] == '/') + fullURL.append(filename); + else + fullURL.append("/" + filename); + + RefPtr<Element> anchorElement = m_doc->createElementNS(xhtmlNamespaceURI, "a", ec); + anchorElement->setAttribute("href", fullURL, ec); + anchorElement->appendChild(new Text(m_doc, filename), ec); + + RefPtr<Element> tdElement = m_doc->createElementNS(xhtmlNamespaceURI, "td", ec); + tdElement->appendChild(anchorElement, ec); + + return tdElement.release(); +} + +static String processFilesizeString(const String& size, bool isDirectory) +{ + if (isDirectory) + return "--"; + + bool valid; + int64_t bytes = size.toUInt64(&valid); + if (!valid) + return unknownFileSizeText(); + + if (bytes < 1000000) + return String::format("%.2f KB", static_cast<float>(bytes)/1000); + + if (bytes < 1000000000) + return String::format("%.2f MB", static_cast<float>(bytes)/1000000); + + return String::format("%.2f GB", static_cast<float>(bytes)/1000000000); +} + +static bool wasLastDayOfMonth(int year, int month, int day) +{ + static int lastDays[] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + if (month < 0 || month > 11) + return false; + + if (month == 2) { + if (year % 4 == 0 && (year % 100 || year % 400 == 0)) { + if (day == 29) + return true; + return false; + } + + if (day == 28) + return true; + return false; + } + + return lastDays[month] == day; +} + +#if PLATFORM(QT) + +/*! + Replacement for localtime_r() which is not available on MinGW. + + We use this on all of Qt's platforms for portability. + */ +struct tm gmtimeQt(const QDateTime &input) +{ + tm result; + + const QDate date(input.date()); + result.tm_year = date.year() - 1900; + result.tm_mon = date.month(); + result.tm_mday = date.day(); + result.tm_wday = date.dayOfWeek(); + result.tm_yday = date.dayOfYear(); + + const QTime time(input.time()); + result.tm_sec = time.second(); + result.tm_min = time.minute(); + result.tm_hour = time.hour(); + + return result; +} + +static struct tm *localTimeQt(const time_t *const timep, struct tm *result) +{ + const QDateTime dt(QDateTime::fromTime_t(*timep)); + *result = WebCore::gmtimeQt(dt.toLocalTime()); + return result; +} + +#define localtime_r(x, y) localTimeQt(x, y) +#elif PLATFORM(WIN_OS) && !defined(localtime_r) +#define localtime_r(x, y) localtime_s((y), (x)) +#endif + +static String processFileDateString(const FTPTime& fileTime) +{ + // FIXME: Need to localize this string? + + String timeOfDay; + + if (!(fileTime.tm_hour == 0 && fileTime.tm_min == 0 && fileTime.tm_sec == 0)) { + int hour = fileTime.tm_hour; + ASSERT(hour >= 0 && hour < 24); + + if (hour < 12) { + if (hour == 0) + hour = 12; + timeOfDay = String::format(", %i:%02i AM", hour, fileTime.tm_min); + } else { + hour = hour - 12; + if (hour == 0) + hour = 12; + timeOfDay = String::format(", %i:%02i PM", hour, fileTime.tm_min); + } + } + + // If it was today or yesterday, lets just do that - but we have to compare to the current time + struct tm now; + time_t now_t = time(NULL); + localtime_r(&now_t, &now); + + // localtime does "year = current year - 1900", compensate for that for readability and comparison purposes + now.tm_year += 1900; + + if (fileTime.tm_year == now.tm_year) { + if (fileTime.tm_mon == now.tm_mon) { + if (fileTime.tm_mday == now.tm_mday) + return "Today" + timeOfDay; + if (fileTime.tm_mday == now.tm_mday - 1) + return "Yesterday" + timeOfDay; + } + + if (now.tm_mday == 1 && (now.tm_mon == fileTime.tm_mon + 1 || now.tm_mon == 0 && fileTime.tm_mon == 11) && + wasLastDayOfMonth(fileTime.tm_year, fileTime.tm_mon, fileTime.tm_mday)) + return "Yesterday" + timeOfDay; + } + + if (fileTime.tm_year == now.tm_year - 1 && fileTime.tm_mon == 12 && fileTime.tm_mday == 31 && now.tm_mon == 1 && now.tm_mday == 1) + return "Yesterday" + timeOfDay; + + static const char* months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "???" }; + + int month = fileTime.tm_mon; + if (month < 0 || month > 11) + month = 12; + + String dateString; + + if (fileTime.tm_year > -1) + dateString = String::format("%s %i, %i", months[month], fileTime.tm_mday, fileTime.tm_year); + else + dateString = String::format("%s %i, %i", months[month], fileTime.tm_mday, now.tm_year); + + return dateString + timeOfDay; +} + +void FTPDirectoryTokenizer::parseAndAppendOneLine(const String& inputLine) +{ + ListResult result; + + FTPEntryType typeResult = parseOneFTPLine(inputLine.latin1().data(), m_listState, result); + + // FTPMiscEntry is a comment or usage statistic which we don't care about, and junk is invalid data - bail in these 2 cases + if (typeResult == FTPMiscEntry || typeResult == FTPJunkEntry) + return; + + String filename(result.filename, result.filenameLength); + if (result.type == FTPDirectoryEntry) { + filename.append("/"); + + // We have no interest in linking to "current directory" + if (filename == "./") + return; + } + + LOG(FTP, "Appending entry - %s, %s", filename.ascii().data(), result.fileSize.ascii().data()); + + appendEntry(filename, processFilesizeString(result.fileSize, result.type == FTPDirectoryEntry), processFileDateString(result.modifiedTime), result.type == FTPDirectoryEntry); +} + +bool FTPDirectoryTokenizer::loadDocumentTemplate() +{ + static RefPtr<SharedBuffer> templateDocumentData; + // FIXME: Instead of storing the data, we'd rather actually parse the template data into the template Document once, + // store that document, then "copy" it whenever we get an FTP directory listing. There are complexities with this + // approach that make it worth putting this off. + + if (!templateDocumentData) { + Settings* settings = m_doc->settings(); + if (settings) + templateDocumentData = SharedBuffer::createWithContentsOfFile(settings->ftpDirectoryTemplatePath()); + if (templateDocumentData) + LOG(FTP, "Loaded FTPDirectoryTemplate of length %i\n", templateDocumentData->size()); + } + + if (!templateDocumentData) { + LOG_ERROR("Could not load templateData"); + return false; + } + + // Tokenize the template as an HTML document synchronously + setForceSynchronous(true); + HTMLTokenizer::write(String(templateDocumentData->data(), templateDocumentData->size()), true); + setForceSynchronous(false); + + RefPtr<Element> tableElement = m_doc->getElementById("ftpDirectoryTable"); + if (!tableElement) + LOG_ERROR("Unable to find element by id \"ftpDirectoryTable\" in the template document."); + else if (!tableElement->hasTagName(tableTag)) + LOG_ERROR("Element of id \"ftpDirectoryTable\" is not a table element"); + else + m_tableElement = static_cast<HTMLTableElement*>(tableElement.get()); + + // Bail if we found the table element + if (m_tableElement) + return true; + + // Otherwise create one manually + ExceptionCode ec; + tableElement = m_doc->createElementNS(xhtmlNamespaceURI, "table", ec); + m_tableElement = static_cast<HTMLTableElement*>(tableElement.get()); + m_tableElement->setAttribute("id", "ftpDirectoryTable", ec); + + // If we didn't find the table element, lets try to append our own to the body + // If that fails for some reason, cram it on the end of the document as a last + // ditch effort + if (Element* body = m_doc->body()) + body->appendChild(m_tableElement, ec); + else + m_doc->appendChild(m_tableElement, ec); + + return true; +} + +void FTPDirectoryTokenizer::createBasicDocument() +{ + LOG(FTP, "Creating a basic FTP document structure as no template was loaded"); + + // FIXME: Make this "basic document" more acceptable + + ExceptionCode ec; + + RefPtr<Element> bodyElement = m_doc->createElementNS(xhtmlNamespaceURI, "body", ec); + + m_doc->appendChild(bodyElement, ec); + + RefPtr<Element> tableElement = m_doc->createElementNS(xhtmlNamespaceURI, "table", ec); + m_tableElement = static_cast<HTMLTableElement*>(tableElement.get()); + m_tableElement->setAttribute("id", "ftpDirectoryTable", ec); + + bodyElement->appendChild(m_tableElement, ec); +} + +bool FTPDirectoryTokenizer::write(const SegmentedString& s, bool appendData) +{ + // Make sure we have the table element to append to by loading the template set in the pref, or + // creating a very basic document with the appropriate table + if (!m_tableElement) { + if (!loadDocumentTemplate()) + createBasicDocument(); + ASSERT(m_tableElement); + } + + bool foundNewLine = false; + + m_dest = m_buffer; + SegmentedString str = s; + while (!str.isEmpty()) { + UChar c = *str; + + if (c == '\r') { + *m_dest++ = '\n'; + foundNewLine = true; + // possibly skip an LF in the case of an CRLF sequence + m_skipLF = true; + } else if (c == '\n') { + if (!m_skipLF) + *m_dest++ = c; + else + m_skipLF = false; + } else { + *m_dest++ = c; + m_skipLF = false; + } + + str.advance(); + + // Maybe enlarge the buffer + checkBuffer(); + } + + if (!foundNewLine) { + m_dest = m_buffer; + return false; + } + + UChar* start = m_buffer; + UChar* cursor = start; + + while (cursor < m_dest) { + if (*cursor == '\n') { + m_carryOver.append(String(start, cursor - start)); + LOG(FTP, "%s", m_carryOver.ascii().data()); + parseAndAppendOneLine(m_carryOver); + m_carryOver = String(); + + start = ++cursor; + } else + cursor++; + } + + // Copy the partial line we have left to the carryover buffer + if (cursor - start > 1) + m_carryOver.append(String(start, cursor - start - 1)); + + return false; +} + +void FTPDirectoryTokenizer::finish() +{ + // Possible the last line in the listing had no newline, so try to parse it now + if (!m_carryOver.isEmpty()) { + parseAndAppendOneLine(m_carryOver); + m_carryOver = String(); + } + + m_tableElement = 0; + fastFree(m_buffer); + + HTMLTokenizer::finish(); +} + +FTPDirectoryDocument::FTPDirectoryDocument(Frame* frame) + : HTMLDocument(frame) +{ +#ifndef NDEBUG + LogFTP.state = WTFLogChannelOn; +#endif +} + +Tokenizer* FTPDirectoryDocument::createTokenizer() +{ + return new FTPDirectoryTokenizer(this); +} + +} + +#endif // ENABLE(FTPDIR) diff --git a/WebCore/loader/FTPDirectoryDocument.h b/WebCore/loader/FTPDirectoryDocument.h new file mode 100644 index 0000000..ecc6f73 --- /dev/null +++ b/WebCore/loader/FTPDirectoryDocument.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * 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 FTPDirectoryDocument_h +#define FTPDirectoryDocument_h + +#include "HTMLDocument.h" + +namespace WebCore { + +class DOMImplementation; + +class FTPDirectoryDocument : public HTMLDocument { +public: + static PassRefPtr<FTPDirectoryDocument> create(Frame* frame) + { + return new FTPDirectoryDocument(frame); + } + +private: + FTPDirectoryDocument(Frame*); + virtual Tokenizer* createTokenizer(); +}; + +} // namespace WebCore + +#endif // FTPDirectoryDocument_h diff --git a/WebCore/loader/FTPDirectoryParser.cpp b/WebCore/loader/FTPDirectoryParser.cpp new file mode 100644 index 0000000..8c76e97 --- /dev/null +++ b/WebCore/loader/FTPDirectoryParser.cpp @@ -0,0 +1,1624 @@ +/* + * Copyright (C) 2002 Cyrus Patel <cyp@fb14.uni-mainz.de> + * (C) 2007 Apple Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +// This was originally Mozilla code, titled ParseFTPList.cpp +// Original version of this file can currently be found at: http://mxr.mozilla.org/mozilla1.8/source/netwerk/streamconv/converters/ParseFTPList.cpp + +#include "config.h" +#if ENABLE(FTPDIR) +#include "FTPDirectoryParser.h" + +#if PLATFORM(QT) +#include <QDateTime> +// On Windows, use the threadsafe *_r functions provided by pthread. +#elif PLATFORM(WIN_OS) && (USE(PTHREADS) || HAVE(PTHREAD_H)) +#include <pthread.h> +#endif + +#include <wtf/ASCIICType.h> +#include <stdio.h> + +using namespace WTF; + +namespace WebCore { +#if PLATFORM(QT) && defined(Q_WS_WIN32) +// Defined in FTPDirectoryDocument.cpp. +struct tm gmtimeQt(const QDateTime &input); + +static struct tm *gmtimeQt(const time_t *const timep, struct tm *result) +{ + const QDateTime dt(QDateTime::fromTime_t(*timep)); + *result = WebCore::gmtimeQt(dt); + return result; +} + +#define gmtime_r(x, y) gmtimeQt(x, y) +#elif PLATFORM(WIN_OS) && !defined(gmtime_r) +#define gmtime_r(x, y) gmtime_s((y), (x)) +#endif + +FTPEntryType parseOneFTPLine(const char* line, ListState& state, ListResult& result) +{ + result.clear(); + + if (!line) + return FTPJunkEntry; + + state.numLines++; + + /* carry buffer is only valid from one line to the next */ + unsigned int carry_buf_len = state.carryBufferLength; + state.carryBufferLength = 0; + + unsigned linelen = 0; + + /* strip leading whitespace */ + while (*line == ' ' || *line == '\t') + line++; + + /* line is terminated at first '\0' or '\n' */ + const char* p = line; + while (*p && *p != '\n') + p++; + linelen = p - line; + + if (linelen > 0 && *p == '\n' && *(p-1) == '\r') + linelen--; + + /* DON'T strip trailing whitespace. */ + + if (linelen > 0) + { + static const char *month_names = "JanFebMarAprMayJunJulAugSepOctNovDec"; + const char *tokens[16]; /* 16 is more than enough */ + unsigned int toklen[(sizeof(tokens)/sizeof(tokens[0]))]; + unsigned int linelen_sans_wsp; // line length sans whitespace + unsigned int numtoks = 0; + unsigned int tokmarker = 0; /* extra info for lstyle handler */ + unsigned int month_num = 0; + char tbuf[4]; + int lstyle = 0; + + if (carry_buf_len) /* VMS long filename carryover buffer */ + { + tokens[0] = state.carryBuffer; + toklen[0] = carry_buf_len; + numtoks++; + } + + unsigned int pos = 0; + while (pos < linelen && numtoks < (sizeof(tokens)/sizeof(tokens[0])) ) + { + while (pos < linelen && + (line[pos] == ' ' || line[pos] == '\t' || line[pos] == '\r')) + pos++; + if (pos < linelen) + { + tokens[numtoks] = &line[pos]; + while (pos < linelen && + (line[pos] != ' ' && line[pos] != '\t' && line[pos] != '\r')) + pos++; + if (tokens[numtoks] != &line[pos]) + { + toklen[numtoks] = (&line[pos] - tokens[numtoks]); + numtoks++; + } + } + } + + linelen_sans_wsp = &(tokens[numtoks-1][toklen[numtoks-1]]) - tokens[0]; + if (numtoks == (sizeof(tokens)/sizeof(tokens[0])) ) + { + pos = linelen; + while (pos > 0 && (line[pos-1] == ' ' || line[pos-1] == '\t')) + pos--; + linelen_sans_wsp = pos; + } + + /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ +#if defined(SUPPORT_EPLF) + /* EPLF handling must come somewhere before /bin/dls handling. */ + if (!lstyle && (!state.listStyle || state.listStyle == 'E')) + { + if (*line == '+' && linelen > 4 && numtoks >= 2) + { + pos = 1; + while (pos < (linelen-1)) + { + p = &line[pos++]; + if (*p == '/') + result.type = FTPDirectoryEntry; /* its a dir */ + else if (*p == 'r') + result.type = FTPFileEntry; /* its a file */ + else if (*p == 'm') + { + if (isASCIIDigit(line[pos])) + { + while (pos < linelen && isASCIIDigit(line[pos])) + pos++; + if (pos < linelen && line[pos] == ',') + { + unsigned long long seconds = 0; + sscanf(p + 1, "%llu", &seconds); + time_t t = static_cast<time_t>(seconds); + + // FIXME: This code has the year 2038 bug + gmtime_r(&t, &result.modifiedTime); + result.modifiedTime.tm_year += 1900; + } + } + } + else if (*p == 's') + { + if (isASCIIDigit(line[pos])) + { + while (pos < linelen && isASCIIDigit(line[pos])) + pos++; + if (pos < linelen && line[pos] == ',') + result.fileSize = String(p + 1, &line[pos] - p + 1); + } + } + else if (isASCIIAlpha(*p)) /* 'i'/'up' or unknown "fact" (property) */ + { + while (pos < linelen && *++p != ',') + pos++; + } + else if (*p != '\t' || (p+1) != tokens[1]) + { + break; /* its not EPLF after all */ + } + else + { + state.parsedOne = true; + state.listStyle = lstyle = 'E'; + + p = &(line[linelen_sans_wsp]); + result.filename = tokens[1]; + result.filenameLength = p - tokens[1]; + + if (!result.type) /* access denied */ + { + result.type = FTPFileEntry; /* is assuming 'f'ile correct? */ + return FTPJunkEntry; /* NO! junk it. */ + } + return result.type; + } + if (pos >= (linelen-1) || line[pos] != ',') + break; + pos++; + } /* while (pos < linelen) */ + result.clear(); + } /* if (*line == '+' && linelen > 4 && numtoks >= 2) */ + } /* if (!lstyle && (!state.listStyle || state.listStyle == 'E')) */ +#endif /* SUPPORT_EPLF */ + + /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ + +#if defined(SUPPORT_VMS) + if (!lstyle && (!state.listStyle || state.listStyle == 'V')) + { /* try VMS Multinet/UCX/CMS server */ + /* + * Legal characters in a VMS file/dir spec are [A-Z0-9$.-_~]. + * '$' cannot begin a filename and `-' cannot be used as the first + * or last character. '.' is only valid as a directory separator + * and <file>.<type> separator. A canonical filename spec might look + * like this: DISK$VOL:[DIR1.DIR2.DIR3]FILE.TYPE;123 + * All VMS FTP servers LIST in uppercase. + * + * We need to be picky about this in order to support + * multi-line listings correctly. + */ + if (!state.parsedOne && + (numtoks == 1 || (numtoks == 2 && toklen[0] == 9 && + memcmp(tokens[0], "Directory", 9)==0 ))) + { + /* If no dirstyle has been detected yet, and this line is a + * VMS list's dirname, then turn on VMS dirstyle. + * eg "ACA:[ANONYMOUS]", "DISK$FTP:[ANONYMOUS]", "SYS$ANONFTP:" + */ + p = tokens[0]; + pos = toklen[0]; + if (numtoks == 2) + { + p = tokens[1]; + pos = toklen[1]; + } + pos--; + if (pos >= 3) + { + while (pos > 0 && p[pos] != '[') + { + pos--; + if (p[pos] == '-' || p[pos] == '$') + { + if (pos == 0 || p[pos-1] == '[' || p[pos-1] == '.' || + (p[pos] == '-' && (p[pos+1] == ']' || p[pos+1] == '.'))) + break; + } + else if (p[pos] != '.' && p[pos] != '~' && + !isASCIIDigit(p[pos]) && !isASCIIAlpha(p[pos])) + break; + else if (isASCIIAlpha(p[pos]) && p[pos] != toASCIIUpper(p[pos])) + break; + } + if (pos > 0) + { + pos--; + if (p[pos] != ':' || p[pos+1] != '[') + pos = 0; + } + } + if (pos > 0 && p[pos] == ':') + { + while (pos > 0) + { + pos--; + if (p[pos] != '$' && p[pos] != '_' && p[pos] != '-' && + p[pos] != '~' && !isASCIIDigit(p[pos]) && !isASCIIAlpha(p[pos])) + break; + else if (isASCIIAlpha(p[pos]) && p[pos] != toASCIIUpper(p[pos])) + break; + } + if (pos == 0) + { + state.listStyle = 'V'; + return FTPJunkEntry; /* its junk */ + } + } + /* fallthrough */ + } + else if ((tokens[0][toklen[0]-1]) != ';') + { + if (numtoks == 1 && (state.listStyle == 'V' && !carry_buf_len)) + lstyle = 'V'; + else if (numtoks < 4) + ; + else if (toklen[1] >= 10 && memcmp(tokens[1], "%RMS-E-PRV", 10) == 0) + lstyle = 'V'; + else if ((&line[linelen] - tokens[1]) >= 22 && + memcmp(tokens[1], "insufficient privilege", 22) == 0) + lstyle = 'V'; + else if (numtoks != 4 && numtoks != 6) + ; + else if (numtoks == 6 && ( + toklen[5] < 4 || *tokens[5] != '(' || /* perms */ + (tokens[5][toklen[5]-1]) != ')' )) + ; + else if ( (toklen[2] == 10 || toklen[2] == 11) && + (tokens[2][toklen[2]-5]) == '-' && + (tokens[2][toklen[2]-9]) == '-' && + (((toklen[3]==4 || toklen[3]==5 || toklen[3]==7 || toklen[3]==8) && + (tokens[3][toklen[3]-3]) == ':' ) || + ((toklen[3]==10 || toklen[3]==11 ) && + (tokens[3][toklen[3]-3]) == '.' ) + ) && /* time in [H]H:MM[:SS[.CC]] format */ + isASCIIDigit(*tokens[1]) && /* size */ + isASCIIDigit(*tokens[2]) && /* date */ + isASCIIDigit(*tokens[3]) /* time */ + ) + { + lstyle = 'V'; + } + if (lstyle == 'V') + { + /* + * MultiNet FTP: + * LOGIN.COM;2 1 4-NOV-1994 04:09 [ANONYMOUS] (RWE,RWE,,) + * PUB.DIR;1 1 27-JAN-1994 14:46 [ANONYMOUS] (RWE,RWE,RE,RWE) + * README.FTP;1 %RMS-E-PRV, insufficient privilege or file protection violation + * ROUSSOS.DIR;1 1 27-JAN-1994 14:48 [CS,ROUSSOS] (RWE,RWE,RE,R) + * S67-50903.JPG;1 328 22-SEP-1998 16:19 [ANONYMOUS] (RWED,RWED,,) + * UCX FTP: + * CII-MANUAL.TEX;1 213/216 29-JAN-1996 03:33:12 [ANONYMOU,ANONYMOUS] (RWED,RWED,,) + * CMU/VMS-IP FTP + * [VMSSERV.FILES]ALARM.DIR;1 1/3 5-MAR-1993 18:09 + * TCPware FTP + * FOO.BAR;1 4 5-MAR-1993 18:09:01.12 + * Long filename example: + * THIS-IS-A-LONG-VMS-FILENAME.AND-THIS-IS-A-LONG-VMS-FILETYPE\r\n + * 213[/nnn] 29-JAN-1996 03:33[:nn] [ANONYMOU,ANONYMOUS] (RWED,RWED,,) + */ + tokmarker = 0; + p = tokens[0]; + pos = 0; + if (*p == '[' && toklen[0] >= 4) /* CMU style */ + { + if (p[1] != ']') + { + p++; + pos++; + } + while (lstyle && pos < toklen[0] && *p != ']') + { + if (*p != '$' && *p != '.' && *p != '_' && *p != '-' && + *p != '~' && !isASCIIDigit(*p) && !isASCIIAlpha(*p)) + lstyle = 0; + pos++; + p++; + } + if (lstyle && pos < (toklen[0]-1) && *p == ']') + { + pos++; + p++; + tokmarker = pos; /* length of leading "[DIR1.DIR2.etc]" */ + } + } + while (lstyle && pos < toklen[0] && *p != ';') + { + if (*p != '$' && *p != '.' && *p != '_' && *p != '-' && + *p != '~' && !isASCIIDigit(*p) && !isASCIIAlpha(*p)) + lstyle = 0; + else if (isASCIIAlpha(*p) && *p != toASCIIUpper(*p)) + lstyle = 0; + p++; + pos++; + } + if (lstyle && *p == ';') + { + if (pos == 0 || pos == (toklen[0]-1)) + lstyle = 0; + for (pos++;lstyle && pos < toklen[0];pos++) + { + if (!isASCIIDigit(tokens[0][pos])) + lstyle = 0; + } + } + pos = (p - tokens[0]); /* => fnlength sans ";####" */ + pos -= tokmarker; /* => fnlength sans "[DIR1.DIR2.etc]" */ + p = &(tokens[0][tokmarker]); /* offset of basename */ + + if (!lstyle || pos > 80) /* VMS filenames can't be longer than that */ + { + lstyle = 0; + } + else if (numtoks == 1) + { + /* if VMS has been detected and there is only one token and that + * token was a VMS filename then this is a multiline VMS LIST entry. + */ + if (pos >= (sizeof(state.carryBuffer)-1)) + pos = (sizeof(state.carryBuffer)-1); /* shouldn't happen */ + memcpy( state.carryBuffer, p, pos ); + state.carryBufferLength = pos; + return FTPJunkEntry; /* tell caller to treat as junk */ + } + else if (isASCIIDigit(*tokens[1])) /* not no-privs message */ + { + for (pos = 0; lstyle && pos < (toklen[1]); pos++) + { + if (!isASCIIDigit((tokens[1][pos])) && (tokens[1][pos]) != '/') + lstyle = 0; + } + if (lstyle && numtoks > 4) /* Multinet or UCX but not CMU */ + { + for (pos = 1; lstyle && pos < (toklen[5]-1); pos++) + { + p = &(tokens[5][pos]); + if (*p!='R' && *p!='W' && *p!='E' && *p!='D' && *p!=',') + lstyle = 0; + } + } + } + } /* passed initial tests */ + } /* else if ((tokens[0][toklen[0]-1]) != ';') */ + + if (lstyle == 'V') + { + state.parsedOne = true; + state.listStyle = lstyle; + + if (isASCIIDigit(*tokens[1])) /* not permission denied etc */ + { + /* strip leading directory name */ + if (*tokens[0] == '[') /* CMU server */ + { + pos = toklen[0]-1; + p = tokens[0]+1; + while (*p != ']') + { + p++; + pos--; + } + toklen[0] = --pos; + tokens[0] = ++p; + } + pos = 0; + while (pos < toklen[0] && (tokens[0][pos]) != ';') + pos++; + + result.caseSensitive = true; + result.type = FTPFileEntry; + result.filename = tokens[0]; + result.filenameLength = pos; + + if (pos > 4) + { + p = &(tokens[0][pos-4]); + if (p[0] == '.' && p[1] == 'D' && p[2] == 'I' && p[3] == 'R') + { + result.filenameLength -= 4; + result.type = FTPDirectoryEntry; + } + } + + if (result.type != FTPDirectoryEntry) + { + /* #### or used/allocated form. If used/allocated form, then + * 'used' is the size in bytes if and only if 'used'<=allocated. + * If 'used' is size in bytes then it can be > 2^32 + * If 'used' is not size in bytes then it is size in blocks. + */ + pos = 0; + while (pos < toklen[1] && (tokens[1][pos]) != '/') + pos++; + +/* + * I've never seen size come back in bytes, its always in blocks, and + * the following test fails. So, always perform the "size in blocks". + * I'm leaving the "size in bytes" code if'd out in case we ever need + * to re-instate it. +*/ +#if 0 + if (pos < toklen[1] && ( (pos<<1) > (toklen[1]-1) || + (strtoul(tokens[1], (char **)0, 10) > + strtoul(tokens[1]+pos+1, (char **)0, 10)) )) + { /* size is in bytes */ + if (pos > (sizeof(result.fe_size)-1)) + pos = sizeof(result.fe_size)-1; + memcpy( result.fe_size, tokens[1], pos ); + result.fe_size[pos] = '\0'; + } + else /* size is in blocks */ +#endif + { + /* size requires multiplication by blocksize. + * + * We could assume blocksize is 512 (like Lynx does) and + * shift by 9, but that might not be right. Even if it + * were, doing that wouldn't reflect what the file's + * real size was. The sanest thing to do is not use the + * LISTing's filesize, so we won't (like ftpmirror). + * + * ulltoa(((unsigned long long)fsz)<<9, result.fe_size, 10); + * + * A block is always 512 bytes on OpenVMS, compute size. + * So its rounded up to the next block, so what, its better + * than not showing the size at all. + * A block is always 512 bytes on OpenVMS, compute size. + * So its rounded up to the next block, so what, its better + * than not showing the size at all. + */ + uint64_t size = strtoul(tokens[1], NULL, 10) * 512; + result.fileSize = String::number(size); + } + + } /* if (result.type != FTPDirectoryEntry) */ + + p = tokens[2] + 2; + if (*p == '-') + p++; + tbuf[0] = p[0]; + tbuf[1] = toASCIILower(p[1]); + tbuf[2] = toASCIILower(p[2]); + month_num = 0; + for (pos = 0; pos < (12*3); pos+=3) + { + if (tbuf[0] == month_names[pos+0] && + tbuf[1] == month_names[pos+1] && + tbuf[2] == month_names[pos+2]) + break; + month_num++; + } + if (month_num >= 12) + month_num = 0; + result.modifiedTime.tm_mon = month_num; + result.modifiedTime.tm_mday = atoi(tokens[2]); + result.modifiedTime.tm_year = atoi(p+4); // NSPR wants year as XXXX + + p = tokens[3] + 2; + if (*p == ':') + p++; + if (p[2] == ':') + result.modifiedTime.tm_sec = atoi(p+3); + result.modifiedTime.tm_hour = atoi(tokens[3]); + result.modifiedTime.tm_min = atoi(p); + + return result.type; + + } /* if (isASCIIDigit(*tokens[1])) */ + + return FTPJunkEntry; /* junk */ + + } /* if (lstyle == 'V') */ + } /* if (!lstyle && (!state.listStyle || state.listStyle == 'V')) */ +#endif + + /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ + +#if defined(SUPPORT_CMS) + /* Virtual Machine/Conversational Monitor System (IBM Mainframe) */ + if (!lstyle && (!state.listStyle || state.listStyle == 'C')) /* VM/CMS */ + { + /* LISTing according to mirror.pl + * Filename FileType Fm Format Lrecl Records Blocks Date Time + * LASTING GLOBALV A1 V 41 21 1 9/16/91 15:10:32 + * J43401 NETLOG A0 V 77 1 1 9/12/91 12:36:04 + * PROFILE EXEC A1 V 17 3 1 9/12/91 12:39:07 + * DIRUNIX SCRIPT A1 V 77 1216 17 1/04/93 20:30:47 + * MAIL PROFILE A2 F 80 1 1 10/14/92 16:12:27 + * BADY2K TEXT A0 V 1 1 1 1/03/102 10:11:12 + * AUTHORS A1 DIR - - - 9/20/99 10:31:11 + * + * LISTing from vm.marist.edu and vm.sc.edu + * 220-FTPSERVE IBM VM Level 420 at VM.MARIST.EDU, 04:58:12 EDT WEDNESDAY 2002-07-10 + * AUTHORS DIR - - - 1999-09-20 10:31:11 - + * HARRINGTON DIR - - - 1997-02-12 15:33:28 - + * PICS DIR - - - 2000-10-12 15:43:23 - + * SYSFILE DIR - - - 2000-07-20 17:48:01 - + * WELCNVT EXEC V 72 9 1 1999-09-20 17:16:18 - + * WELCOME EREADME F 80 21 1 1999-12-27 16:19:00 - + * WELCOME README V 82 21 1 1999-12-27 16:19:04 - + * README ANONYMOU V 71 26 1 1997-04-02 12:33:20 TCP291 + * README ANONYOLD V 71 15 1 1995-08-25 16:04:27 TCP291 + */ + if (numtoks >= 7 && (toklen[0]+toklen[1]) <= 16) + { + for (pos = 1; !lstyle && (pos+5) < numtoks; pos++) + { + p = tokens[pos]; + if ((toklen[pos] == 1 && (*p == 'F' || *p == 'V')) || + (toklen[pos] == 3 && *p == 'D' && p[1] == 'I' && p[2] == 'R')) + { + if (toklen[pos+5] == 8 && (tokens[pos+5][2]) == ':' && + (tokens[pos+5][5]) == ':' ) + { + p = tokens[pos+4]; + if ((toklen[pos+4] == 10 && p[4] == '-' && p[7] == '-') || + (toklen[pos+4] >= 7 && toklen[pos+4] <= 9 && + p[((p[1]!='/')?(2):(1))] == '/' && + p[((p[1]!='/')?(5):(4))] == '/')) + /* Y2K bugs possible ("7/06/102" or "13/02/101") */ + { + if ( (*tokens[pos+1] == '-' && + *tokens[pos+2] == '-' && + *tokens[pos+3] == '-') || + (isASCIIDigit(*tokens[pos+1]) && + isASCIIDigit(*tokens[pos+2]) && + isASCIIDigit(*tokens[pos+3])) ) + { + lstyle = 'C'; + tokmarker = pos; + } + } + } + } + } /* for (pos = 1; !lstyle && (pos+5) < numtoks; pos++) */ + } /* if (numtoks >= 7) */ + + /* extra checking if first pass */ + if (lstyle && !state.listStyle) + { + for (pos = 0, p = tokens[0]; lstyle && pos < toklen[0]; pos++, p++) + { + if (isASCIIAlpha(*p) && toASCIIUpper(*p) != *p) + lstyle = 0; + } + for (pos = tokmarker+1; pos <= tokmarker+3; pos++) + { + if (!(toklen[pos] == 1 && *tokens[pos] == '-')) + { + for (p = tokens[pos]; lstyle && p<(tokens[pos]+toklen[pos]); p++) + { + if (!isASCIIDigit(*p)) + lstyle = 0; + } + } + } + for (pos = 0, p = tokens[tokmarker+4]; + lstyle && pos < toklen[tokmarker+4]; pos++, p++) + { + if (*p == '/') + { + /* There may be Y2K bugs in the date. Don't simplify to + * pos != (len-3) && pos != (len-6) like time is done. + */ + if ((tokens[tokmarker+4][1]) == '/') + { + if (pos != 1 && pos != 4) + lstyle = 0; + } + else if (pos != 2 && pos != 5) + lstyle = 0; + } + else if (*p != '-' && !isASCIIDigit(*p)) + lstyle = 0; + else if (*p == '-' && pos != 4 && pos != 7) + lstyle = 0; + } + for (pos = 0, p = tokens[tokmarker+5]; + lstyle && pos < toklen[tokmarker+5]; pos++, p++) + { + if (*p != ':' && !isASCIIDigit(*p)) + lstyle = 0; + else if (*p == ':' && pos != (toklen[tokmarker+5]-3) + && pos != (toklen[tokmarker+5]-6)) + lstyle = 0; + } + } /* initial if() */ + + if (lstyle == 'C') + { + state.parsedOne = true; + state.listStyle = lstyle; + + p = tokens[tokmarker+4]; + if (toklen[tokmarker+4] == 10) /* newstyle: YYYY-MM-DD format */ + { + result.modifiedTime.tm_year = atoi(p+0) - 1900; + result.modifiedTime.tm_mon = atoi(p+5) - 1; + result.modifiedTime.tm_mday = atoi(p+8); + } + else /* oldstyle: [M]M/DD/YY format */ + { + pos = toklen[tokmarker+4]; + result.modifiedTime.tm_mon = atoi(p) - 1; + result.modifiedTime.tm_mday = atoi((p+pos)-5); + result.modifiedTime.tm_year = atoi((p+pos)-2); + if (result.modifiedTime.tm_year < 70) + result.modifiedTime.tm_year += 100; + } + + p = tokens[tokmarker+5]; + pos = toklen[tokmarker+5]; + result.modifiedTime.tm_hour = atoi(p); + result.modifiedTime.tm_min = atoi((p+pos)-5); + result.modifiedTime.tm_sec = atoi((p+pos)-2); + + result.caseSensitive = true; + result.filename = tokens[0]; + result.filenameLength = toklen[0]; + result.type = FTPFileEntry; + + p = tokens[tokmarker]; + if (toklen[tokmarker] == 3 && *p=='D' && p[1]=='I' && p[2]=='R') + result.type = FTPDirectoryEntry; + + if ((/*newstyle*/ toklen[tokmarker+4] == 10 && tokmarker > 1) || + (/*oldstyle*/ toklen[tokmarker+4] != 10 && tokmarker > 2)) + { /* have a filetype column */ + char *dot; + p = &(tokens[0][toklen[0]]); + memcpy( &dot, &p, sizeof(dot) ); /* NASTY! */ + *dot++ = '.'; + p = tokens[1]; + for (pos = 0; pos < toklen[1]; pos++) + *dot++ = *p++; + result.filenameLength += 1 + toklen[1]; + } + + /* oldstyle LISTING: + * files/dirs not on the 'A' minidisk are not RETRievable/CHDIRable + if (toklen[tokmarker+4] != 10 && *tokens[tokmarker-1] != 'A') + return FTPJunkEntry; + */ + + /* VM/CMS LISTings have no usable filesize field. + * Have to use the 'SIZE' command for that. + */ + return result.type; + + } /* if (lstyle == 'C' && (!state.listStyle || state.listStyle == lstyle)) */ + } /* VM/CMS */ +#endif + + /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ + +#if defined(SUPPORT_DOS) /* WinNT DOS dirstyle */ + if (!lstyle && (!state.listStyle || state.listStyle == 'W')) + { + /* + * "10-23-00 01:27PM <DIR> veronist" + * "06-15-00 07:37AM <DIR> zoe" + * "07-14-00 01:35PM 2094926 canprankdesk.tif" + * "07-21-00 01:19PM 95077 Jon Kauffman Enjoys the Good Life.jpg" + * "07-21-00 01:19PM 52275 Name Plate.jpg" + * "07-14-00 01:38PM 2250540 Valentineoffprank-HiRes.jpg" + */ + if ((numtoks >= 4) && toklen[0] == 8 && toklen[1] == 7 && + (*tokens[2] == '<' || isASCIIDigit(*tokens[2])) ) + { + p = tokens[0]; + if ( isASCIIDigit(p[0]) && isASCIIDigit(p[1]) && p[2]=='-' && + isASCIIDigit(p[3]) && isASCIIDigit(p[4]) && p[5]=='-' && + isASCIIDigit(p[6]) && isASCIIDigit(p[7]) ) + { + p = tokens[1]; + if ( isASCIIDigit(p[0]) && isASCIIDigit(p[1]) && p[2]==':' && + isASCIIDigit(p[3]) && isASCIIDigit(p[4]) && + (p[5]=='A' || p[5]=='P') && p[6]=='M') + { + lstyle = 'W'; + if (!state.listStyle) + { + p = tokens[2]; + /* <DIR> or <JUNCTION> */ + if (*p != '<' || p[toklen[2]-1] != '>') + { + for (pos = 1; (lstyle && pos < toklen[2]); pos++) + { + if (!isASCIIDigit(*++p)) + lstyle = 0; + } + } + } + } + } + } + + if (lstyle == 'W') + { + state.parsedOne = true; + state.listStyle = lstyle; + + p = &(line[linelen_sans_wsp]); /* line end sans wsp */ + result.caseSensitive = true; + result.filename = tokens[3]; + result.filenameLength = p - tokens[3]; + result.type = FTPDirectoryEntry; + + if (*tokens[2] != '<') /* not <DIR> or <JUNCTION> */ + { + result.type = FTPFileEntry; + pos = toklen[2]; + result.fileSize = String(tokens[2], pos); + } + else if ((tokens[2][1]) != 'D') /* not <DIR> */ + { + result.type = FTPJunkEntry; /* unknown until junc for sure */ + if (result.filenameLength > 4) + { + p = result.filename; + for (pos = result.filenameLength - 4; pos > 0; pos--) + { + if (p[0] == ' ' && p[3] == ' ' && p[2] == '>' && + (p[1] == '=' || p[1] == '-')) + { + result.type = FTPLinkEntry; + result.filenameLength = p - result.filename; + result.linkname = p + 4; + result.linknameLength = &(line[linelen_sans_wsp]) + - result.linkname; + break; + } + p++; + } + } + } + + result.modifiedTime.tm_mon = atoi(tokens[0]+0); + if (result.modifiedTime.tm_mon != 0) + { + result.modifiedTime.tm_mon--; + result.modifiedTime.tm_mday = atoi(tokens[0]+3); + result.modifiedTime.tm_year = atoi(tokens[0]+6); + if (result.modifiedTime.tm_year < 80) + result.modifiedTime.tm_year += 100; + } + + result.modifiedTime.tm_hour = atoi(tokens[1]+0); + result.modifiedTime.tm_min = atoi(tokens[1]+3); + if ((tokens[1][5]) == 'P' && result.modifiedTime.tm_hour < 12) + result.modifiedTime.tm_hour += 12; + + /* the caller should do this (if dropping "." and ".." is desired) + if (result.type == FTPDirectoryEntry && result.filename[0] == '.' && + (result.filenameLength == 1 || (result.filenameLength == 2 && + result.filename[1] == '.'))) + return FTPJunkEntry; + */ + + return result.type; + } /* if (lstyle == 'W' && (!state.listStyle || state.listStyle == lstyle)) */ + } /* if (!lstyle && (!state.listStyle || state.listStyle == 'W')) */ +#endif + + /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ + +#if defined(SUPPORT_OS2) + if (!lstyle && (!state.listStyle || state.listStyle == 'O')) /* OS/2 test */ + { + /* 220 server IBM TCP/IP for OS/2 - FTP Server ver 23:04:36 on Jan 15 1997 ready. + * fixed position, space padded columns. I have only a vague idea + * of what the contents between col 18 and 34 might be: All I can infer + * is that there may be attribute flags in there and there may be + * a " DIR" in there. + * + * 1 2 3 4 5 6 + *0123456789012345678901234567890123456789012345678901234567890123456789 + *----- size -------|??????????????? MM-DD-YY| HH:MM| nnnnnnnnn.... + * 0 DIR 04-11-95 16:26 . + * 0 DIR 04-11-95 16:26 .. + * 0 DIR 04-11-95 16:26 ADDRESS + * 612 RHSA 07-28-95 16:45 air_tra1.bag + * 195 A 08-09-95 10:23 Alfa1.bag + * 0 RHS DIR 04-11-95 16:26 ATTACH + * 372 A 08-09-95 10:26 Aussie_1.bag + * 310992 06-28-94 09:56 INSTALL.EXE + * 1 2 3 4 + * 01234567890123456789012345678901234567890123456789 + * dirlist from the mirror.pl project, col positions from Mozilla. + */ + p = &(line[toklen[0]]); + /* \s(\d\d-\d\d-\d\d)\s+(\d\d:\d\d)\s */ + if (numtoks >= 4 && toklen[0] <= 18 && isASCIIDigit(*tokens[0]) && + (linelen - toklen[0]) >= (53-18) && + p[18-18] == ' ' && p[34-18] == ' ' && + p[37-18] == '-' && p[40-18] == '-' && p[43-18] == ' ' && + p[45-18] == ' ' && p[48-18] == ':' && p[51-18] == ' ' && + isASCIIDigit(p[35-18]) && isASCIIDigit(p[36-18]) && + isASCIIDigit(p[38-18]) && isASCIIDigit(p[39-18]) && + isASCIIDigit(p[41-18]) && isASCIIDigit(p[42-18]) && + isASCIIDigit(p[46-18]) && isASCIIDigit(p[47-18]) && + isASCIIDigit(p[49-18]) && isASCIIDigit(p[50-18]) + ) + { + lstyle = 'O'; /* OS/2 */ + if (!state.listStyle) + { + for (pos = 1; lstyle && pos < toklen[0]; pos++) + { + if (!isASCIIDigit(tokens[0][pos])) + lstyle = 0; + } + } + } + + if (lstyle == 'O') + { + state.parsedOne = true; + state.listStyle = lstyle; + + p = &(line[toklen[0]]); + + result.caseSensitive = true; + result.filename = &p[53-18]; + result.filenameLength = (&(line[linelen_sans_wsp])) + - (result.filename); + result.type = FTPFileEntry; + + /* I don't have a real listing to determine exact pos, so scan. */ + for (pos = (18-18); pos < ((35-18)-4); pos++) + { + if (p[pos+0] == ' ' && p[pos+1] == 'D' && + p[pos+2] == 'I' && p[pos+3] == 'R') + { + result.type = FTPDirectoryEntry; + break; + } + } + + if (result.type != FTPDirectoryEntry) + { + pos = toklen[0]; + result.fileSize = String(tokens[0], pos); + } + + result.modifiedTime.tm_mon = atoi(&p[35-18]) - 1; + result.modifiedTime.tm_mday = atoi(&p[38-18]); + result.modifiedTime.tm_year = atoi(&p[41-18]); + if (result.modifiedTime.tm_year < 80) + result.modifiedTime.tm_year += 100; + result.modifiedTime.tm_hour = atoi(&p[46-18]); + result.modifiedTime.tm_min = atoi(&p[49-18]); + + /* the caller should do this (if dropping "." and ".." is desired) + if (result.type == FTPDirectoryEntry && result.filename[0] == '.' && + (result.filenameLength == 1 || (result.filenameLength == 2 && + result.filename[1] == '.'))) + return FTPJunkEntry; + */ + + return result.type; + } /* if (lstyle == 'O') */ + + } /* if (!lstyle && (!state.listStyle || state.listStyle == 'O')) */ +#endif + + /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ + +#if defined(SUPPORT_LSL) + if (!lstyle && (!state.listStyle || state.listStyle == 'U')) /* /bin/ls & co. */ + { + /* UNIX-style listing, without inum and without blocks + * "-rw-r--r-- 1 root other 531 Jan 29 03:26 README" + * "dr-xr-xr-x 2 root other 512 Apr 8 1994 etc" + * "dr-xr-xr-x 2 root 512 Apr 8 1994 etc" + * "lrwxrwxrwx 1 root other 7 Jan 25 00:17 bin -> usr/bin" + * Also produced by Microsoft's FTP servers for Windows: + * "---------- 1 owner group 1803128 Jul 10 10:18 ls-lR.Z" + * "d--------- 1 owner group 0 May 9 19:45 Softlib" + * Also WFTPD for MSDOS: + * "-rwxrwxrwx 1 noone nogroup 322 Aug 19 1996 message.ftp" + * Hellsoft for NetWare: + * "d[RWCEMFA] supervisor 512 Jan 16 18:53 login" + * "-[RWCEMFA] rhesus 214059 Oct 20 15:27 cx.exe" + * Newer Hellsoft for NetWare: (netlab2.usu.edu) + * - [RWCEAFMS] NFAUUser 192 Apr 27 15:21 HEADER.html + * d [RWCEAFMS] jrd 512 Jul 11 03:01 allupdates + * Also NetPresenz for the Mac: + * "-------r-- 326 1391972 1392298 Nov 22 1995 MegaPhone.sit" + * "drwxrwxr-x folder 2 May 10 1996 network" + * Protected directory: + * "drwx-wx-wt 2 root wheel 512 Jul 1 02:15 incoming" + * uid/gid instead of username/groupname: + * "drwxr-xr-x 2 0 0 512 May 28 22:17 etc" + */ + + if (numtoks >= 6) + { + /* there are two perm formats (Hellsoft/NetWare and *IX strmode(3)). + * Scan for size column only if the perm format is one or the other. + */ + if (toklen[0] == 1 || (tokens[0][1]) == '[') + { + if (*tokens[0] == 'd' || *tokens[0] == '-') + { + pos = toklen[0]-1; + p = tokens[0] + 1; + if (pos == 0) + { + p = tokens[1]; + pos = toklen[1]; + } + if ((pos == 9 || pos == 10) && + (*p == '[' && p[pos-1] == ']') && + (p[1] == 'R' || p[1] == '-') && + (p[2] == 'W' || p[2] == '-') && + (p[3] == 'C' || p[3] == '-') && + (p[4] == 'E' || p[4] == '-')) + { + /* rest is FMA[S] or AFM[S] */ + lstyle = 'U'; /* very likely one of the NetWare servers */ + } + } + } + else if ((toklen[0] == 10 || toklen[0] == 11) + && strchr("-bcdlpsw?DFam", *tokens[0])) + { + p = &(tokens[0][1]); + if ((p[0] == 'r' || p[0] == '-') && + (p[1] == 'w' || p[1] == '-') && + (p[3] == 'r' || p[3] == '-') && + (p[4] == 'w' || p[4] == '-') && + (p[6] == 'r' || p[6] == '-') && + (p[7] == 'w' || p[7] == '-')) + /* 'x'/p[9] can be S|s|x|-|T|t or implementation specific */ + { + lstyle = 'U'; /* very likely /bin/ls */ + } + } + } + if (lstyle == 'U') /* first token checks out */ + { + lstyle = 0; + for (pos = (numtoks-5); !lstyle && pos > 1; pos--) + { + /* scan for: (\d+)\s+([A-Z][a-z][a-z])\s+ + * (\d\d\d\d|\d\:\d\d|\d\d\:\d\d|\d\:\d\d\:\d\d|\d\d\:\d\d\:\d\d) + * \s+(.+)$ + */ + if (isASCIIDigit(*tokens[pos]) /* size */ + /* (\w\w\w) */ + && toklen[pos+1] == 3 && isASCIIAlpha(*tokens[pos+1]) && + isASCIIAlpha(tokens[pos+1][1]) && isASCIIAlpha(tokens[pos+1][2]) + /* (\d|\d\d) */ + && isASCIIDigit(*tokens[pos+2]) && + (toklen[pos+2] == 1 || + (toklen[pos+2] == 2 && isASCIIDigit(tokens[pos+2][1]))) + && toklen[pos+3] >= 4 && isASCIIDigit(*tokens[pos+3]) + /* (\d\:\d\d\:\d\d|\d\d\:\d\d\:\d\d) */ + && (toklen[pos+3] <= 5 || ( + (toklen[pos+3] == 7 || toklen[pos+3] == 8) && + (tokens[pos+3][toklen[pos+3]-3]) == ':')) + && isASCIIDigit(tokens[pos+3][toklen[pos+3]-2]) + && isASCIIDigit(tokens[pos+3][toklen[pos+3]-1]) + && ( + /* (\d\d\d\d) */ + ((toklen[pos+3] == 4 || toklen[pos+3] == 5) && + isASCIIDigit(tokens[pos+3][1]) && + isASCIIDigit(tokens[pos+3][2]) ) + /* (\d\:\d\d|\d\:\d\d\:\d\d) */ + || ((toklen[pos+3] == 4 || toklen[pos+3] == 7) && + (tokens[pos+3][1]) == ':' && + isASCIIDigit(tokens[pos+3][2]) && isASCIIDigit(tokens[pos+3][3])) + /* (\d\d\:\d\d|\d\d\:\d\d\:\d\d) */ + || ((toklen[pos+3] == 5 || toklen[pos+3] == 8) && + isASCIIDigit(tokens[pos+3][1]) && (tokens[pos+3][2]) == ':' && + isASCIIDigit(tokens[pos+3][3]) && isASCIIDigit(tokens[pos+3][4])) + ) + ) + { + lstyle = 'U'; /* assume /bin/ls or variant format */ + tokmarker = pos; + + /* check that size is numeric */ + p = tokens[tokmarker]; + for (pos = 0; lstyle && pos < toklen[tokmarker]; pos++) + { + if (!isASCIIDigit(*p++)) + lstyle = 0; + } + if (lstyle) + { + month_num = 0; + p = tokens[tokmarker+1]; + for (pos = 0;pos < (12*3); pos+=3) + { + if (p[0] == month_names[pos+0] && + p[1] == month_names[pos+1] && + p[2] == month_names[pos+2]) + break; + month_num++; + } + if (month_num >= 12) + lstyle = 0; + } + } /* relative position test */ + } /* while (pos+5) < numtoks */ + } /* if (numtoks >= 4) */ + + if (lstyle == 'U') + { + state.parsedOne = true; + state.listStyle = lstyle; + + result.caseSensitive = false; + result.type = FTPJunkEntry; + if (*tokens[0] == 'd' || *tokens[0] == 'D') + result.type = FTPDirectoryEntry; + else if (*tokens[0] == 'l') + result.type = FTPLinkEntry; + else if (*tokens[0] == '-' || *tokens[0] == 'F') + result.type = FTPFileEntry; /* (hopefully a regular file) */ + + if (result.type != FTPDirectoryEntry) + { + pos = toklen[tokmarker]; + result.fileSize = String(tokens[tokmarker], pos); + } + + result.modifiedTime.tm_mon = month_num; + result.modifiedTime.tm_mday = atoi(tokens[tokmarker+2]); + if (result.modifiedTime.tm_mday == 0) + result.modifiedTime.tm_mday++; + + p = tokens[tokmarker+3]; + pos = (unsigned int)atoi(p); + if (p[1] == ':') /* one digit hour */ + p--; + if (p[2] != ':') /* year */ + { + result.modifiedTime.tm_year = pos; + } + else + { + result.modifiedTime.tm_hour = pos; + result.modifiedTime.tm_min = atoi(p+3); + if (p[5] == ':') + result.modifiedTime.tm_sec = atoi(p+6); + + if (!state.now) + { + time_t now = time(NULL); + state.now = now * 1000000.0; + + // FIXME: This code has the year 2038 bug + gmtime_r(&now, &state.nowFTPTime); + state.nowFTPTime.tm_year += 1900; + } + + result.modifiedTime.tm_year = state.nowFTPTime.tm_year; + if ( (( state.nowFTPTime.tm_mon << 5) + state.nowFTPTime.tm_mday) < + ((result.modifiedTime.tm_mon << 5) + result.modifiedTime.tm_mday) ) + result.modifiedTime.tm_year--; + + } /* time/year */ + + result.filename = tokens[tokmarker+4]; + result.filenameLength = (&(line[linelen_sans_wsp])) + - (result.filename); + + if (result.type == FTPLinkEntry && result.filenameLength > 4) + { + p = result.filename + 1; + for (pos = 1; pos < (result.filenameLength - 4); pos++) + { + if (*p == ' ' && p[1] == '-' && p[2] == '>' && p[3] == ' ') + { + result.linkname = p + 4; + result.linknameLength = (&(line[linelen_sans_wsp])) + - (result.linkname); + result.filenameLength = pos; + break; + } + p++; + } + } + +#if defined(SUPPORT_LSLF) /* some (very rare) servers return ls -lF */ + if (result.filenameLength > 1) + { + p = result.filename[result.filenameLength-1]; + pos = result.type; + if (pos == 'd') { + if (*p == '/') result.filenameLength--; /* directory */ + } else if (pos == 'l') { + if (*p == '@') result.filenameLength--; /* symlink */ + } else if (pos == 'f') { + if (*p == '*') result.filenameLength--; /* executable */ + } else if (*p == '=' || *p == '%' || *p == '|') { + result.filenameLength--; /* socket, whiteout, fifo */ + } + } +#endif + + /* the caller should do this (if dropping "." and ".." is desired) + if (result.type == FTPDirectoryEntry && result.filename[0] == '.' && + (result.filenameLength == 1 || (result.filenameLength == 2 && + result.filename[1] == '.'))) + return FTPJunkEntry; + */ + + return result.type; + + } /* if (lstyle == 'U') */ + + } /* if (!lstyle && (!state.listStyle || state.listStyle == 'U')) */ +#endif + + /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ + +#if defined(SUPPORT_W16) /* 16bit Windows */ + if (!lstyle && (!state.listStyle || state.listStyle == 'w')) + { /* old SuperTCP suite FTP server for Win3.1 */ + /* old NetManage Chameleon TCP/IP suite FTP server for Win3.1 */ + /* + * SuperTCP dirlist from the mirror.pl project + * mon/day/year separator may be '/' or '-'. + * . <DIR> 11-16-94 17:16 + * .. <DIR> 11-16-94 17:16 + * INSTALL <DIR> 11-16-94 17:17 + * CMT <DIR> 11-21-94 10:17 + * DESIGN1.DOC 11264 05-11-95 14:20 + * README.TXT 1045 05-10-95 11:01 + * WPKIT1.EXE 960338 06-21-95 17:01 + * CMT.CSV 0 07-06-95 14:56 + * + * Chameleon dirlist guessed from lynx + * . <DIR> Nov 16 1994 17:16 + * .. <DIR> Nov 16 1994 17:16 + * INSTALL <DIR> Nov 16 1994 17:17 + * CMT <DIR> Nov 21 1994 10:17 + * DESIGN1.DOC 11264 May 11 1995 14:20 A + * README.TXT 1045 May 10 1995 11:01 + * WPKIT1.EXE 960338 Jun 21 1995 17:01 R + * CMT.CSV 0 Jul 06 1995 14:56 RHA + */ + if (numtoks >= 4 && toklen[0] < 13 && + ((toklen[1] == 5 && *tokens[1] == '<') || isASCIIDigit(*tokens[1])) ) + { + if (numtoks == 4 + && (toklen[2] == 8 || toklen[2] == 9) + && (((tokens[2][2]) == '/' && (tokens[2][5]) == '/') || + ((tokens[2][2]) == '-' && (tokens[2][5]) == '-')) + && (toklen[3] == 4 || toklen[3] == 5) + && (tokens[3][toklen[3]-3]) == ':' + && isASCIIDigit(tokens[2][0]) && isASCIIDigit(tokens[2][1]) + && isASCIIDigit(tokens[2][3]) && isASCIIDigit(tokens[2][4]) + && isASCIIDigit(tokens[2][6]) && isASCIIDigit(tokens[2][7]) + && (toklen[2] < 9 || isASCIIDigit(tokens[2][8])) + && isASCIIDigit(tokens[3][toklen[3]-1]) && isASCIIDigit(tokens[3][toklen[3]-2]) + && isASCIIDigit(tokens[3][toklen[3]-4]) && isASCIIDigit(*tokens[3]) + ) + { + lstyle = 'w'; + } + else if ((numtoks == 6 || numtoks == 7) + && toklen[2] == 3 && toklen[3] == 2 + && toklen[4] == 4 && toklen[5] == 5 + && (tokens[5][2]) == ':' + && isASCIIAlpha(tokens[2][0]) && isASCIIAlpha(tokens[2][1]) + && isASCIIAlpha(tokens[2][2]) + && isASCIIDigit(tokens[3][0]) && isASCIIDigit(tokens[3][1]) + && isASCIIDigit(tokens[4][0]) && isASCIIDigit(tokens[4][1]) + && isASCIIDigit(tokens[4][2]) && isASCIIDigit(tokens[4][3]) + && isASCIIDigit(tokens[5][0]) && isASCIIDigit(tokens[5][1]) + && isASCIIDigit(tokens[5][3]) && isASCIIDigit(tokens[5][4]) + /* could also check that (&(tokens[5][5]) - tokens[2]) == 17 */ + ) + { + lstyle = 'w'; + } + if (lstyle && state.listStyle != lstyle) /* first time */ + { + p = tokens[1]; + if (toklen[1] != 5 || p[0] != '<' || p[1] != 'D' || + p[2] != 'I' || p[3] != 'R' || p[4] != '>') + { + for (pos = 0; lstyle && pos < toklen[1]; pos++) + { + if (!isASCIIDigit(*p++)) + lstyle = 0; + } + } /* not <DIR> */ + } /* if (first time) */ + } /* if (numtoks == ...) */ + + if (lstyle == 'w') + { + state.parsedOne = true; + state.listStyle = lstyle; + + result.caseSensitive = true; + result.filename = tokens[0]; + result.filenameLength = toklen[0]; + result.type = FTPDirectoryEntry; + + p = tokens[1]; + if (isASCIIDigit(*p)) + { + result.type = FTPFileEntry; + pos = toklen[1]; + result.fileSize = String(p, pos); + } + + p = tokens[2]; + if (toklen[2] == 3) /* Chameleon */ + { + tbuf[0] = toASCIIUpper(p[0]); + tbuf[1] = toASCIILower(p[1]); + tbuf[2] = toASCIILower(p[2]); + for (pos = 0; pos < (12*3); pos+=3) + { + if (tbuf[0] == month_names[pos+0] && + tbuf[1] == month_names[pos+1] && + tbuf[2] == month_names[pos+2]) + { + result.modifiedTime.tm_mon = pos/3; + result.modifiedTime.tm_mday = atoi(tokens[3]); + result.modifiedTime.tm_year = atoi(tokens[4]) - 1900; + break; + } + } + pos = 5; /* Chameleon toknum of date field */ + } + else + { + result.modifiedTime.tm_mon = atoi(p+0)-1; + result.modifiedTime.tm_mday = atoi(p+3); + result.modifiedTime.tm_year = atoi(p+6); + if (result.modifiedTime.tm_year < 80) /* SuperTCP */ + result.modifiedTime.tm_year += 100; + + pos = 3; /* SuperTCP toknum of date field */ + } + + result.modifiedTime.tm_hour = atoi(tokens[pos]); + result.modifiedTime.tm_min = atoi(&(tokens[pos][toklen[pos]-2])); + + /* the caller should do this (if dropping "." and ".." is desired) + if (result.type == FTPDirectoryEntry && result.filename[0] == '.' && + (result.filenameLength == 1 || (result.filenameLength == 2 && + result.filename[1] == '.'))) + return FTPJunkEntry; + */ + + return result.type; + } /* (lstyle == 'w') */ + + } /* if (!lstyle && (!state.listStyle || state.listStyle == 'w')) */ +#endif + + /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ + +#if defined(SUPPORT_DLS) /* dls -dtR */ + if (!lstyle && + (state.listStyle == 'D' || (!state.listStyle && state.numLines == 1))) + /* /bin/dls lines have to be immediately recognizable (first line) */ + { + /* I haven't seen an FTP server that delivers a /bin/dls listing, + * but can infer the format from the lynx and mirror.pl projects. + * Both formats are supported. + * + * Lynx says: + * README 763 Information about this server\0 + * bin/ - \0 + * etc/ = \0 + * ls-lR 0 \0 + * ls-lR.Z 3 \0 + * pub/ = Public area\0 + * usr/ - \0 + * morgan 14 -> ../real/morgan\0 + * TIMIT.mostlikely.Z\0 + * 79215 \0 + * + * mirror.pl says: + * filename: ^(\S*)\s+ + * size: (\-|\=|\d+)\s+ + * month/day: ((\w\w\w\s+\d+|\d+\s+\w\w\w)\s+ + * time/year: (\d+:\d+|\d\d\d\d))\s+ + * rest: (.+) + * + * README 763 Jul 11 21:05 Information about this server + * bin/ - Apr 28 1994 + * etc/ = 11 Jul 21:04 + * ls-lR 0 6 Aug 17:14 + * ls-lR.Z 3 05 Sep 1994 + * pub/ = Jul 11 21:04 Public area + * usr/ - Sep 7 09:39 + * morgan 14 Apr 18 09:39 -> ../real/morgan + * TIMIT.mostlikely.Z + * 79215 Jul 11 21:04 + */ + if (!state.listStyle && line[linelen-1] == ':' && + linelen >= 2 && toklen[numtoks-1] != 1) + { + /* code in mirror.pl suggests that a listing may be preceded + * by a PWD line in the form "/some/dir/names/here:" + * but does not necessarily begin with '/'. *sigh* + */ + pos = 0; + p = line; + while (pos < (linelen-1)) + { + /* illegal (or extremely unusual) chars in a dirspec */ + if (*p == '<' || *p == '|' || *p == '>' || + *p == '?' || *p == '*' || *p == '\\') + break; + if (*p == '/' && pos < (linelen-2) && p[1] == '/') + break; + pos++; + p++; + } + if (pos == (linelen-1)) + { + state.listStyle = 'D'; + return FTPJunkEntry; + } + } + + if (!lstyle && numtoks >= 2) + { + pos = 22; /* pos of (\d+|-|=) if this is not part of a multiline */ + if (state.listStyle && carry_buf_len) /* first is from previous line */ + pos = toklen[1]-1; /* and is 'as-is' (may contain whitespace) */ + + if (linelen > pos) + { + p = &line[pos]; + if ((*p == '-' || *p == '=' || isASCIIDigit(*p)) && + ((linelen == (pos+1)) || + (linelen >= (pos+3) && p[1] == ' ' && p[2] == ' ')) ) + { + tokmarker = 1; + if (!carry_buf_len) + { + pos = 1; + while (pos < numtoks && (tokens[pos]+toklen[pos]) < (&line[23])) + pos++; + tokmarker = 0; + if ((tokens[pos]+toklen[pos]) == (&line[23])) + tokmarker = pos; + } + if (tokmarker) + { + lstyle = 'D'; + if (*tokens[tokmarker] == '-' || *tokens[tokmarker] == '=') + { + if (toklen[tokmarker] != 1 || + (tokens[tokmarker-1][toklen[tokmarker-1]-1]) != '/') + lstyle = 0; + } + else + { + for (pos = 0; lstyle && pos < toklen[tokmarker]; pos++) + { + if (!isASCIIDigit(tokens[tokmarker][pos])) + lstyle = 0; + } + } + if (lstyle && !state.listStyle) /* first time */ + { + /* scan for illegal (or incredibly unusual) chars in fname */ + for (p = tokens[0]; lstyle && + p < &(tokens[tokmarker-1][toklen[tokmarker-1]]); p++) + { + if (*p == '<' || *p == '|' || *p == '>' || + *p == '?' || *p == '*' || *p == '/' || *p == '\\') + lstyle = 0; + } + } + + } /* size token found */ + } /* expected chars behind expected size token */ + } /* if (linelen > pos) */ + } /* if (!lstyle && numtoks >= 2) */ + + if (!lstyle && state.listStyle == 'D' && !carry_buf_len) + { + /* the filename of a multi-line entry can be identified + * correctly only if dls format had been previously established. + * This should always be true because there should be entries + * for '.' and/or '..' and/or CWD that precede the rest of the + * listing. + */ + pos = linelen; + if (pos > (sizeof(state.carryBuffer)-1)) + pos = sizeof(state.carryBuffer)-1; + memcpy( state.carryBuffer, line, pos ); + state.carryBufferLength = pos; + return FTPJunkEntry; + } + + if (lstyle == 'D') + { + state.parsedOne = true; + state.listStyle = lstyle; + + p = &(tokens[tokmarker-1][toklen[tokmarker-1]]); + result.filename = tokens[0]; + result.filenameLength = p - tokens[0]; + result.type = FTPFileEntry; + + if (result.filename[result.filenameLength-1] == '/') + { + if (result.linknameLength == 1) + result.type = FTPJunkEntry; + else + { + result.filenameLength--; + result.type = FTPDirectoryEntry; + } + } + else if (isASCIIDigit(*tokens[tokmarker])) + { + pos = toklen[tokmarker]; + result.fileSize = String(tokens[tokmarker], pos); + } + + if ((tokmarker+3) < numtoks && + (&(tokens[numtoks-1][toklen[numtoks-1]]) - + tokens[tokmarker+1]) >= (1+1+3+1+4) ) + { + pos = (tokmarker+3); + p = tokens[pos]; + pos = toklen[pos]; + + if ((pos == 4 || pos == 5) + && isASCIIDigit(*p) && isASCIIDigit(p[pos-1]) && isASCIIDigit(p[pos-2]) + && ((pos == 5 && p[2] == ':') || + (pos == 4 && (isASCIIDigit(p[1]) || p[1] == ':'))) + ) + { + month_num = tokmarker+1; /* assumed position of month field */ + pos = tokmarker+2; /* assumed position of mday field */ + if (isASCIIDigit(*tokens[month_num])) /* positions are reversed */ + { + month_num++; + pos--; + } + p = tokens[month_num]; + if (isASCIIDigit(*tokens[pos]) + && (toklen[pos] == 1 || + (toklen[pos] == 2 && isASCIIDigit(tokens[pos][1]))) + && toklen[month_num] == 3 + && isASCIIAlpha(*p) && isASCIIAlpha(p[1]) && isASCIIAlpha(p[2]) ) + { + pos = atoi(tokens[pos]); + if (pos > 0 && pos <= 31) + { + result.modifiedTime.tm_mday = pos; + month_num = 1; + for (pos = 0; pos < (12*3); pos+=3) + { + if (p[0] == month_names[pos+0] && + p[1] == month_names[pos+1] && + p[2] == month_names[pos+2]) + break; + month_num++; + } + if (month_num > 12) + result.modifiedTime.tm_mday = 0; + else + result.modifiedTime.tm_mon = month_num - 1; + } + } + if (result.modifiedTime.tm_mday) + { + tokmarker += 3; /* skip mday/mon/yrtime (to find " -> ") */ + p = tokens[tokmarker]; + + pos = atoi(p); + if (pos > 24) + result.modifiedTime.tm_year = pos-1900; + else + { + if (p[1] == ':') + p--; + result.modifiedTime.tm_hour = pos; + result.modifiedTime.tm_min = atoi(p+3); + if (!state.now) + { + time_t now = time(NULL); + state.now = now * 1000000.0; + + // FIXME: This code has the year 2038 bug + gmtime_r(&now, &state.nowFTPTime); + state.nowFTPTime.tm_year += 1900; + } + result.modifiedTime.tm_year = state.nowFTPTime.tm_year; + if ( (( state.nowFTPTime.tm_mon << 4) + state.nowFTPTime.tm_mday) < + ((result.modifiedTime.tm_mon << 4) + result.modifiedTime.tm_mday) ) + result.modifiedTime.tm_year--; + } /* got year or time */ + } /* got month/mday */ + } /* may have year or time */ + } /* enough remaining to possibly have date/time */ + + if (numtoks > (tokmarker+2)) + { + pos = tokmarker+1; + p = tokens[pos]; + if (toklen[pos] == 2 && *p == '-' && p[1] == '>') + { + p = &(tokens[numtoks-1][toklen[numtoks-1]]); + result.type = FTPLinkEntry; + result.linkname = tokens[pos+1]; + result.linknameLength = p - result.linkname; + if (result.linknameLength > 1 && + result.linkname[result.linknameLength-1] == '/') + result.linknameLength--; + } + } /* if (numtoks > (tokmarker+2)) */ + + /* the caller should do this (if dropping "." and ".." is desired) + if (result.type == FTPDirectoryEntry && result.filename[0] == '.' && + (result.filenameLength == 1 || (result.filenameLength == 2 && + result.filename[1] == '.'))) + return FTPJunkEntry; + */ + + return result.type; + + } /* if (lstyle == 'D') */ + } /* if (!lstyle && (!state.listStyle || state.listStyle == 'D')) */ +#endif + + /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ + + } /* if (linelen > 0) */ + + if (state.parsedOne || state.listStyle) /* junk if we fail to parse */ + return FTPJunkEntry; /* this time but had previously parsed sucessfully */ + return FTPMiscEntry; /* its part of a comment or error message */ +} + +} // namespace WebCore + +#endif // ENABLE(FTPDIR) diff --git a/WebCore/loader/FTPDirectoryParser.h b/WebCore/loader/FTPDirectoryParser.h new file mode 100644 index 0000000..023a895 --- /dev/null +++ b/WebCore/loader/FTPDirectoryParser.h @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2002 Cyrus Patel <cyp@fb14.uni-mainz.de> + * (C) 2007 Apple Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/* ParseFTPList() parses lines from an FTP LIST command. +** +** Written July 2002 by Cyrus Patel <cyp@fb14.uni-mainz.de> +** with acknowledgements to squid, lynx, wget and ftpmirror. +** +** Arguments: +** 'line': line of FTP data connection output. The line is assumed +** to end at the first '\0' or '\n' or '\r\n'. +** 'state': a structure used internally to track state between +** lines. Needs to be bzero()'d at LIST begin. +** 'result': where ParseFTPList will store the results of the parse +** if 'line' is not a comment and is not junk. +** +** Returns one of the following: +** 'd' - LIST line is a directory entry ('result' is valid) +** 'f' - LIST line is a file's entry ('result' is valid) +** 'l' - LIST line is a symlink's entry ('result' is valid) +** '?' - LIST line is junk. (cwd, non-file/dir/link, etc) +** '"' - its not a LIST line (its a "comment") +** +** It may be advisable to let the end-user see "comments" (particularly when +** the listing results in ONLY such lines) because such a listing may be: +** - an unknown LIST format (NLST or "custom" format for example) +** - an error msg (EPERM,ENOENT,ENFILE,EMFILE,ENOTDIR,ENOTBLK,EEXDEV etc). +** - an empty directory and the 'comment' is a "total 0" line or similar. +** (warning: a "total 0" can also mean the total size is unknown). +** +** ParseFTPList() supports all known FTP LISTing formats: +** - '/bin/ls -l' and all variants (including Hellsoft FTP for NetWare); +** - EPLF (Easily Parsable List Format); +** - Windows NT's default "DOS-dirstyle"; +** - OS/2 basic server format LIST format; +** - VMS (MultiNet, UCX, and CMU) LIST format (including multi-line format); +** - IBM VM/CMS, VM/ESA LIST format (two known variants); +** - SuperTCP FTP Server for Win16 LIST format; +** - NetManage Chameleon (NEWT) for Win16 LIST format; +** - '/bin/dls' (two known variants, plus multi-line) LIST format; +** If there are others, then I'd like to hear about them (send me a sample). +** +** NLSTings are not supported explicitely because they cannot be machine +** parsed consistantly: NLSTings do not have unique characteristics - even +** the assumption that there won't be whitespace on the line does not hold +** because some nlistings have more than one filename per line and/or +** may have filenames that have spaces in them. Moreover, distinguishing +** between an error message and an NLST line would require ParseList() to +** recognize all the possible strerror() messages in the world. +*/ + +// This was originally Mozilla code, titled ParseFTPList.h +// Original version of this file can currently be found at: http://mxr.mozilla.org/mozilla1.8/source/netwerk/streamconv/converters/ParseFTPList.h + +#ifndef FTPDirectoryParser_h +#define FTPDirectoryParser_h + +#include "PlatformString.h" + +#include <time.h> + +#define SUPPORT_LSL /* Support for /bin/ls -l and dozens of variations therof */ +#define SUPPORT_DLS /* Support for /bin/dls format (very, Very, VERY rare) */ +#define SUPPORT_EPLF /* Support for Extraordinarily Pathetic List Format */ +#define SUPPORT_DOS /* Support for WinNT server in 'site dirstyle' dos */ +#define SUPPORT_VMS /* Support for VMS (all: MultiNet, UCX, CMU-IP) */ +#define SUPPORT_CMS /* Support for IBM VM/CMS,VM/ESA (z/VM and LISTING forms) */ +#define SUPPORT_OS2 /* Support for IBM TCP/IP for OS/2 - FTP Server */ +#define SUPPORT_W16 /* Support for win16 hosts: SuperTCP or NetManage Chameleon */ + +namespace WebCore { + +typedef struct tm FTPTime; + +struct ListState { + ListState() + : now(0) + , listStyle(0) + , parsedOne(false) + , carryBufferLength(0) + , numLines(0) + { + memset(&nowFTPTime, 0, sizeof(FTPTime)); + } + + double now; /* needed for year determination */ + FTPTime nowFTPTime; + char listStyle; /* LISTing style */ + bool parsedOne; /* returned anything yet? */ + char carryBuffer[84]; /* for VMS multiline */ + int carryBufferLength; /* length of name in carry_buf */ + int64_t numLines; /* number of lines seen */ +}; + +enum FTPEntryType { + FTPDirectoryEntry, + FTPFileEntry, + FTPLinkEntry, + FTPMiscEntry, + FTPJunkEntry +}; + +struct ListResult +{ + ListResult() + { + clear(); + } + + void clear() + { + valid = false; + type = FTPJunkEntry; + filename = 0; + filenameLength = 0; + linkname = 0; + linknameLength = 0; + fileSize.truncate(0); + caseSensitive = false; + memset(&modifiedTime, 0, sizeof(FTPTime)); + } + + bool valid; + FTPEntryType type; + + const char* filename; + uint32_t filenameLength; + + const char* linkname; + uint32_t linknameLength; + + String fileSize; + FTPTime modifiedTime; + bool caseSensitive; // file system is definitely case insensitive +}; + +FTPEntryType parseOneFTPLine(const char* inputLine, ListState&, ListResult&); + +} // namespace WebCore + +#endif // FTPDirectoryParser_h diff --git a/WebCore/loader/FormState.cpp b/WebCore/loader/FormState.cpp new file mode 100644 index 0000000..c55b8ac --- /dev/null +++ b/WebCore/loader/FormState.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "FormState.h" + +#include "Frame.h" +#include "HTMLFormElement.h" + +namespace WebCore { + +PassRefPtr<FormState> FormState::create(PassRefPtr<HTMLFormElement> form, const HashMap<String, String>& values, PassRefPtr<Frame> sourceFrame) +{ + return adoptRef(new FormState(form, values, sourceFrame)); +} + +FormState::FormState(PassRefPtr<HTMLFormElement> form, const HashMap<String, String>& values, PassRefPtr<Frame> sourceFrame) + : m_form(form) + , m_values(values) + , m_sourceFrame(sourceFrame) +{ +} + +} diff --git a/WebCore/loader/FormState.h b/WebCore/loader/FormState.h new file mode 100644 index 0000000..5370e8a --- /dev/null +++ b/WebCore/loader/FormState.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 FormState_h +#define FormState_h + +#include <wtf/RefCounted.h> +#include "StringHash.h" +#include <wtf/HashMap.h> + +namespace WebCore { + + class Frame; + class HTMLFormElement; + + class FormState : public RefCounted<FormState> { + public: + static PassRefPtr<FormState> create(PassRefPtr<HTMLFormElement> form, const HashMap<String, String>& values, PassRefPtr<Frame> sourceFrame); + + HTMLFormElement* form() const { return m_form.get(); } + const HashMap<String, String>& values() const { return m_values; } + Frame* sourceFrame() const { return m_sourceFrame.get(); } + + private: + FormState(PassRefPtr<HTMLFormElement> form, const HashMap<String, String>& values, PassRefPtr<Frame> sourceFrame); + + RefPtr<HTMLFormElement> m_form; + HashMap<String, String> m_values; + RefPtr<Frame> m_sourceFrame; + }; + +} + +#endif // FormState_h diff --git a/WebCore/loader/FrameLoader.cpp b/WebCore/loader/FrameLoader.cpp new file mode 100644 index 0000000..0285a8c --- /dev/null +++ b/WebCore/loader/FrameLoader.cpp @@ -0,0 +1,5283 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "FrameLoader.h" + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size +#include "Archive.h" +#include "ArchiveFactory.h" +#endif +#include "CString.h" +#include "Cache.h" +#include "CachedPage.h" +#include "Chrome.h" +#include "DOMImplementation.h" +#include "DOMWindow.h" +#include "DocLoader.h" +#include "Document.h" +#include "DocumentLoader.h" +#include "Editor.h" +#include "EditorClient.h" +#include "Element.h" +#include "Event.h" +#include "EventNames.h" +#include "FloatRect.h" +#include "FormState.h" +#include "Frame.h" +#include "FrameLoadRequest.h" +#include "FrameLoaderClient.h" +#include "FramePrivate.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "HTMLFormElement.h" +#include "HTMLFrameElement.h" +#include "HTMLNames.h" +#include "HTMLObjectElement.h" +#include "HTTPParsers.h" +#include "HistoryItem.h" +#include "IconDatabase.h" +#include "IconLoader.h" +#include "InspectorController.h" +#include "Logging.h" +#include "MIMETypeRegistry.h" +#include "MainResourceLoader.h" +#include "Page.h" +#include "PageCache.h" +#include "PageGroup.h" +#include "PluginData.h" +#include "ProgressTracker.h" +#include "RenderPart.h" +#include "RenderWidget.h" +#include "RenderView.h" +#include "ResourceHandle.h" +#include "ResourceRequest.h" +#include "SecurityOrigin.h" +#include "SegmentedString.h" +#include "Settings.h" +#include "SystemTime.h" +#include "TextResourceDecoder.h" +#include "WindowFeatures.h" +#include "XMLHttpRequest.h" +#include "XMLTokenizer.h" +#include "JSDOMBinding.h" +#include "ScriptController.h" +#include <runtime/JSLock.h> +#include <runtime/JSObject.h> + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) +#include "ApplicationCache.h" +#include "ApplicationCacheResource.h" +#endif + +#if ENABLE(SVG) +#include "SVGDocument.h" +#include "SVGLocatable.h" +#include "SVGNames.h" +#include "SVGPreserveAspectRatio.h" +#include "SVGSVGElement.h" +#include "SVGViewElement.h" +#include "SVGViewSpec.h" +#endif + +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif + +using namespace JSC; + +namespace WebCore { + +#if ENABLE(SVG) +using namespace SVGNames; +#endif +using namespace HTMLNames; + +#if USE(LOW_BANDWIDTH_DISPLAY) +const unsigned int cMaxPendingSourceLengthInLowBandwidthDisplay = 128 * 1024; +#endif + +struct FormSubmission { + const char* action; + String url; + RefPtr<FormData> data; + String target; + String contentType; + String boundary; + RefPtr<Event> event; + + FormSubmission(const char* a, const String& u, PassRefPtr<FormData> d, const String& t, + const String& ct, const String& b, PassRefPtr<Event> e) + : action(a) + , url(u) + , data(d) + , target(t) + , contentType(ct) + , boundary(b) + , event(e) + { + } +}; + +struct ScheduledRedirection { + enum Type { redirection, locationChange, historyNavigation, locationChangeDuringLoad }; + Type type; + double delay; + String url; + String referrer; + int historySteps; + bool lockHistory; + bool wasUserGesture; + + ScheduledRedirection(double redirectDelay, const String& redirectURL, bool redirectLockHistory, bool userGesture) + : type(redirection) + , delay(redirectDelay) + , url(redirectURL) + , historySteps(0) + , lockHistory(redirectLockHistory) + , wasUserGesture(userGesture) + { + } + + ScheduledRedirection(Type locationChangeType, + const String& locationChangeURL, const String& locationChangeReferrer, + bool locationChangeLockHistory, bool locationChangeWasUserGesture) + : type(locationChangeType) + , delay(0) + , url(locationChangeURL) + , referrer(locationChangeReferrer) + , historySteps(0) + , lockHistory(locationChangeLockHistory) + , wasUserGesture(locationChangeWasUserGesture) + { + } + + explicit ScheduledRedirection(int historyNavigationSteps) + : type(historyNavigation) + , delay(0) + , historySteps(historyNavigationSteps) + , lockHistory(false) + , wasUserGesture(false) + { + } +}; + +static double storedTimeOfLastCompletedLoad; +static FrameLoader::LocalLoadPolicy localLoadPolicy = FrameLoader::AllowLocalLoadsForLocalOnly; + +static bool getString(JSValue* result, String& string) +{ + if (!result) + return false; + JSLock lock(false); + UString ustring; + if (!result->getString(ustring)) + return false; + string = ustring; + return true; +} + +bool isBackForwardLoadType(FrameLoadType type) +{ + switch (type) { + case FrameLoadTypeStandard: + case FrameLoadTypeReload: + case FrameLoadTypeReloadAllowingStaleData: + case FrameLoadTypeSame: + case FrameLoadTypeRedirectWithLockedHistory: + case FrameLoadTypeReplace: + return false; + case FrameLoadTypeBack: + case FrameLoadTypeForward: + case FrameLoadTypeIndexedBackForward: + return true; + } + ASSERT_NOT_REACHED(); + return false; +} + +static int numRequests(Document* document) +{ + if (!document) + return 0; + + return document->docLoader()->requestCount(); +} + +FrameLoader::FrameLoader(Frame* frame, FrameLoaderClient* client) + : m_frame(frame) + , m_client(client) + , m_state(FrameStateCommittedPage) + , m_loadType(FrameLoadTypeStandard) + , m_policyLoadType(FrameLoadTypeStandard) + , m_delegateIsHandlingProvisionalLoadError(false) + , m_delegateIsDecidingNavigationPolicy(false) + , m_delegateIsHandlingUnimplementablePolicy(false) + , m_firstLayoutDone(false) + , m_quickRedirectComing(false) + , m_sentRedirectNotification(false) + , m_inStopAllLoaders(false) + , m_navigationDuringLoad(false) + , m_cachePolicy(CachePolicyVerify) + , m_isExecutingJavaScriptFormAction(false) + , m_isRunningScript(false) + , m_didCallImplicitClose(false) + , m_wasUnloadEventEmitted(false) + , m_isComplete(false) + , m_isLoadingMainResource(false) + , m_cancellingWithLoadInProgress(false) + , m_needsClear(false) + , m_receivedData(false) + , m_encodingWasChosenByUser(false) + , m_containsPlugIns(false) + , m_redirectionTimer(this, &FrameLoader::redirectionTimerFired) + , m_checkCompletedTimer(this, &FrameLoader::checkCompletedTimerFired) + , m_checkLoadCompleteTimer(this, &FrameLoader::checkLoadCompleteTimerFired) + , m_opener(0) + , m_openedByDOM(false) + , m_creatingInitialEmptyDocument(false) + , m_isDisplayingInitialEmptyDocument(false) + , m_committedFirstRealDocumentLoad(false) + , m_didPerformFirstNavigation(false) +#ifndef NDEBUG + , m_didDispatchDidCommitLoad(false) +#endif +#if USE(LOW_BANDWIDTH_DISPLAY) + , m_useLowBandwidthDisplay(true) + , m_finishedParsingDuringLowBandwidthDisplay(false) + , m_needToSwitchOutLowBandwidthDisplay(false) +#endif +{ +} + +FrameLoader::~FrameLoader() +{ + setOpener(0); + + HashSet<Frame*>::iterator end = m_openedFrames.end(); + for (HashSet<Frame*>::iterator it = m_openedFrames.begin(); it != end; ++it) + (*it)->loader()->m_opener = 0; + + m_client->frameLoaderDestroyed(); +} + +void FrameLoader::init() +{ + // this somewhat odd set of steps is needed to give the frame an initial empty document + m_isDisplayingInitialEmptyDocument = false; + m_creatingInitialEmptyDocument = true; + setPolicyDocumentLoader(m_client->createDocumentLoader(ResourceRequest(String("")), SubstituteData()).get()); + setProvisionalDocumentLoader(m_policyDocumentLoader.get()); + setState(FrameStateProvisional); + m_provisionalDocumentLoader->setResponse(ResourceResponse(KURL(), "text/html", 0, String(), String())); + m_provisionalDocumentLoader->finishedLoading(); + begin(KURL(), false); + end(); + m_frame->document()->cancelParsing(); + m_creatingInitialEmptyDocument = false; + m_didCallImplicitClose = true; +} + +void FrameLoader::setDefersLoading(bool defers) +{ + if (m_documentLoader) + m_documentLoader->setDefersLoading(defers); + if (m_provisionalDocumentLoader) + m_provisionalDocumentLoader->setDefersLoading(defers); + if (m_policyDocumentLoader) + m_policyDocumentLoader->setDefersLoading(defers); +} + +Frame* FrameLoader::createWindow(FrameLoader* frameLoaderForFrameLookup, const FrameLoadRequest& request, const WindowFeatures& features, bool& created) +{ + ASSERT(!features.dialog || request.frameName().isEmpty()); + + if (!request.frameName().isEmpty() && request.frameName() != "_blank") { + Frame* frame = frameLoaderForFrameLookup->frame()->tree()->find(request.frameName()); + if (frame && shouldAllowNavigation(frame)) { + if (!request.resourceRequest().url().isEmpty()) + frame->loader()->loadFrameRequestWithFormAndValues(request, false, 0, 0, HashMap<String, String>()); + if (Page* page = frame->page()) + page->chrome()->focus(); + created = false; + return frame; + } + } + + // FIXME: Setting the referrer should be the caller's responsibility. + FrameLoadRequest requestWithReferrer = request; + requestWithReferrer.resourceRequest().setHTTPReferrer(m_outgoingReferrer); + addHTTPOriginIfNeeded(requestWithReferrer.resourceRequest(), outgoingOrigin()); + + Page* oldPage = m_frame->page(); + if (!oldPage) + return 0; + + Page* page = oldPage->chrome()->createWindow(m_frame, requestWithReferrer, features); + if (!page) + return 0; + + Frame* frame = page->mainFrame(); + if (request.frameName() != "_blank") + frame->tree()->setName(request.frameName()); + + page->chrome()->setToolbarsVisible(features.toolBarVisible || features.locationBarVisible); + page->chrome()->setStatusbarVisible(features.statusBarVisible); + page->chrome()->setScrollbarsVisible(features.scrollbarsVisible); + page->chrome()->setMenubarVisible(features.menuBarVisible); + page->chrome()->setResizable(features.resizable); + + // 'x' and 'y' specify the location of the window, while 'width' and 'height' + // specify the size of the page. We can only resize the window, so + // adjust for the difference between the window size and the page size. + + FloatRect windowRect = page->chrome()->windowRect(); + FloatSize pageSize = page->chrome()->pageRect().size(); + if (features.xSet) + windowRect.setX(features.x); + if (features.ySet) + windowRect.setY(features.y); + if (features.widthSet) + windowRect.setWidth(features.width + (windowRect.width() - pageSize.width())); + if (features.heightSet) + windowRect.setHeight(features.height + (windowRect.height() - pageSize.height())); + page->chrome()->setWindowRect(windowRect); + + page->chrome()->show(); + + created = true; + return frame; +} + +bool FrameLoader::canHandleRequest(const ResourceRequest& request) +{ + return m_client->canHandleRequest(request); +} + +void FrameLoader::changeLocation(const String& url, const String& referrer, bool lockHistory, bool userGesture) +{ + changeLocation(completeURL(url), referrer, lockHistory, userGesture); +} + + +void FrameLoader::changeLocation(const KURL& url, const String& referrer, bool lockHistory, bool userGesture) +{ + RefPtr<Frame> protect(m_frame); + + ResourceRequestCachePolicy policy = (m_cachePolicy == CachePolicyReload) || (m_cachePolicy == CachePolicyRefresh) + ? ReloadIgnoringCacheData : UseProtocolCachePolicy; + ResourceRequest request(url, referrer, policy); +#ifdef ANDROID_USER_GESTURE + request.setUserGesture(userGesture); +#endif + + if (executeIfJavaScriptURL(request.url(), userGesture)) + return; + + urlSelected(request, "_self", 0, lockHistory, userGesture); +} + +void FrameLoader::urlSelected(const FrameLoadRequest& request, Event* event, bool lockHistory) +{ + FrameLoadRequest copy = request; + if (copy.resourceRequest().httpReferrer().isEmpty()) + copy.resourceRequest().setHTTPReferrer(m_outgoingReferrer); + addHTTPOriginIfNeeded(copy.resourceRequest(), outgoingOrigin()); + + loadFrameRequestWithFormAndValues(copy, lockHistory, event, 0, HashMap<String, String>()); +} + +void FrameLoader::urlSelected(const ResourceRequest& request, const String& _target, Event* triggeringEvent, bool lockHistory, bool userGesture) +{ + if (executeIfJavaScriptURL(request.url(), userGesture, false)) + return; + + String target = _target; + if (target.isEmpty() && m_frame->document()) + target = m_frame->document()->baseTarget(); + + FrameLoadRequest frameRequest(request, target); +#ifdef ANDROID_USER_GESTURE + frameRequest.setWasUserGesture(userGesture); +#endif + + urlSelected(frameRequest, triggeringEvent, lockHistory); +} + +bool FrameLoader::requestFrame(HTMLFrameOwnerElement* ownerElement, const String& urlString, const AtomicString& frameName) +{ +#if USE(LOW_BANDWIDTH_DISPLAY) + // don't create sub-frame during low bandwidth display + if (frame()->document()->inLowBandwidthDisplay()) { + m_needToSwitchOutLowBandwidthDisplay = true; + return false; + } +#endif + + // Support for <frame src="javascript:string"> + KURL scriptURL; + KURL url; + if (protocolIs(urlString, "javascript")) { + scriptURL = KURL(urlString); + url = blankURL(); + } else + url = completeURL(urlString); + + Frame* frame = ownerElement->contentFrame(); + if (frame) + frame->loader()->scheduleLocationChange(url.string(), m_outgoingReferrer, true, userGestureHint()); + else + frame = loadSubframe(ownerElement, url, frameName, m_outgoingReferrer); + + if (!frame) + return false; + + if (!scriptURL.isEmpty()) + frame->loader()->executeIfJavaScriptURL(scriptURL); + + return true; +} + +Frame* FrameLoader::loadSubframe(HTMLFrameOwnerElement* ownerElement, const KURL& url, const String& name, const String& referrer) +{ + bool allowsScrolling = true; + int marginWidth = -1; + int marginHeight = -1; + if (ownerElement->hasTagName(frameTag) || ownerElement->hasTagName(iframeTag)) { + HTMLFrameElementBase* o = static_cast<HTMLFrameElementBase*>(ownerElement); + allowsScrolling = o->scrollingMode() != ScrollbarAlwaysOff; + marginWidth = o->getMarginWidth(); + marginHeight = o->getMarginHeight(); + } + + if (!canLoad(url, referrer)) { + FrameLoader::reportLocalLoadFailed(m_frame, url.string()); + return 0; + } + + bool hideReferrer = shouldHideReferrer(url, referrer); + RefPtr<Frame> frame = m_client->createFrame(url, name, ownerElement, hideReferrer ? String() : referrer, + allowsScrolling, marginWidth, marginHeight); + + if (!frame) { + checkCallImplicitClose(); + return 0; + } + + frame->loader()->m_isComplete = false; + + RenderObject* renderer = ownerElement->renderer(); + FrameView* view = frame->view(); + if (renderer && renderer->isWidget() && view) + static_cast<RenderWidget*>(renderer)->setWidget(view); + + checkCallImplicitClose(); + + // In these cases, the synchronous load would have finished + // before we could connect the signals, so make sure to send the + // completed() signal for the child by hand + // FIXME: In this case the Frame will have finished loading before + // it's being added to the child list. It would be a good idea to + // create the child first, then invoke the loader separately. + if (url.isEmpty() || url == blankURL()) { + frame->loader()->completed(); + frame->loader()->checkCompleted(); + } + + return frame.get(); +} + +void FrameLoader::submitFormAgain() +{ + if (m_isRunningScript) + return; + OwnPtr<FormSubmission> form(m_deferredFormSubmission.release()); + if (form) + submitForm(form->action, form->url, form->data, form->target, + form->contentType, form->boundary, form->event.get()); +} + +void FrameLoader::submitForm(const char* action, const String& url, PassRefPtr<FormData> formData, + const String& target, const String& contentType, const String& boundary, Event* event) +{ + ASSERT(formData); + + if (!m_frame->page()) + return; + + KURL u = completeURL(url.isNull() ? "" : url); + // FIXME: Do we really need to special-case an empty URL? + // Would it be better to just go on with the form submisson and let the I/O fail? + if (u.isEmpty()) + return; + + if (u.protocolIs("javascript")) { + m_isExecutingJavaScriptFormAction = true; + executeIfJavaScriptURL(u, false, false); + m_isExecutingJavaScriptFormAction = false; + return; + } + + if (m_isRunningScript) { + if (m_deferredFormSubmission) + return; + m_deferredFormSubmission.set(new FormSubmission(action, url, formData, target, + contentType, boundary, event)); + return; + } + + formData->generateFiles(m_frame->page()->chrome()->client()); + + FrameLoadRequest frameRequest; +#ifdef ANDROID_USER_GESTURE + frameRequest.setWasUserGesture(userGestureHint()); +#endif + + if (!m_outgoingReferrer.isEmpty()) + frameRequest.resourceRequest().setHTTPReferrer(m_outgoingReferrer); + + frameRequest.setFrameName(target.isEmpty() ? m_frame->document()->baseTarget() : target); + + // Handle mailto: forms + bool isMailtoForm = equalIgnoringCase(u.protocol(), "mailto"); + if (isMailtoForm && strcmp(action, "GET") != 0) { + // Append body= for POST mailto, replace the whole query string for GET one. + String body = formData->flattenToString(); + String query = u.query(); + if (!query.isEmpty()) + query.append('&'); + u.setQuery(query + body); + } + + if (strcmp(action, "GET") == 0) { + u.setQuery(formData->flattenToString()); + } else { + if (!isMailtoForm) + frameRequest.resourceRequest().setHTTPBody(formData.get()); + frameRequest.resourceRequest().setHTTPMethod("POST"); + + // construct some user headers if necessary + if (contentType.isNull() || contentType == "application/x-www-form-urlencoded") + frameRequest.resourceRequest().setHTTPContentType(contentType); + else // contentType must be "multipart/form-data" + frameRequest.resourceRequest().setHTTPContentType(contentType + "; boundary=" + boundary); + } + + frameRequest.resourceRequest().setURL(u); + addHTTPOriginIfNeeded(frameRequest.resourceRequest(), outgoingOrigin()); + + submitForm(frameRequest, event); +} + +void FrameLoader::stopLoading(bool sendUnload) +{ + if (m_frame->document() && m_frame->document()->tokenizer()) + m_frame->document()->tokenizer()->stopParsing(); + + if (sendUnload) { + if (m_frame->document()) { + if (m_didCallImplicitClose && !m_wasUnloadEventEmitted) { + Node* currentFocusedNode = m_frame->document()->focusedNode(); + if (currentFocusedNode) + currentFocusedNode->aboutToUnload(); + m_frame->document()->dispatchWindowEvent(eventNames().unloadEvent, false, false); + if (m_frame->document()) + m_frame->document()->updateRendering(); + m_wasUnloadEventEmitted = true; + if (m_frame->eventHandler()->pendingFrameUnloadEventCount()) + m_frame->eventHandler()->clearPendingFrameUnloadEventCount(); + if (m_frame->eventHandler()->pendingFrameBeforeUnloadEventCount()) + m_frame->eventHandler()->clearPendingFrameBeforeUnloadEventCount(); + } + } + if (m_frame->document() && !m_frame->document()->inPageCache()) + m_frame->document()->removeAllEventListenersFromAllNodes(); + } + + m_isComplete = true; // to avoid calling completed() in finishedParsing() (David) + m_isLoadingMainResource = false; + m_didCallImplicitClose = true; // don't want that one either + m_cachePolicy = CachePolicyVerify; // Why here? + + if (m_frame->document() && m_frame->document()->parsing()) { + finishedParsing(); + m_frame->document()->setParsing(false); + } + + m_workingURL = KURL(); + + if (Document* doc = m_frame->document()) { + if (DocLoader* docLoader = doc->docLoader()) + cache()->loader()->cancelRequests(docLoader); + + doc->stopActiveDOMObjects(); + +#if ENABLE(DATABASE) + doc->stopDatabases(); +#endif + } + + // tell all subframes to stop as well + for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) + child->loader()->stopLoading(sendUnload); + + cancelRedirection(); + +#if USE(LOW_BANDWIDTH_DISPLAY) + if (m_frame->document() && m_frame->document()->inLowBandwidthDisplay()) { + // Since loading is forced to stop, reset the state without really switching. + m_needToSwitchOutLowBandwidthDisplay = false; + switchOutLowBandwidthDisplayIfReady(); + } +#endif +} + +void FrameLoader::stop() +{ + // http://bugs.webkit.org/show_bug.cgi?id=10854 + // The frame's last ref may be removed and it will be deleted by checkCompleted(). + RefPtr<Frame> protector(m_frame); + + if (m_frame->document()) { + if (m_frame->document()->tokenizer()) + m_frame->document()->tokenizer()->stopParsing(); + m_frame->document()->finishParsing(); + } else + // WebKit partially uses WebCore when loading non-HTML docs. In these cases doc==nil, but + // WebCore is enough involved that we need to checkCompleted() in order for m_bComplete to + // become true. An example is when a subframe is a pure text doc, and that subframe is the + // last one to complete. + checkCompleted(); + if (m_iconLoader) + m_iconLoader->stopLoading(); +} + +bool FrameLoader::closeURL() +{ + saveDocumentState(); + stopLoading(true); + m_frame->editor()->clearUndoRedoOperations(); + return true; +} + +void FrameLoader::cancelRedirection(bool cancelWithLoadInProgress) +{ + m_cancellingWithLoadInProgress = cancelWithLoadInProgress; + + stopRedirectionTimer(); + + m_scheduledRedirection.clear(); +} + +KURL FrameLoader::iconURL() +{ + // If this isn't a top level frame, return nothing + if (m_frame->tree() && m_frame->tree()->parent()) + return KURL(); + + // If we have an iconURL from a Link element, return that + if (m_frame->document() && !m_frame->document()->iconURL().isEmpty()) + return KURL(m_frame->document()->iconURL()); + + // Don't return a favicon iconURL unless we're http or https + if (!m_URL.protocolIs("http") && !m_URL.protocolIs("https")) + return KURL(); + + KURL url; + url.setProtocol(m_URL.protocol()); + url.setHost(m_URL.host()); + if (int port = m_URL.port()) + url.setPort(port); + url.setPath("/favicon.ico"); + return url; +} + +bool FrameLoader::didOpenURL(const KURL& url) +{ + if (m_scheduledRedirection && m_scheduledRedirection->type == ScheduledRedirection::locationChangeDuringLoad) + // A redirect was scheduled before the document was created. + // This can happen when one frame changes another frame's location. + return false; + + cancelRedirection(); + m_frame->editor()->clearLastEditCommand(); + closeURL(); + + m_isComplete = false; + m_isLoadingMainResource = true; + m_didCallImplicitClose = false; + + m_frame->setJSStatusBarText(String()); + m_frame->setJSDefaultStatusBarText(String()); + + m_URL = url; + if ((m_URL.protocolIs("http") || m_URL.protocolIs("https")) && !m_URL.host().isEmpty() && m_URL.path().isEmpty()) + m_URL.setPath("/"); + m_workingURL = m_URL; + + started(); + + return true; +} + +void FrameLoader::didExplicitOpen() +{ + m_isComplete = false; + m_didCallImplicitClose = false; + + // Calling document.open counts as committing the first real document load. + m_committedFirstRealDocumentLoad = true; + + // Prevent window.open(url) -- eg window.open("about:blank") -- from blowing away results + // from a subsequent window.document.open / window.document.write call. + // Cancelling redirection here works for all cases because document.open + // implicitly precedes document.write. + cancelRedirection(); + if (m_frame->document()->url() != blankURL()) + m_URL = m_frame->document()->url(); +} + +bool FrameLoader::executeIfJavaScriptURL(const KURL& url, bool userGesture, bool replaceDocument) +{ + if (!url.protocolIs("javascript")) + return false; + + String script = decodeURLEscapeSequences(url.string().substring(strlen("javascript:"))); + JSValue* result = executeScript(script, userGesture); + + String scriptResult; + if (!getString(result, scriptResult)) + return true; + + SecurityOrigin* currentSecurityOrigin = 0; + if (m_frame->document()) + currentSecurityOrigin = m_frame->document()->securityOrigin(); + + // FIXME: We should always replace the document, but doing so + // synchronously can cause crashes: + // http://bugs.webkit.org/show_bug.cgi?id=16782 + if (replaceDocument) { + begin(m_URL, true, currentSecurityOrigin); + write(scriptResult); + end(); + } + + return true; +} + +JSValue* FrameLoader::executeScript(const String& script, bool forceUserGesture) +{ + return executeScript(forceUserGesture ? String() : m_URL.string(), 1, script); +} + +JSValue* FrameLoader::executeScript(const String& url, int baseLine, const String& script) +{ + if (!m_frame->script()->isEnabled() || m_frame->script()->isPaused()) + return noValue(); + + bool wasRunningScript = m_isRunningScript; + m_isRunningScript = true; + + JSValue* result = m_frame->script()->evaluate(url, baseLine, script); + + if (!wasRunningScript) { + m_isRunningScript = false; + submitFormAgain(); + Document::updateDocumentsRendering(); + } + + return result; +} + +void FrameLoader::cancelAndClear() +{ + cancelRedirection(); + + if (!m_isComplete) + closeURL(); + + clear(false); +} + +void FrameLoader::clear(bool clearWindowProperties, bool clearScriptObjects) +{ + // FIXME: Commenting out the below line causes <http://bugs.webkit.org/show_bug.cgi?id=11212>, but putting it + // back causes a measurable performance regression which we will need to fix to restore the correct behavior + // urlsBridgeKnowsAbout.clear(); + + m_frame->editor()->clear(); + + if (!m_needsClear) + return; + m_needsClear = false; + + if (m_frame->document() && !m_frame->document()->inPageCache()) { + m_frame->document()->cancelParsing(); + if (m_frame->document()->attached()) { + m_frame->document()->willRemove(); + m_frame->document()->detach(); + + m_frame->document()->removeFocusedNodeOfSubtree(m_frame->document()); + } + } + + // Do this after detaching the document so that the unload event works. + if (clearWindowProperties) { + m_frame->clearDOMWindow(); + m_frame->script()->clearWindowShell(); + } + + m_frame->selection()->clear(); + m_frame->eventHandler()->clear(); + if (m_frame->view()) + m_frame->view()->clear(); + + m_frame->setSelectionGranularity(CharacterGranularity); + + // Do not drop the document before the ScriptController and view are cleared + // as some destructors might still try to access the document. + m_frame->setDocument(0); + m_decoder = 0; + + m_containsPlugIns = false; + + if (clearScriptObjects) + m_frame->script()->clearScriptObjects(); + + m_redirectionTimer.stop(); + m_scheduledRedirection.clear(); + + m_checkCompletedTimer.stop(); + m_checkLoadCompleteTimer.stop(); + + m_receivedData = false; + m_isDisplayingInitialEmptyDocument = false; + + if (!m_encodingWasChosenByUser) + m_encoding = String(); +} + +void FrameLoader::receivedFirstData() +{ + begin(m_workingURL, false); + + dispatchDidCommitLoad(); + dispatchWindowObjectAvailable(); + + String ptitle = m_documentLoader->title(); + // If we have a title let the WebView know about it. + if (!ptitle.isNull()) + m_client->dispatchDidReceiveTitle(ptitle); + + m_frame->document()->docLoader()->setCachePolicy(m_cachePolicy); + m_workingURL = KURL(); + + double delay; + String url; + if (!m_documentLoader) + return; + if (!parseHTTPRefresh(m_documentLoader->response().httpHeaderField("Refresh"), false, delay, url)) + return; + + if (url.isEmpty()) + url = m_URL.string(); + else + url = m_frame->document()->completeURL(url).string(); + + scheduleHTTPRedirection(delay, url); +} + +const String& FrameLoader::responseMIMEType() const +{ + return m_responseMIMEType; +} + +void FrameLoader::setResponseMIMEType(const String& type) +{ + m_responseMIMEType = type; +} + +void FrameLoader::begin() +{ + begin(KURL()); +} + +void FrameLoader::begin(const KURL& url, bool dispatch, SecurityOrigin* origin) +{ + // We need to take a reference to the security origin because |clear| + // might destroy the document that owns it. + RefPtr<SecurityOrigin> forcedSecurityOrigin = origin; + + bool resetScripting = !(m_isDisplayingInitialEmptyDocument && m_frame->document() && m_frame->document()->securityOrigin()->isSecureTransitionTo(url)); + clear(resetScripting, resetScripting); + if (dispatch) + dispatchWindowObjectAvailable(); + + m_needsClear = true; + m_isComplete = false; + m_didCallImplicitClose = false; + m_isLoadingMainResource = true; + m_isDisplayingInitialEmptyDocument = m_creatingInitialEmptyDocument; + + KURL ref(url); + ref.setUser(String()); + ref.setPass(String()); + ref.setRef(String()); + m_outgoingReferrer = ref.string(); + m_URL = url; + + RefPtr<Document> document = DOMImplementation::createDocument(m_responseMIMEType, m_frame, m_frame->inViewSourceMode()); + m_frame->setDocument(document); + + document->setURL(m_URL); + if (m_decoder) + document->setDecoder(m_decoder.get()); + if (forcedSecurityOrigin) + document->setSecurityOrigin(forcedSecurityOrigin.get()); + + m_frame->domWindow()->setURL(document->url()); + m_frame->domWindow()->setSecurityOrigin(document->securityOrigin()); + + updatePolicyBaseURL(); + + Settings* settings = document->settings(); + document->docLoader()->setAutoLoadImages(settings && settings->loadsImagesAutomatically()); +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + document->docLoader()->setBlockNetworkImage(settings && settings->blockNetworkImage()); +#endif + + if (m_documentLoader) { + String dnsPrefetchControl = m_documentLoader->response().httpHeaderField("X-DNS-Prefetch-Control"); + if (!dnsPrefetchControl.isEmpty()) + document->parseDNSPrefetchControlHeader(dnsPrefetchControl); + } + +#if FRAME_LOADS_USER_STYLESHEET + KURL userStyleSheet = settings ? settings->userStyleSheetLocation() : KURL(); + if (!userStyleSheet.isEmpty()) + m_frame->setUserStyleSheetLocation(userStyleSheet); +#endif + + restoreDocumentState(); + + document->implicitOpen(); + + if (m_frame->view()) + m_frame->view()->setContentsSize(IntSize()); + +#if USE(LOW_BANDWIDTH_DISPLAY) + // Low bandwidth display is a first pass display without external resources + // used to give an instant visual feedback. We currently only enable it for + // HTML documents in the top frame. + if (document->isHTMLDocument() && !m_frame->tree()->parent() && m_useLowBandwidthDisplay) { + m_pendingSourceInLowBandwidthDisplay = String(); + m_finishedParsingDuringLowBandwidthDisplay = false; + m_needToSwitchOutLowBandwidthDisplay = false; + document->setLowBandwidthDisplay(true); + } +#endif +} + +void FrameLoader::write(const char* str, int len, bool flush) +{ + if (len == 0 && !flush) + return; + + if (len == -1) + len = strlen(str); + + Tokenizer* tokenizer = m_frame->document()->tokenizer(); + if (tokenizer && tokenizer->wantsRawData()) { + if (len > 0) + tokenizer->writeRawData(str, len); + return; + } + + if (!m_decoder) { + Settings* settings = m_frame->settings(); + m_decoder = TextResourceDecoder::create(m_responseMIMEType, settings ? settings->defaultTextEncodingName() : String()); + if (m_encoding.isEmpty()) { + Frame* parentFrame = m_frame->tree()->parent(); + if (parentFrame && parentFrame->document()->securityOrigin()->canAccess(m_frame->document()->securityOrigin())) + m_decoder->setEncoding(parentFrame->document()->inputEncoding(), TextResourceDecoder::DefaultEncoding); + } else { + m_decoder->setEncoding(m_encoding, + m_encodingWasChosenByUser ? TextResourceDecoder::UserChosenEncoding : TextResourceDecoder::EncodingFromHTTPHeader); + } + m_frame->document()->setDecoder(m_decoder.get()); + } + + String decoded = m_decoder->decode(str, len); + if (flush) + decoded += m_decoder->flush(); + if (decoded.isEmpty()) + return; + +#if USE(LOW_BANDWIDTH_DISPLAY) + if (m_frame->document()->inLowBandwidthDisplay()) + m_pendingSourceInLowBandwidthDisplay.append(decoded); + else // reset policy which is changed in switchOutLowBandwidthDisplayIfReady() + m_frame->document()->docLoader()->setCachePolicy(m_cachePolicy); +#endif + + if (!m_receivedData) { + m_receivedData = true; + if (m_decoder->encoding().usesVisualOrdering()) + m_frame->document()->setVisuallyOrdered(); + m_frame->document()->recalcStyle(Node::Force); + } + + if (tokenizer) { + ASSERT(!tokenizer->wantsRawData()); + tokenizer->write(decoded, true); + } +} + +void FrameLoader::write(const String& str) +{ + if (str.isNull()) + return; + + if (!m_receivedData) { + m_receivedData = true; + m_frame->document()->setParseMode(Document::Strict); + } + + if (Tokenizer* tokenizer = m_frame->document()->tokenizer()) + tokenizer->write(str, true); +} + +void FrameLoader::end() +{ + m_isLoadingMainResource = false; + endIfNotLoadingMainResource(); +} + +void FrameLoader::endIfNotLoadingMainResource() +{ + if (m_isLoadingMainResource || !m_frame->page()) + return; + + // http://bugs.webkit.org/show_bug.cgi?id=10854 + // The frame's last ref may be removed and it can be deleted by checkCompleted(), + // so we'll add a protective refcount + RefPtr<Frame> protector(m_frame); + + // make sure nothing's left in there + if (m_frame->document()) { + write(0, 0, true); + m_frame->document()->finishParsing(); +#if USE(LOW_BANDWIDTH_DISPLAY) + if (m_frame->document()->inLowBandwidthDisplay()) { + m_finishedParsingDuringLowBandwidthDisplay = true; + switchOutLowBandwidthDisplayIfReady(); + } +#endif + } else + // WebKit partially uses WebCore when loading non-HTML docs. In these cases doc==nil, but + // WebCore is enough involved that we need to checkCompleted() in order for m_bComplete to + // become true. An example is when a subframe is a pure text doc, and that subframe is the + // last one to complete. + checkCompleted(); +} + +void FrameLoader::iconLoadDecisionAvailable() +{ + if (!m_mayLoadIconLater) + return; + LOG(IconDatabase, "FrameLoader %p was told a load decision is available for its icon", this); + startIconLoader(); + m_mayLoadIconLater = false; +} + +void FrameLoader::startIconLoader() +{ + // FIXME: We kick off the icon loader when the frame is done receiving its main resource. + // But we should instead do it when we're done parsing the head element. + if (!isLoadingMainFrame()) + return; + + if (!iconDatabase() || !iconDatabase()->isEnabled()) + return; + + KURL url(iconURL()); + String urlString(url.string()); + if (urlString.isEmpty()) + return; + + // If we're not reloading and the icon database doesn't say to load now then bail before we actually start the load + if (loadType() != FrameLoadTypeReload) { + IconLoadDecision decision = iconDatabase()->loadDecisionForIconURL(urlString, m_documentLoader.get()); + if (decision == IconLoadNo) { + LOG(IconDatabase, "FrameLoader::startIconLoader() - Told not to load this icon, committing iconURL %s to database for pageURL mapping", urlString.ascii().data()); + commitIconURLToIconDatabase(url); + + // We were told not to load this icon - that means this icon is already known by the database + // If the icon data hasn't been read in from disk yet, kick off the read of the icon from the database to make sure someone + // has done it. This is after registering for the notification so the WebView can call the appropriate delegate method. + // Otherwise if the icon data *is* available, notify the delegate + if (!iconDatabase()->iconDataKnownForIconURL(urlString)) { + LOG(IconDatabase, "Told not to load icon %s but icon data is not yet available - registering for notification and requesting load from disk", urlString.ascii().data()); + m_client->registerForIconNotification(); + iconDatabase()->iconForPageURL(m_URL.string(), IntSize(0, 0)); + iconDatabase()->iconForPageURL(originalRequestURL().string(), IntSize(0, 0)); + } else + m_client->dispatchDidReceiveIcon(); + + return; + } + + if (decision == IconLoadUnknown) { + // In this case, we may end up loading the icon later, but we still want to commit the icon url mapping to the database + // just in case we don't end up loading later - if we commit the mapping a second time after the load, that's no big deal + // We also tell the client to register for the notification that the icon is received now so it isn't missed in case the + // icon is later read in from disk + LOG(IconDatabase, "FrameLoader %p might load icon %s later", this, urlString.ascii().data()); + m_mayLoadIconLater = true; + m_client->registerForIconNotification(); + commitIconURLToIconDatabase(url); + return; + } + } + + // This is either a reload or the icon database said "yes, load the icon", so kick off the load! + if (!m_iconLoader) + m_iconLoader.set(IconLoader::create(m_frame).release()); + + m_iconLoader->startLoading(); +} + +void FrameLoader::setLocalLoadPolicy(LocalLoadPolicy policy) +{ + localLoadPolicy = policy; +} + +bool FrameLoader::restrictAccessToLocal() +{ + return localLoadPolicy != FrameLoader::AllowLocalLoadsForAll; +} + +bool FrameLoader::allowSubstituteDataAccessToLocal() +{ + return localLoadPolicy != FrameLoader::AllowLocalLoadsForLocalOnly; +} + +static HashSet<String, CaseFoldingHash>& localSchemes() +{ + static HashSet<String, CaseFoldingHash> localSchemes; + + if (localSchemes.isEmpty()) { + localSchemes.add("file"); +#if PLATFORM(MAC) + localSchemes.add("applewebdata"); +#endif +#if PLATFORM(QT) + localSchemes.add("qrc"); +#endif + } + + return localSchemes; +} + +void FrameLoader::commitIconURLToIconDatabase(const KURL& icon) +{ + ASSERT(iconDatabase()); + LOG(IconDatabase, "Committing iconURL %s to database for pageURLs %s and %s", icon.string().ascii().data(), m_URL.string().ascii().data(), originalRequestURL().string().ascii().data()); + iconDatabase()->setIconURLForPageURL(icon.string(), m_URL.string()); + iconDatabase()->setIconURLForPageURL(icon.string(), originalRequestURL().string()); +} + +void FrameLoader::restoreDocumentState() +{ + Document* doc = m_frame->document(); + if (!doc) + return; + + HistoryItem* itemToRestore = 0; + + switch (loadType()) { + case FrameLoadTypeReload: +#ifndef ANDROID_HISTORY_CLIENT + case FrameLoadTypeReloadAllowingStaleData: +#endif + case FrameLoadTypeSame: + case FrameLoadTypeReplace: + break; + case FrameLoadTypeBack: + case FrameLoadTypeForward: + case FrameLoadTypeIndexedBackForward: + case FrameLoadTypeRedirectWithLockedHistory: + case FrameLoadTypeStandard: +#ifdef ANDROID_HISTORY_CLIENT + case FrameLoadTypeReloadAllowingStaleData: +#endif + itemToRestore = m_currentHistoryItem.get(); + } + + if (!itemToRestore) + return; + + doc->setStateForNewFormElements(itemToRestore->documentState()); +} + +void FrameLoader::gotoAnchor() +{ + // If our URL has no ref, then we have no place we need to jump to. + // OTOH If CSS target was set previously, we want to set it to 0, recalc + // and possibly repaint because :target pseudo class may have been + // set (see bug 11321). + if (!m_URL.hasRef() && !(m_frame->document() && m_frame->document()->getCSSTarget())) + return; + + String ref = m_URL.ref(); + if (gotoAnchor(ref)) + return; + + // Try again after decoding the ref, based on the document's encoding. + if (m_decoder) + gotoAnchor(decodeURLEscapeSequences(ref, m_decoder->encoding())); +} + +void FrameLoader::finishedParsing() +{ + if (m_creatingInitialEmptyDocument) + return; + + // This can be called from the Frame's destructor, in which case we shouldn't protect ourselves + // because doing so will cause us to re-enter the destructor when protector goes out of scope. + // Null-checking the FrameView indicates whether or not we're in the destructor. + RefPtr<Frame> protector = m_frame->view() ? m_frame : 0; + + checkCompleted(); + + if (!m_frame->view()) + return; // We are being destroyed by something checkCompleted called. + + // Check if the scrollbars are really needed for the content. + // If not, remove them, relayout, and repaint. + m_frame->view()->restoreScrollbar(); + + m_client->dispatchDidFinishDocumentLoad(); + + gotoAnchor(); +} + +void FrameLoader::loadDone() +{ + if (m_frame->document()) + checkCompleted(); +} + +void FrameLoader::checkCompleted() +{ + // Any frame that hasn't completed yet? + for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) + if (!child->loader()->m_isComplete) + return; + + // Have we completed before? + if (m_isComplete) + return; + + // Are we still parsing? + if (m_frame->document() && m_frame->document()->parsing()) + return; + + // Still waiting for images/scripts? + if (m_frame->document()) + if (numRequests(m_frame->document())) + return; + +#if USE(LOW_BANDWIDTH_DISPLAY) + // as switch will be called, don't complete yet + if (m_frame->document() && m_frame->document()->inLowBandwidthDisplay() && m_needToSwitchOutLowBandwidthDisplay) + return; +#endif + + // OK, completed. + m_isComplete = true; + + RefPtr<Frame> protect(m_frame); + checkCallImplicitClose(); // if we didn't do it before + + // Do not start a redirection timer for subframes here. + // That is deferred until the parent is completed. + if (m_scheduledRedirection && !m_frame->tree()->parent()) + startRedirectionTimer(); + + completed(); + if (m_frame->page()) + checkLoadComplete(); +} + +void FrameLoader::checkCompletedTimerFired(Timer<FrameLoader>*) +{ + checkCompleted(); +} + +void FrameLoader::scheduleCheckCompleted() +{ + if (!m_checkCompletedTimer.isActive()) + m_checkCompletedTimer.startOneShot(0); +} + +void FrameLoader::checkLoadCompleteTimerFired(Timer<FrameLoader>*) +{ + if (!m_frame->page()) + return; + checkLoadComplete(); +} + +void FrameLoader::scheduleCheckLoadComplete() +{ + if (!m_checkLoadCompleteTimer.isActive()) + m_checkLoadCompleteTimer.startOneShot(0); +} + +void FrameLoader::checkCallImplicitClose() +{ + if (m_didCallImplicitClose || !m_frame->document() || m_frame->document()->parsing()) + return; + + for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) + if (!child->loader()->m_isComplete) // still got a frame running -> too early + return; + + m_didCallImplicitClose = true; + m_wasUnloadEventEmitted = false; + if (m_frame->document()) + m_frame->document()->implicitClose(); +} + +KURL FrameLoader::baseURL() const +{ + ASSERT(m_frame->document()); + return m_frame->document()->baseURL(); +} + +String FrameLoader::baseTarget() const +{ + ASSERT(m_frame->document()); + return m_frame->document()->baseTarget(); +} + +KURL FrameLoader::completeURL(const String& url) +{ + ASSERT(m_frame->document()); + return m_frame->document()->completeURL(url); +} + +void FrameLoader::scheduleHTTPRedirection(double delay, const String& url) +{ + if (delay < 0 || delay > INT_MAX / 1000) + return; + + if (!m_frame->page()) + return; + + // We want a new history item if the refresh timeout is > 1 second. + if (!m_scheduledRedirection || delay <= m_scheduledRedirection->delay) +#ifdef ANDROID_USER_GESTURE + { + bool wasUserGesture = false; + DocumentLoader* docLoader = activeDocumentLoader(); + if (docLoader) + wasUserGesture = docLoader->request().userGesture(); + scheduleRedirection(new ScheduledRedirection(delay, url, delay <= 1, wasUserGesture)); + } +#else + scheduleRedirection(new ScheduledRedirection(delay, url, delay <= 1, false)); +#endif +} + +void FrameLoader::scheduleLocationChange(const String& url, const String& referrer, bool lockHistory, bool wasUserGesture) +{ + if (!m_frame->page()) + return; + + // If the URL we're going to navigate to is the same as the current one, except for the + // fragment part, we don't need to schedule the location change. + KURL parsedURL(url); + if (parsedURL.hasRef() && equalIgnoringRef(m_URL, parsedURL)) { + changeLocation(url, referrer, lockHistory, wasUserGesture); + return; + } + + // Handle a location change of a page with no document as a special case. + // This may happen when a frame changes the location of another frame. + bool duringLoad = !m_committedFirstRealDocumentLoad; + + // If a redirect was scheduled during a load, then stop the current load. + // Otherwise when the current load transitions from a provisional to a + // committed state, pending redirects may be cancelled. + if (duringLoad) { + if (m_provisionalDocumentLoader) + m_provisionalDocumentLoader->stopLoading(); + stopLoading(true); + } + + ScheduledRedirection::Type type = duringLoad + ? ScheduledRedirection::locationChangeDuringLoad : ScheduledRedirection::locationChange; + scheduleRedirection(new ScheduledRedirection(type, url, referrer, lockHistory, wasUserGesture)); +} + +void FrameLoader::scheduleRefresh(bool wasUserGesture) +{ + if (!m_frame->page()) + return; + + // Handle a location change of a page with no document as a special case. + // This may happen when a frame requests a refresh of another frame. + bool duringLoad = !m_frame->document(); + + // If a refresh was scheduled during a load, then stop the current load. + // Otherwise when the current load transitions from a provisional to a + // committed state, pending redirects may be cancelled. + if (duringLoad) + stopLoading(true); + + ScheduledRedirection::Type type = duringLoad + ? ScheduledRedirection::locationChangeDuringLoad : ScheduledRedirection::locationChange; + scheduleRedirection(new ScheduledRedirection(type, m_URL.string(), m_outgoingReferrer, true, wasUserGesture)); + m_cachePolicy = CachePolicyRefresh; +} + +bool FrameLoader::isLocationChange(const ScheduledRedirection& redirection) +{ + switch (redirection.type) { + case ScheduledRedirection::redirection: + return false; + case ScheduledRedirection::historyNavigation: + case ScheduledRedirection::locationChange: + case ScheduledRedirection::locationChangeDuringLoad: + return true; + } + ASSERT_NOT_REACHED(); + return false; +} + +void FrameLoader::scheduleHistoryNavigation(int steps) +{ + if (!m_frame->page()) + return; + + // navigation will always be allowed in the 0 steps case, which is OK because that's supposed to force a reload. + if (!canGoBackOrForward(steps)) { + cancelRedirection(); + return; + } + + // If the steps to navigate is not zero (which needs to force a reload), and if we think the navigation is going to be a fragment load + // (when the URL we're going to navigate to is the same as the current one, except for the fragment part - but not exactly the same because that's a reload), + // then we don't need to schedule the navigation. + if (steps != 0) { + KURL destination = historyURL(steps); + // FIXME: This doesn't seem like a reliable way to tell whether or not the load will be a fragment load. + if (equalIgnoringRef(m_URL, destination) && m_URL != destination) { + goBackOrForward(steps); + return; + } + } + + scheduleRedirection(new ScheduledRedirection(steps)); +} + +void FrameLoader::goBackOrForward(int distance) +{ + if (distance == 0) + return; + + Page* page = m_frame->page(); + if (!page) + return; + BackForwardList* list = page->backForwardList(); + if (!list) + return; + + HistoryItem* item = list->itemAtIndex(distance); + if (!item) { + if (distance > 0) { + int forwardListCount = list->forwardListCount(); + if (forwardListCount > 0) + item = list->itemAtIndex(forwardListCount); + } else { + int backListCount = list->backListCount(); + if (backListCount > 0) + item = list->itemAtIndex(-backListCount); + } + } + + ASSERT(item); // we should not reach this line with an empty back/forward list + if (item) + page->goToItem(item, FrameLoadTypeIndexedBackForward); +} + +void FrameLoader::redirectionTimerFired(Timer<FrameLoader>*) +{ + ASSERT(m_frame->page()); + + OwnPtr<ScheduledRedirection> redirection(m_scheduledRedirection.release()); + + switch (redirection->type) { + case ScheduledRedirection::redirection: + case ScheduledRedirection::locationChange: + case ScheduledRedirection::locationChangeDuringLoad: + changeLocation(redirection->url, redirection->referrer, + redirection->lockHistory, redirection->wasUserGesture); + return; + case ScheduledRedirection::historyNavigation: + if (redirection->historySteps == 0) { + // Special case for go(0) from a frame -> reload only the frame + urlSelected(m_URL, "", 0, redirection->lockHistory, redirection->wasUserGesture); + return; + } + // go(i!=0) from a frame navigates into the history of the frame only, + // in both IE and NS (but not in Mozilla). We can't easily do that. + goBackOrForward(redirection->historySteps); + return; + } + + ASSERT_NOT_REACHED(); +} + +/* + In the case of saving state about a page with frames, we store a tree of items that mirrors the frame tree. + The item that was the target of the user's navigation is designated as the "targetItem". + When this method is called with doClip=YES we're able to create the whole tree except for the target's children, + which will be loaded in the future. That part of the tree will be filled out as the child loads are committed. +*/ +void FrameLoader::loadURLIntoChildFrame(const KURL& url, const String& referer, Frame* childFrame) +{ + ASSERT(childFrame); + HistoryItem* parentItem = currentHistoryItem(); + FrameLoadType loadType = this->loadType(); + FrameLoadType childLoadType = FrameLoadTypeRedirectWithLockedHistory; + + KURL workingURL = url; + + // If we're moving in the backforward list, we might want to replace the content + // of this child frame with whatever was there at that point. + // Reload will maintain the frame contents, LoadSame will not. + if (parentItem && parentItem->children().size() && + (isBackForwardLoadType(loadType) || loadType == FrameLoadTypeReloadAllowingStaleData)) + { + HistoryItem* childItem = parentItem->childItemWithName(childFrame->tree()->name()); + if (childItem) { + // Use the original URL to ensure we get all the side-effects, such as + // onLoad handlers, of any redirects that happened. An example of where + // this is needed is Radar 3213556. + workingURL = KURL(childItem->originalURLString()); + // These behaviors implied by these loadTypes should apply to the child frames + childLoadType = loadType; + + if (isBackForwardLoadType(loadType)) { + // For back/forward, remember this item so we can traverse any child items as child frames load + childFrame->loader()->setProvisionalHistoryItem(childItem); + } else { + // For reload, just reinstall the current item, since a new child frame was created but we won't be creating a new BF item + childFrame->loader()->setCurrentHistoryItem(childItem); + } + } + } + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + RefPtr<Archive> subframeArchive = activeDocumentLoader()->popArchiveForSubframe(childFrame->tree()->name()); + + if (subframeArchive) + childFrame->loader()->loadArchive(subframeArchive.release()); + else +#endif +#ifdef ANDROID_USER_GESTURE + childFrame->loader()->loadURL(workingURL, referer, String(), childLoadType, 0, 0, false); +#else + childFrame->loader()->loadURL(workingURL, referer, String(), childLoadType, 0, 0); +#endif +} + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size +void FrameLoader::loadArchive(PassRefPtr<Archive> prpArchive) +{ + RefPtr<Archive> archive = prpArchive; + + ArchiveResource* mainResource = archive->mainResource(); + ASSERT(mainResource); + if (!mainResource) + return; + + SubstituteData substituteData(mainResource->data(), mainResource->mimeType(), mainResource->textEncoding(), KURL()); + + ResourceRequest request(mainResource->url()); +#if PLATFORM(MAC) + request.applyWebArchiveHackForMail(); +#endif + + RefPtr<DocumentLoader> documentLoader = m_client->createDocumentLoader(request, substituteData); + documentLoader->addAllArchiveResources(archive.get()); + load(documentLoader.get()); +} +#endif + +String FrameLoader::encoding() const +{ + if (m_encodingWasChosenByUser && !m_encoding.isEmpty()) + return m_encoding; + if (m_decoder && m_decoder->encoding().isValid()) + return m_decoder->encoding().name(); + Settings* settings = m_frame->settings(); + return settings ? settings->defaultTextEncodingName() : String(); +} + +bool FrameLoader::gotoAnchor(const String& name) +{ + ASSERT(m_frame->document()); + + if (!m_frame->document()->haveStylesheetsLoaded()) { + m_frame->document()->setGotoAnchorNeededAfterStylesheetsLoad(true); + return false; + } + + m_frame->document()->setGotoAnchorNeededAfterStylesheetsLoad(false); + + Node* anchorNode = m_frame->document()->getElementById(AtomicString(name)); + if (!anchorNode && !name.isEmpty()) + anchorNode = m_frame->document()->anchors()->namedItem(name, !m_frame->document()->inCompatMode()); + +#if ENABLE(SVG) + if (m_frame->document()->isSVGDocument()) { + if (name.startsWith("xpointer(")) { + // We need to parse the xpointer reference here + } else if (name.startsWith("svgView(")) { + RefPtr<SVGSVGElement> svg = static_cast<SVGDocument*>(m_frame->document())->rootElement(); + if (!svg->currentView()->parseViewSpec(name)) + return false; + svg->setUseCurrentView(true); + } else { + if (anchorNode && anchorNode->hasTagName(SVGNames::viewTag)) { + RefPtr<SVGViewElement> viewElement = anchorNode->hasTagName(SVGNames::viewTag) ? static_cast<SVGViewElement*>(anchorNode) : 0; + if (viewElement.get()) { + RefPtr<SVGSVGElement> svg = static_cast<SVGSVGElement*>(SVGLocatable::nearestViewportElement(viewElement.get())); + svg->inheritViewAttributes(viewElement.get()); + } + } + } + // FIXME: need to decide which <svg> to focus on, and zoom to that one + // FIXME: need to actually "highlight" the viewTarget(s) + } +#endif + + m_frame->document()->setCSSTarget(anchorNode); // Setting to null will clear the current target. + + // Implement the rule that "" and "top" both mean top of page as in other browsers. + if (!anchorNode && !(name.isEmpty() || equalIgnoringCase(name, "top"))) + return false; + + // We need to update the layout before scrolling, otherwise we could + // really mess things up if an anchor scroll comes at a bad moment. + if (m_frame->document()) { + m_frame->document()->updateRendering(); + // Only do a layout if changes have occurred that make it necessary. + if (m_frame->view() && m_frame->contentRenderer() && m_frame->contentRenderer()->needsLayout()) + m_frame->view()->layout(); + } + + // Scroll nested layers and frames to reveal the anchor. + // Align to the top and to the closest side (this matches other browsers). + RenderObject* renderer; + IntRect rect; + if (!anchorNode) + renderer = m_frame->document()->renderer(); // top of document + else { + renderer = anchorNode->renderer(); + rect = anchorNode->getRect(); + } + if (renderer) + renderer->enclosingLayer()->scrollRectToVisible(rect, true, RenderLayer::gAlignToEdgeIfNeeded, RenderLayer::gAlignTopAlways); + + return true; +} + +bool FrameLoader::requestObject(RenderPart* renderer, const String& url, const AtomicString& frameName, + const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues) +{ + if (url.isEmpty() && mimeType.isEmpty()) + return false; + +#if USE(LOW_BANDWIDTH_DISPLAY) + // don't care object during low bandwidth display + if (frame()->document()->inLowBandwidthDisplay()) { + m_needToSwitchOutLowBandwidthDisplay = true; + return false; + } +#endif + + KURL completedURL; + if (!url.isEmpty()) + completedURL = completeURL(url); + + bool useFallback; + if (shouldUsePlugin(completedURL, mimeType, renderer->hasFallbackContent(), useFallback)) { + Settings* settings = m_frame->settings(); + if (!settings || !settings->arePluginsEnabled() || + (!settings->isJavaEnabled() && MIMETypeRegistry::isJavaAppletMIMEType(mimeType))) + return false; + return loadPlugin(renderer, completedURL, mimeType, paramNames, paramValues, useFallback); + } + + ASSERT(renderer->node()->hasTagName(objectTag) || renderer->node()->hasTagName(embedTag)); + HTMLPlugInElement* element = static_cast<HTMLPlugInElement*>(renderer->node()); + + // FIXME: OK to always make a new frame? When does the old frame get removed? + return loadSubframe(element, completedURL, frameName, m_outgoingReferrer); +} + +bool FrameLoader::shouldUsePlugin(const KURL& url, const String& mimeType, bool hasFallback, bool& useFallback) +{ + // Allow other plug-ins to win over QuickTime because if the user has installed a plug-in that + // can handle TIFF (which QuickTime can also handle) they probably intended to override QT. + if (m_frame->page() && (mimeType == "image/tiff" || mimeType == "image/tif" || mimeType == "image/x-tiff")) { + String pluginName = m_frame->page()->pluginData()->pluginNameForMimeType(mimeType); + if (!pluginName.isEmpty() && !pluginName.contains("QuickTime", false)) + return true; + } + + ObjectContentType objectType = m_client->objectContentType(url, mimeType); + // If an object's content can't be handled and it has no fallback, let + // it be handled as a plugin to show the broken plugin icon. + useFallback = objectType == ObjectContentNone && hasFallback; + return objectType == ObjectContentNone || objectType == ObjectContentNetscapePlugin || objectType == ObjectContentOtherPlugin; +} + +bool FrameLoader::loadPlugin(RenderPart* renderer, const KURL& url, const String& mimeType, + const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback) +{ + Widget* widget = 0; + + if (renderer && !useFallback) { + Element* pluginElement = 0; + if (renderer->node() && renderer->node()->isElementNode()) + pluginElement = static_cast<Element*>(renderer->node()); + + if (!canLoad(url, String(), frame()->document())) { + FrameLoader::reportLocalLoadFailed(m_frame, url.string()); + return false; + } + + widget = m_client->createPlugin(IntSize(renderer->contentWidth(), renderer->contentHeight()), + pluginElement, url, paramNames, paramValues, mimeType, + m_frame->document()->isPluginDocument()); + if (widget) { + renderer->setWidget(widget); + m_containsPlugIns = true; + } + } + + return widget != 0; +} + +void FrameLoader::clearRecordedFormValues() +{ + m_formAboutToBeSubmitted = 0; + m_formValuesAboutToBeSubmitted.clear(); +} + +void FrameLoader::setFormAboutToBeSubmitted(PassRefPtr<HTMLFormElement> element) +{ + m_formAboutToBeSubmitted = element; +} + +void FrameLoader::recordFormValue(const String& name, const String& value) +{ + m_formValuesAboutToBeSubmitted.set(name, value); +} + +void FrameLoader::parentCompleted() +{ + if (m_scheduledRedirection && !m_redirectionTimer.isActive()) + startRedirectionTimer(); +} + +String FrameLoader::outgoingReferrer() const +{ + return m_outgoingReferrer; +} + +String FrameLoader::outgoingOrigin() const +{ + if (m_frame->document()) + return m_frame->document()->securityOrigin()->toString(); + + return SecurityOrigin::createEmpty()->toString(); +} + +Frame* FrameLoader::opener() +{ + return m_opener; +} + +void FrameLoader::setOpener(Frame* opener) +{ + if (m_opener) + m_opener->loader()->m_openedFrames.remove(m_frame); + if (opener) + opener->loader()->m_openedFrames.add(m_frame); + m_opener = opener; + + if (m_frame->document()) { + m_frame->document()->initSecurityContext(); + m_frame->domWindow()->setSecurityOrigin(m_frame->document()->securityOrigin()); + } +} + +bool FrameLoader::openedByDOM() const +{ + return m_openedByDOM; +} + +void FrameLoader::setOpenedByDOM() +{ + m_openedByDOM = true; +} + +void FrameLoader::handleFallbackContent() +{ + HTMLFrameOwnerElement* owner = m_frame->ownerElement(); + if (!owner || !owner->hasTagName(objectTag)) + return; + static_cast<HTMLObjectElement*>(owner)->renderFallbackContent(); +} + +void FrameLoader::provisionalLoadStarted() +{ +#ifdef ANDROID_INSTRUMENT + if (!m_frame->tree()->parent()) + android::TimeCounter::reset(); +#endif + + Page* page = m_frame->page(); + + // this is used to update the current history item + // in the event of a navigation aytime during loading + m_navigationDuringLoad = false; + if (page) { + Document *document = page->mainFrame()->document(); + m_navigationDuringLoad = !page->mainFrame()->loader()->isComplete() || (document && document->processingLoadEvent()); + } + + m_firstLayoutDone = false; + cancelRedirection(true); + m_client->provisionalLoadStarted(); +} + +bool FrameLoader::userGestureHint() +{ + Frame* rootFrame = m_frame; + while (rootFrame->tree()->parent()) + rootFrame = rootFrame->tree()->parent(); + + if (rootFrame->script()->isEnabled()) + return rootFrame->script()->processingUserGesture(); + + return true; // If JavaScript is disabled, a user gesture must have initiated the navigation +} + +void FrameLoader::didNotOpenURL(const KURL& url) +{ + if (m_submittedFormURL == url) + m_submittedFormURL = KURL(); +} + +void FrameLoader::resetMultipleFormSubmissionProtection() +{ + m_submittedFormURL = KURL(); +} + +void FrameLoader::setEncoding(const String& name, bool userChosen) +{ + if (!m_workingURL.isEmpty()) + receivedFirstData(); + m_encoding = name; + m_encodingWasChosenByUser = userChosen; +} + +void FrameLoader::addData(const char* bytes, int length) +{ + ASSERT(m_workingURL.isEmpty()); + ASSERT(m_frame->document()); + ASSERT(m_frame->document()->parsing()); + write(bytes, length); +} + +bool FrameLoader::canCachePage() +{ + // Cache the page, if possible. + // Don't write to the cache if in the middle of a redirect, since we will want to + // store the final page we end up on. + // No point writing to the cache on a reload or loadSame, since we will just write + // over it again when we leave that page. + // FIXME: <rdar://problem/4886592> - We should work out the complexities of caching pages with frames as they + // are the most interesting pages on the web, and often those that would benefit the most from caching! + FrameLoadType loadType = this->loadType(); + + return m_documentLoader + && m_documentLoader->mainDocumentError().isNull() + && !m_frame->tree()->childCount() + && !m_frame->tree()->parent() + // FIXME: If we ever change this so that pages with plug-ins will be cached, + // we need to make sure that we don't cache pages that have outstanding NPObjects + // (objects created by the plug-in). Since there is no way to pause/resume a Netscape plug-in, + // they would need to be destroyed and then recreated, and there is no way that we can recreate + // the right NPObjects. See <rdar://problem/5197041> for more information. + && !m_containsPlugIns + && !m_URL.protocolIs("https") + && m_frame->document() + && !m_frame->document()->hasWindowEventListener(eventNames().unloadEvent) +#if ENABLE(DATABASE) + && !m_frame->document()->hasOpenDatabases() +#endif + && !m_frame->document()->usingGeolocation() + && m_frame->page() + && m_frame->page()->backForwardList()->enabled() + && m_frame->page()->backForwardList()->capacity() > 0 + && m_frame->page()->settings()->usesPageCache() + && m_currentHistoryItem + && !isQuickRedirectComing() + && loadType != FrameLoadTypeReload + && loadType != FrameLoadTypeReloadAllowingStaleData + && loadType != FrameLoadTypeSame + && !m_documentLoader->isLoadingInAPISense() + && !m_documentLoader->isStopping() +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + // FIXME: We should investigating caching pages that have an associated + // application cache. <rdar://problem/5917899> tracks that work. + && !m_documentLoader->applicationCache() + && !m_documentLoader->candidateApplicationCacheGroup() +#endif + ; +} + +void FrameLoader::updatePolicyBaseURL() +{ + if (m_frame->tree()->parent() && m_frame->tree()->parent()->document()) + setPolicyBaseURL(m_frame->tree()->parent()->document()->policyBaseURL()); + else + setPolicyBaseURL(m_URL); +} + +void FrameLoader::setPolicyBaseURL(const KURL& url) +{ + if (m_frame->document()) + m_frame->document()->setPolicyBaseURL(url); + for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) + child->loader()->setPolicyBaseURL(url); +} + +// This does the same kind of work that didOpenURL does, except it relies on the fact +// that a higher level already checked that the URLs match and the scrolling is the right thing to do. +void FrameLoader::scrollToAnchor(const KURL& url) +{ + m_URL = url; + updateHistoryForAnchorScroll(); + + // If we were in the autoscroll/panScroll mode we want to stop it before following the link to the anchor + m_frame->eventHandler()->stopAutoscrollTimer(); + started(); + gotoAnchor(); + + // It's important to model this as a load that starts and immediately finishes. + // Otherwise, the parent frame may think we never finished loading. + m_isComplete = false; + checkCompleted(); +} + +bool FrameLoader::isComplete() const +{ + return m_isComplete; +} + +void FrameLoader::scheduleRedirection(ScheduledRedirection* redirection) +{ + ASSERT(m_frame->page()); + + stopRedirectionTimer(); + m_scheduledRedirection.set(redirection); + if (!m_isComplete && redirection->type != ScheduledRedirection::redirection) + completed(); + if (m_isComplete || redirection->type != ScheduledRedirection::redirection) + startRedirectionTimer(); +} + +void FrameLoader::startRedirectionTimer() +{ + ASSERT(m_frame->page()); + ASSERT(m_scheduledRedirection); + + m_redirectionTimer.stop(); + m_redirectionTimer.startOneShot(m_scheduledRedirection->delay); + + switch (m_scheduledRedirection->type) { + case ScheduledRedirection::redirection: + case ScheduledRedirection::locationChange: + case ScheduledRedirection::locationChangeDuringLoad: + clientRedirected(KURL(m_scheduledRedirection->url), + m_scheduledRedirection->delay, + currentTime() + m_redirectionTimer.nextFireInterval(), + m_scheduledRedirection->lockHistory, + m_isExecutingJavaScriptFormAction); + return; + case ScheduledRedirection::historyNavigation: + // Don't report history navigations. + return; + } + ASSERT_NOT_REACHED(); +} + +void FrameLoader::stopRedirectionTimer() +{ + if (!m_redirectionTimer.isActive()) + return; + + m_redirectionTimer.stop(); + + if (m_scheduledRedirection) { + switch (m_scheduledRedirection->type) { + case ScheduledRedirection::redirection: + case ScheduledRedirection::locationChange: + case ScheduledRedirection::locationChangeDuringLoad: + clientRedirectCancelledOrFinished(m_cancellingWithLoadInProgress); + return; + case ScheduledRedirection::historyNavigation: + // Don't report history navigations. + return; + } + ASSERT_NOT_REACHED(); + } +} + +void FrameLoader::completed() +{ + RefPtr<Frame> protect(m_frame); + for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) + child->loader()->parentCompleted(); + if (Frame* parent = m_frame->tree()->parent()) + parent->loader()->checkCompleted(); + submitFormAgain(); +} + +void FrameLoader::started() +{ + for (Frame* frame = m_frame; frame; frame = frame->tree()->parent()) + frame->loader()->m_isComplete = false; +} + +bool FrameLoader::containsPlugins() const +{ + return m_containsPlugIns; +} + +void FrameLoader::prepareForLoadStart() +{ + if (Page* page = m_frame->page()) + page->progress()->progressStarted(m_frame); + m_client->dispatchDidStartProvisionalLoad(); +} + +void FrameLoader::setupForReplace() +{ + setState(FrameStateProvisional); + m_provisionalDocumentLoader = m_documentLoader; + m_documentLoader = 0; + detachChildren(); +} + +void FrameLoader::setupForReplaceByMIMEType(const String& newMIMEType) +{ + activeDocumentLoader()->setupForReplaceByMIMEType(newMIMEType); +} + +void FrameLoader::loadFrameRequestWithFormState(const FrameLoadRequest& request, bool lockHistory, Event* event, PassRefPtr<FormState> prpFormState) +{ + RefPtr<FormState> formState = prpFormState; + KURL url = request.resourceRequest().url(); + + String referrer; + String argsReferrer = request.resourceRequest().httpReferrer(); + if (!argsReferrer.isEmpty()) + referrer = argsReferrer; + else + referrer = m_outgoingReferrer; + + ASSERT(frame()->document()); + if (url.protocolIs("file")) { + if (!canLoad(url, String(), frame()->document()) && !canLoad(url, referrer)) { + FrameLoader::reportLocalLoadFailed(m_frame, url.string()); + return; + } + } + + if (shouldHideReferrer(url, referrer)) + referrer = String(); + + Frame* targetFrame = findFrameForNavigation(request.frameName()); + + if (request.resourceRequest().httpMethod() != "POST") { + FrameLoadType loadType; + if (request.resourceRequest().cachePolicy() == ReloadIgnoringCacheData) + loadType = FrameLoadTypeReload; + else if (lockHistory) + loadType = FrameLoadTypeRedirectWithLockedHistory; + else + loadType = FrameLoadTypeStandard; + +#ifdef ANDROID_USER_GESTURE + loadURL(request.resourceRequest().url(), referrer, request.frameName(), loadType, + event, formState.release(), request.wasUserGesture()); +#else + loadURL(request.resourceRequest().url(), referrer, request.frameName(), loadType, + event, formState.release()); +#endif + } else +#ifdef ANDROID_USER_GESTURE + loadPostRequest(request.resourceRequest(), referrer, request.frameName(), event, formState.release(), request.wasUserGesture()); +#else + loadPostRequest(request.resourceRequest(), referrer, request.frameName(), event, formState.release()); +#endif + + if (targetFrame && targetFrame != m_frame) + if (Page* page = targetFrame->page()) + page->chrome()->focus(); +} + +void FrameLoader::loadFrameRequestWithFormAndValues(const FrameLoadRequest& request, bool lockHistory, Event* event, + HTMLFormElement* submitForm, const HashMap<String, String>& formValues) +{ + RefPtr<FormState> formState; + if (submitForm) + formState = FormState::create(submitForm, formValues, m_frame); + + loadFrameRequestWithFormState(request, lockHistory, event, formState.release()); +} + +#ifdef ANDROID_USER_GESTURE +void FrameLoader::loadURL(const KURL& newURL, const String& referrer, const String& frameName, FrameLoadType newLoadType, + Event* event, PassRefPtr<FormState> prpFormState, bool userGesture) +#else +void FrameLoader::loadURL(const KURL& newURL, const String& referrer, const String& frameName, FrameLoadType newLoadType, + Event* event, PassRefPtr<FormState> prpFormState) +#endif +{ + RefPtr<FormState> formState = prpFormState; + bool isFormSubmission = formState; + + ResourceRequest request(newURL); +#ifdef ANDROID_USER_GESTURE + request.setUserGesture(userGesture); +#endif + if (!referrer.isEmpty()) { + request.setHTTPReferrer(referrer); + RefPtr<SecurityOrigin> referrerOrigin = SecurityOrigin::createFromString(referrer); + addHTTPOriginIfNeeded(request, referrerOrigin->toString()); + } + addExtraFieldsToRequest(request, true, event || isFormSubmission); + if (newLoadType == FrameLoadTypeReload) + request.setCachePolicy(ReloadIgnoringCacheData); + + ASSERT(newLoadType != FrameLoadTypeSame); + + NavigationAction action(newURL, newLoadType, isFormSubmission, event); + + if (!frameName.isEmpty()) { + if (Frame* targetFrame = findFrameForNavigation(frameName)) +#ifdef ANDROID_USER_GESTURE + targetFrame->loader()->loadURL(newURL, referrer, String(), newLoadType, event, formState, userGesture); +#else + targetFrame->loader()->loadURL(newURL, referrer, String(), newLoadType, event, formState); +#endif + else + checkNewWindowPolicy(action, request, formState, frameName); + return; + } + + RefPtr<DocumentLoader> oldDocumentLoader = m_documentLoader; + + bool sameURL = shouldTreatURLAsSameAsCurrent(newURL); + + // Make sure to do scroll to anchor processing even if the URL is + // exactly the same so pages with '#' links and DHTML side effects + // work properly. + if (shouldScrollToAnchor(isFormSubmission, newLoadType, newURL)) { + oldDocumentLoader->setTriggeringAction(action); + stopPolicyCheck(); + checkNavigationPolicy(request, oldDocumentLoader.get(), formState, + callContinueFragmentScrollAfterNavigationPolicy, this); + } else { + // must grab this now, since this load may stop the previous load and clear this flag + bool isRedirect = m_quickRedirectComing; + loadWithNavigationAction(request, action, newLoadType, formState); + if (isRedirect) { + m_quickRedirectComing = false; + if (m_provisionalDocumentLoader) + m_provisionalDocumentLoader->setIsClientRedirect(true); +#ifdef ANDROID_HISTORY_CLIENT + } else if (sameURL && (newLoadType != FrameLoadTypeReloadAllowingStaleData)) +#else + } else if (sameURL) +#endif + // Example of this case are sites that reload the same URL with a different cookie + // driving the generated content, or a master frame with links that drive a target + // frame, where the user has clicked on the same link repeatedly. + m_loadType = FrameLoadTypeSame; + } +} + +void FrameLoader::load(const ResourceRequest& request) +{ + load(request, SubstituteData()); +} + +void FrameLoader::load(const ResourceRequest& request, const SubstituteData& substituteData) +{ + if (m_inStopAllLoaders) + return; + + // FIXME: is this the right place to reset loadType? Perhaps this should be done after loading is finished or aborted. + m_loadType = FrameLoadTypeStandard; + load(m_client->createDocumentLoader(request, substituteData).get()); +} + +void FrameLoader::load(const ResourceRequest& request, const String& frameName) +{ + if (frameName.isEmpty()) { + load(request); + return; + } + + Frame* frame = findFrameForNavigation(frameName); + if (frame) { + frame->loader()->load(request); + return; + } + + checkNewWindowPolicy(NavigationAction(request.url(), NavigationTypeOther), request, 0, frameName); +} + +void FrameLoader::loadWithNavigationAction(const ResourceRequest& request, const NavigationAction& action, FrameLoadType type, PassRefPtr<FormState> formState) +{ + RefPtr<DocumentLoader> loader = m_client->createDocumentLoader(request, SubstituteData()); + + loader->setTriggeringAction(action); + if (m_documentLoader) + loader->setOverrideEncoding(m_documentLoader->overrideEncoding()); + + loadWithDocumentLoader(loader.get(), type, formState); +} + +void FrameLoader::load(DocumentLoader* newDocumentLoader) +{ + ResourceRequest& r = newDocumentLoader->request(); + addExtraFieldsToRequest(r, true, false); + FrameLoadType type; + + if (shouldTreatURLAsSameAsCurrent(newDocumentLoader->originalRequest().url())) { + r.setCachePolicy(ReloadIgnoringCacheData); + type = FrameLoadTypeSame; + } else + type = FrameLoadTypeStandard; + + if (m_documentLoader) + newDocumentLoader->setOverrideEncoding(m_documentLoader->overrideEncoding()); + + // When we loading alternate content for an unreachable URL that we're + // visiting in the history list, we treat it as a reload so the history list + // is appropriately maintained. + // + // FIXME: This seems like a dangerous overloading of the meaning of "FrameLoadTypeReload" ... + // shouldn't a more explicit type of reload be defined, that means roughly + // "load without affecting history" ? + if (shouldReloadToHandleUnreachableURL(newDocumentLoader)) { + ASSERT(type == FrameLoadTypeStandard); + type = FrameLoadTypeReload; + } + + loadWithDocumentLoader(newDocumentLoader, type, 0); +} + +void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType type, PassRefPtr<FormState> prpFormState) +{ + ASSERT(m_client->hasWebView()); + + // Unfortunately the view must be non-nil, this is ultimately due + // to parser requiring a FrameView. We should fix this dependency. + + ASSERT(m_frame->view()); + + m_policyLoadType = type; + RefPtr<FormState> formState = prpFormState; + bool isFormSubmission = formState; + + const KURL& newURL = loader->request().url(); + + if (shouldScrollToAnchor(isFormSubmission, m_policyLoadType, newURL)) { + RefPtr<DocumentLoader> oldDocumentLoader = m_documentLoader; + NavigationAction action(newURL, m_policyLoadType, isFormSubmission); + + oldDocumentLoader->setTriggeringAction(action); + stopPolicyCheck(); + checkNavigationPolicy(loader->request(), oldDocumentLoader.get(), formState, + callContinueFragmentScrollAfterNavigationPolicy, this); + } else { + if (Frame* parent = m_frame->tree()->parent()) + loader->setOverrideEncoding(parent->loader()->documentLoader()->overrideEncoding()); + + stopPolicyCheck(); + setPolicyDocumentLoader(loader); + + checkNavigationPolicy(loader->request(), loader, formState, + callContinueLoadAfterNavigationPolicy, this); + } +} + +bool FrameLoader::canLoad(const KURL& url, const String& referrer, const Document* doc) +{ + // We can always load any URL that isn't considered local (e.g. http URLs) + if (!shouldTreatURLAsLocal(url.string())) + return true; + + // If we were provided a document, we let its local file policy dictate the result, + // otherwise we allow local loads only if the supplied referrer is also local. + if (doc) + return doc->securityOrigin()->canLoadLocalResources(); + else if (!referrer.isEmpty()) + return shouldTreatURLAsLocal(referrer); + else + return false; +} + +void FrameLoader::reportLocalLoadFailed(Frame* frame, const String& url) +{ + ASSERT(!url.isEmpty()); + if (!frame) + return; + + frame->domWindow()->console()->addMessage(JSMessageSource, ErrorMessageLevel, "Not allowed to load local resource: " + url, 0, String()); +} + +bool FrameLoader::shouldHideReferrer(const KURL& url, const String& referrer) +{ + bool referrerIsSecureURL = protocolIs(referrer, "https"); + bool referrerIsWebURL = referrerIsSecureURL || protocolIs(referrer, "http"); + + if (!referrerIsWebURL) + return true; + + if (!referrerIsSecureURL) + return false; + + bool URLIsSecureURL = url.protocolIs("https"); + + return !URLIsSecureURL; +} + +const ResourceRequest& FrameLoader::initialRequest() const +{ + return activeDocumentLoader()->originalRequest(); +} + +void FrameLoader::receivedData(const char* data, int length) +{ + activeDocumentLoader()->receivedData(data, length); +} + +void FrameLoader::handleUnimplementablePolicy(const ResourceError& error) +{ + m_delegateIsHandlingUnimplementablePolicy = true; + m_client->dispatchUnableToImplementPolicy(error); + m_delegateIsHandlingUnimplementablePolicy = false; +} + +void FrameLoader::cannotShowMIMEType(const ResourceResponse& response) +{ + handleUnimplementablePolicy(m_client->cannotShowMIMETypeError(response)); +} + +ResourceError FrameLoader::interruptionForPolicyChangeError(const ResourceRequest& request) +{ + return m_client->interruptForPolicyChangeError(request); +} + +void FrameLoader::checkNavigationPolicy(const ResourceRequest& newRequest, NavigationPolicyDecisionFunction function, void* argument) +{ + checkNavigationPolicy(newRequest, activeDocumentLoader(), 0, function, argument); +} + +void FrameLoader::checkContentPolicy(const String& MIMEType, ContentPolicyDecisionFunction function, void* argument) +{ + ASSERT(activeDocumentLoader()); + + // Always show content with valid substitute data. + if (activeDocumentLoader()->substituteData().isValid()) { + function(argument, PolicyUse); + return; + } + +#if ENABLE(FTPDIR) + // Respect the hidden FTP Directory Listing pref so it can be tested even if the policy delegate might otherwise disallow it + Settings* settings = m_frame->settings(); + if (settings && settings->forceFTPDirectoryListings() && MIMEType == "application/x-ftp-directory") { + function(argument, PolicyUse); + return; + } +#endif + + m_policyCheck.set(function, argument); + m_client->dispatchDecidePolicyForMIMEType(&FrameLoader::continueAfterContentPolicy, + MIMEType, activeDocumentLoader()->request()); +} + +bool FrameLoader::shouldReloadToHandleUnreachableURL(DocumentLoader* docLoader) +{ + KURL unreachableURL = docLoader->unreachableURL(); + + if (unreachableURL.isEmpty()) + return false; + + if (!isBackForwardLoadType(m_policyLoadType)) + return false; + + // We only treat unreachableURLs specially during the delegate callbacks + // for provisional load errors and navigation policy decisions. The former + // case handles well-formed URLs that can't be loaded, and the latter + // case handles malformed URLs and unknown schemes. Loading alternate content + // at other times behaves like a standard load. + DocumentLoader* compareDocumentLoader = 0; + if (m_delegateIsDecidingNavigationPolicy || m_delegateIsHandlingUnimplementablePolicy) + compareDocumentLoader = m_policyDocumentLoader.get(); + else if (m_delegateIsHandlingProvisionalLoadError) + compareDocumentLoader = m_provisionalDocumentLoader.get(); + + return compareDocumentLoader && unreachableURL == compareDocumentLoader->request().url(); +} + +void FrameLoader::reloadAllowingStaleData(const String& encoding) +{ + if (!m_documentLoader) + return; + + ResourceRequest request = m_documentLoader->request(); + KURL unreachableURL = m_documentLoader->unreachableURL(); + if (!unreachableURL.isEmpty()) + request.setURL(unreachableURL); + + request.setCachePolicy(ReturnCacheDataElseLoad); + + RefPtr<DocumentLoader> loader = m_client->createDocumentLoader(request, SubstituteData()); + setPolicyDocumentLoader(loader.get()); + + loader->setOverrideEncoding(encoding); + + loadWithDocumentLoader(loader.get(), FrameLoadTypeReloadAllowingStaleData, 0); +} + +void FrameLoader::reload() +{ + if (!m_documentLoader) + return; + + ResourceRequest& initialRequest = m_documentLoader->request(); + + // If a window is created by javascript, its main frame can have an empty but non-nil URL. + // Reloading in this case will lose the current contents (see 4151001). + if (initialRequest.url().isEmpty()) + return; + + // Replace error-page URL with the URL we were trying to reach. + KURL unreachableURL = m_documentLoader->unreachableURL(); + if (!unreachableURL.isEmpty()) + initialRequest = ResourceRequest(unreachableURL); + + // Create a new document loader for the reload, this will become m_documentLoader eventually, + // but first it has to be the "policy" document loader, and then the "provisional" document loader. + RefPtr<DocumentLoader> loader = m_client->createDocumentLoader(initialRequest, SubstituteData()); + + ResourceRequest& request = loader->request(); + + request.setCachePolicy(ReloadIgnoringCacheData); + request.setHTTPHeaderField("Cache-Control", "max-age=0"); + + // If we're about to re-post, set up action so the application can warn the user. + if (request.httpMethod() == "POST") + loader->setTriggeringAction(NavigationAction(request.url(), NavigationTypeFormResubmitted)); + + loader->setOverrideEncoding(m_documentLoader->overrideEncoding()); + + loadWithDocumentLoader(loader.get(), FrameLoadTypeReload, 0); +} + +static bool canAccessAncestor(const SecurityOrigin* activeSecurityOrigin, Frame* targetFrame) +{ + // targetFrame can be NULL when we're trying to navigate a top-level frame + // that has a NULL opener. + if (!targetFrame) + return false; + + for (Frame* ancestorFrame = targetFrame; ancestorFrame; ancestorFrame = ancestorFrame->tree()->parent()) { + Document* ancestorDocument = ancestorFrame->document(); + if (!ancestorDocument) + return true; + + const SecurityOrigin* ancestorSecurityOrigin = ancestorDocument->securityOrigin(); + if (activeSecurityOrigin->canAccess(ancestorSecurityOrigin)) + return true; + } + + return false; +} + +bool FrameLoader::shouldAllowNavigation(Frame* targetFrame) const +{ + // The navigation change is safe if the active frame is: + // - in the same security origin as the target or one of the target's + // ancestors. + // + // Or the target frame is: + // - a top-level frame in the frame hierarchy and the active frame can + // navigate the target frame's opener per above. + + if (!targetFrame) + return true; + + // Performance optimization. + if (m_frame == targetFrame) + return true; + + // Let a frame navigate the top-level window that contains it. This is + // important to allow because it lets a site "frame-bust" (escape from a + // frame created by another web site). + if (targetFrame == m_frame->tree()->top()) + return true; + + Document* activeDocument = m_frame->document(); + ASSERT(activeDocument); + const SecurityOrigin* activeSecurityOrigin = activeDocument->securityOrigin(); + + // For top-level windows, check the opener. + if (!targetFrame->tree()->parent() && canAccessAncestor(activeSecurityOrigin, targetFrame->loader()->opener())) + return true; + + // In general, check the frame's ancestors. + if (canAccessAncestor(activeSecurityOrigin, targetFrame)) + return true; + + Settings* settings = targetFrame->settings(); + if (settings && !settings->privateBrowsingEnabled()) { + Document* targetDocument = targetFrame->document(); + // FIXME: this error message should contain more specifics of why the navigation change is not allowed. + String message = String::format("Unsafe JavaScript attempt to initiate a navigation change for frame with URL %s from frame with URL %s.\n", + targetDocument->url().string().utf8().data(), activeDocument->url().string().utf8().data()); + + // FIXME: should we print to the console of the activeFrame as well? + targetFrame->domWindow()->console()->addMessage(JSMessageSource, ErrorMessageLevel, message, 1, String()); + } + + return false; +} + +void FrameLoader::stopLoadingSubframes() +{ + for (RefPtr<Frame> child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) + child->loader()->stopAllLoaders(); +} + +void FrameLoader::stopAllLoaders() +{ + // If this method is called from within this method, infinite recursion can occur (3442218). Avoid this. + if (m_inStopAllLoaders) + return; + + m_inStopAllLoaders = true; + + stopPolicyCheck(); + + stopLoadingSubframes(); + if (m_provisionalDocumentLoader) + m_provisionalDocumentLoader->stopLoading(); + if (m_documentLoader) + m_documentLoader->stopLoading(); + + setProvisionalDocumentLoader(0); + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + if (m_documentLoader) + m_documentLoader->clearArchiveResources(); +#endif + + m_inStopAllLoaders = false; +} + +void FrameLoader::stopForUserCancel(bool deferCheckLoadComplete) +{ + stopAllLoaders(); + + if (deferCheckLoadComplete) + scheduleCheckLoadComplete(); + else if (m_frame->page()) + checkLoadComplete(); +} + +DocumentLoader* FrameLoader::activeDocumentLoader() const +{ + if (m_state == FrameStateProvisional) + return m_provisionalDocumentLoader.get(); + return m_documentLoader.get(); +} + +bool FrameLoader::isLoading() const +{ + DocumentLoader* docLoader = activeDocumentLoader(); + if (!docLoader) + return false; + return docLoader->isLoadingMainResource() || docLoader->isLoadingSubresources() || docLoader->isLoadingPlugIns(); +} + +bool FrameLoader::frameHasLoaded() const +{ + return m_committedFirstRealDocumentLoad || (m_provisionalDocumentLoader && !m_creatingInitialEmptyDocument); +} + +void FrameLoader::setDocumentLoader(DocumentLoader* loader) +{ + if (!loader && !m_documentLoader) + return; + + ASSERT(loader != m_documentLoader); + ASSERT(!loader || loader->frameLoader() == this); + + m_client->prepareForDataSourceReplacement(); + detachChildren(); + if (m_documentLoader) + m_documentLoader->detachFromFrame(); + + m_documentLoader = loader; +} + +DocumentLoader* FrameLoader::documentLoader() const +{ + return m_documentLoader.get(); +} + +void FrameLoader::setPolicyDocumentLoader(DocumentLoader* loader) +{ + if (m_policyDocumentLoader == loader) + return; + + ASSERT(m_frame); + if (loader) + loader->setFrame(m_frame); + if (m_policyDocumentLoader + && m_policyDocumentLoader != m_provisionalDocumentLoader + && m_policyDocumentLoader != m_documentLoader) + m_policyDocumentLoader->detachFromFrame(); + + m_policyDocumentLoader = loader; +} + +DocumentLoader* FrameLoader::policyDocumentLoader() const +{ + return m_policyDocumentLoader.get(); +} + +DocumentLoader* FrameLoader::provisionalDocumentLoader() const +{ + return m_provisionalDocumentLoader.get(); +} + +void FrameLoader::setProvisionalDocumentLoader(DocumentLoader* loader) +{ + ASSERT(!loader || !m_provisionalDocumentLoader); + ASSERT(!loader || loader->frameLoader() == this); + + if (m_provisionalDocumentLoader && m_provisionalDocumentLoader != m_documentLoader) + m_provisionalDocumentLoader->detachFromFrame(); + + m_provisionalDocumentLoader = loader; +} + +FrameState FrameLoader::state() const +{ + return m_state; +} + +double FrameLoader::timeOfLastCompletedLoad() +{ + return storedTimeOfLastCompletedLoad; +} + +void FrameLoader::setState(FrameState newState) +{ + m_state = newState; + + if (newState == FrameStateProvisional) + provisionalLoadStarted(); + else if (newState == FrameStateComplete) { + frameLoadCompleted(); + storedTimeOfLastCompletedLoad = currentTime(); + if (m_documentLoader) + m_documentLoader->stopRecordingResponses(); + } +} + +void FrameLoader::clearProvisionalLoad() +{ + setProvisionalDocumentLoader(0); + if (Page* page = m_frame->page()) + page->progress()->progressCompleted(m_frame); + setState(FrameStateComplete); +} + +void FrameLoader::markLoadComplete() +{ + setState(FrameStateComplete); +} + +void FrameLoader::commitProvisionalLoad(PassRefPtr<CachedPage> prpCachedPage) +{ + RefPtr<CachedPage> cachedPage = prpCachedPage; + RefPtr<DocumentLoader> pdl = m_provisionalDocumentLoader; + + // Check to see if we need to cache the page we are navigating away from into the back/forward cache. + // We are doing this here because we know for sure that a new page is about to be loaded. + if (canCachePage() && m_client->canCachePage() && !m_currentHistoryItem->isInPageCache()) + cachePageForHistoryItem(m_currentHistoryItem.get()); + else if (m_frame->page() && m_frame == m_frame->page()->mainFrame()) { + // If the main frame installs a timeout late enough (for example in its onunload handler) + // it could sometimes fire when transitioning to a non-HTML document representation (such as the Mac bookmarks view). + // To avoid this, we clear all timeouts if the page is not to be cached in the back forward list. + // Cached pages have their timers paused so they are fine. + ScriptController* proxy = m_frame->script(); + if (proxy->haveWindowShell()) + proxy->windowShell()->window()->clearAllTimeouts(); + } + + if (m_loadType != FrameLoadTypeReplace) + closeOldDataSources(); + + if (!cachedPage && !m_creatingInitialEmptyDocument) + m_client->makeRepresentation(pdl.get()); + + transitionToCommitted(cachedPage); + + // Call clientRedirectCancelledOrFinished() here so that the frame load delegate is notified that the redirect's + // status has changed, if there was a redirect. The frame load delegate may have saved some state about + // the redirect in its -webView:willPerformClientRedirectToURL:delay:fireDate:forFrame:. Since we are + // just about to commit a new page, there cannot possibly be a pending redirect at this point. + if (m_sentRedirectNotification) + clientRedirectCancelledOrFinished(false); + + if (cachedPage && cachedPage->document()) { + open(*cachedPage); + cachedPage->clear(); + } else { + KURL url = pdl->substituteData().responseURL(); + if (url.isEmpty()) + url = pdl->url(); + if (url.isEmpty()) + url = pdl->responseURL(); + if (url.isEmpty()) + url = blankURL(); + + didOpenURL(url); + } + opened(); +} + +void FrameLoader::transitionToCommitted(PassRefPtr<CachedPage> cachedPage) +{ + ASSERT(m_client->hasWebView()); + ASSERT(m_state == FrameStateProvisional); + + if (m_state != FrameStateProvisional) + return; + + m_client->setCopiesOnScroll(); + updateHistoryForCommit(); + + // The call to closeURL() invokes the unload event handler, which can execute arbitrary + // JavaScript. If the script initiates a new load, we need to abandon the current load, + // or the two will stomp each other. + DocumentLoader* pdl = m_provisionalDocumentLoader.get(); + if (m_documentLoader) + closeURL(); + if (pdl != m_provisionalDocumentLoader) + return; + + // Nothing else can interupt this commit - set the Provisional->Committed transition in stone + if (m_documentLoader) + m_documentLoader->stopLoadingSubresources(); + if (m_documentLoader) + m_documentLoader->stopLoadingPlugIns(); + + setDocumentLoader(m_provisionalDocumentLoader.get()); + setProvisionalDocumentLoader(0); + setState(FrameStateCommittedPage); + + // Handle adding the URL to the back/forward list. + DocumentLoader* dl = m_documentLoader.get(); + String ptitle = dl->title(); + + switch (m_loadType) { + case FrameLoadTypeForward: + case FrameLoadTypeBack: + case FrameLoadTypeIndexedBackForward: + if (Page* page = m_frame->page()) + if (page->backForwardList()) { + updateHistoryForBackForwardNavigation(); + + // Create a document view for this document, or used the cached view. + if (cachedPage) { + DocumentLoader* cachedDocumentLoader = cachedPage->documentLoader(); + ASSERT(cachedDocumentLoader); + cachedDocumentLoader->setFrame(m_frame); + m_client->transitionToCommittedFromCachedPage(cachedPage.get()); + + } else + m_client->transitionToCommittedForNewPage(); + } + break; + + case FrameLoadTypeReload: + case FrameLoadTypeSame: + case FrameLoadTypeReplace: + updateHistoryForReload(); + m_client->transitionToCommittedForNewPage(); + break; + + // FIXME - just get rid of this case, and merge FrameLoadTypeReloadAllowingStaleData with the above case + case FrameLoadTypeReloadAllowingStaleData: + m_client->transitionToCommittedForNewPage(); + break; + + case FrameLoadTypeStandard: + updateHistoryForStandardLoad(); +#ifndef BUILDING_ON_TIGER + // This code was originally added for a Leopard performance imporvement. We decided to + // ifdef it to fix correctness issues on Tiger documented in <rdar://problem/5441823>. + if (m_frame->view()) + m_frame->view()->setScrollbarsSuppressed(true); +#endif + m_client->transitionToCommittedForNewPage(); + break; + + case FrameLoadTypeRedirectWithLockedHistory: + updateHistoryForRedirectWithLockedHistory(); + m_client->transitionToCommittedForNewPage(); + break; + + // FIXME Remove this check when dummy ds is removed (whatever "dummy ds" is). + // An exception should be thrown if we're in the FrameLoadTypeUninitialized state. + default: + ASSERT_NOT_REACHED(); + } + + m_responseMIMEType = dl->responseMIMEType(); + + // Tell the client we've committed this URL. + ASSERT(m_frame->view()); + + if (m_creatingInitialEmptyDocument) + return; + + m_committedFirstRealDocumentLoad = true; + + // For non-cached HTML pages, these methods are called in FrameLoader::begin. + if (cachedPage || !m_client->hasHTMLView()) { + dispatchDidCommitLoad(); + + // If we have a title let the WebView know about it. + if (!ptitle.isNull()) + m_client->dispatchDidReceiveTitle(ptitle); + } +} + +void FrameLoader::clientRedirectCancelledOrFinished(bool cancelWithLoadInProgress) +{ + // Note that -webView:didCancelClientRedirectForFrame: is called on the frame load delegate even if + // the redirect succeeded. We should either rename this API, or add a new method, like + // -webView:didFinishClientRedirectForFrame: + m_client->dispatchDidCancelClientRedirect(); + + if (!cancelWithLoadInProgress) + m_quickRedirectComing = false; + + m_sentRedirectNotification = false; +} + +void FrameLoader::clientRedirected(const KURL& url, double seconds, double fireDate, bool lockHistory, bool isJavaScriptFormAction) +{ + m_client->dispatchWillPerformClientRedirect(url, seconds, fireDate); + + // Remember that we sent a redirect notification to the frame load delegate so that when we commit + // the next provisional load, we can send a corresponding -webView:didCancelClientRedirectForFrame: + m_sentRedirectNotification = true; + + // If a "quick" redirect comes in an, we set a special mode so we treat the next + // load as part of the same navigation. If we don't have a document loader, we have + // no "original" load on which to base a redirect, so we treat the redirect as a normal load. + m_quickRedirectComing = lockHistory && m_documentLoader && !isJavaScriptFormAction; +} + +bool FrameLoader::shouldReload(const KURL& currentURL, const KURL& destinationURL) +{ + // This function implements the rule: "Don't reload if navigating by fragment within + // the same URL, but do reload if going to a new URL or to the same URL with no + // fragment identifier at all." + if (!destinationURL.hasRef()) + return true; + return !equalIgnoringRef(currentURL, destinationURL); +} + +void FrameLoader::closeOldDataSources() +{ + // FIXME: Is it important for this traversal to be postorder instead of preorder? + // If so, add helpers for postorder traversal, and use them. If not, then lets not + // use a recursive algorithm here. + for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) + child->loader()->closeOldDataSources(); + + if (m_documentLoader) + m_client->dispatchWillClose(); + + m_client->setMainFrameDocumentReady(false); // stop giving out the actual DOMDocument to observers +} + +void FrameLoader::open(CachedPage& cachedPage) +{ + ASSERT(m_frame->page()); + ASSERT(m_frame->page()->mainFrame() == m_frame); + + cancelRedirection(); + + // We still have to close the previous part page. + closeURL(); + + m_isComplete = false; + + // Don't re-emit the load event. + m_didCallImplicitClose = true; + + // Delete old status bar messages (if it _was_ activated on last URL). + if (m_frame->script()->isEnabled()) { + m_frame->setJSStatusBarText(String()); + m_frame->setJSDefaultStatusBarText(String()); + } + + KURL url = cachedPage.url(); + + if ((url.protocolIs("http") || url.protocolIs("https")) && !url.host().isEmpty() && url.path().isEmpty()) + url.setPath("/"); + + m_URL = url; + m_workingURL = url; + + started(); + + clear(); + + Document* document = cachedPage.document(); + ASSERT(document); + document->setInPageCache(false); + + m_needsClear = true; + m_isComplete = false; + m_didCallImplicitClose = false; + m_outgoingReferrer = url.string(); + + FrameView* view = cachedPage.view(); + if (view) + view->setWasScrolledByUser(false); + m_frame->setView(view); + + m_frame->setDocument(document); + m_frame->domWindow()->setURL(document->url()); + m_frame->domWindow()->setSecurityOrigin(document->securityOrigin()); + + m_decoder = document->decoder(); + + updatePolicyBaseURL(); + + cachedPage.restore(m_frame->page()); + + checkCompleted(); +} + +bool FrameLoader::isStopping() const +{ + return activeDocumentLoader()->isStopping(); +} + +void FrameLoader::finishedLoading() +{ + // Retain because the stop may release the last reference to it. + RefPtr<Frame> protect(m_frame); + + RefPtr<DocumentLoader> dl = activeDocumentLoader(); + dl->finishedLoading(); + if (!dl->mainDocumentError().isNull() || !dl->frameLoader()) + return; + dl->setPrimaryLoadComplete(true); + m_client->dispatchDidLoadMainResource(dl.get()); + checkLoadComplete(); +} + +bool FrameLoader::isHostedByObjectElement() const +{ + HTMLFrameOwnerElement* owner = m_frame->ownerElement(); + return owner && owner->hasTagName(objectTag); +} + +bool FrameLoader::isLoadingMainFrame() const +{ + Page* page = m_frame->page(); + return page && m_frame == page->mainFrame(); +} + +bool FrameLoader::canShowMIMEType(const String& MIMEType) const +{ + return m_client->canShowMIMEType(MIMEType); +} + +bool FrameLoader::representationExistsForURLScheme(const String& URLScheme) +{ + return m_client->representationExistsForURLScheme(URLScheme); +} + +String FrameLoader::generatedMIMETypeForURLScheme(const String& URLScheme) +{ + return m_client->generatedMIMETypeForURLScheme(URLScheme); +} + +void FrameLoader::cancelContentPolicyCheck() +{ + m_client->cancelPolicyCheck(); + m_policyCheck.clear(); +} + +void FrameLoader::didReceiveServerRedirectForProvisionalLoadForFrame() +{ + m_client->dispatchDidReceiveServerRedirectForProvisionalLoad(); +} + +void FrameLoader::finishedLoadingDocument(DocumentLoader* loader) +{ + // FIXME: Platforms shouldn't differ here! +#if PLATFORM(WIN) || PLATFORM(CHROMIUM) || defined(ANDROID) + if (m_creatingInitialEmptyDocument) + return; +#endif + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + // If loading a webarchive, run through webarchive machinery + const String& responseMIMEType = loader->responseMIMEType(); + + // FIXME: Mac's FrameLoaderClient::finishedLoading() method does work that is required even with Archive loads + // so we still need to call it. Other platforms should only call finishLoading for non-archive loads + // That work should be factored out so this #ifdef can be removed +#if PLATFORM(MAC) + m_client->finishedLoading(loader); + if (!ArchiveFactory::isArchiveMimeType(responseMIMEType)) + return; +#else + if (!ArchiveFactory::isArchiveMimeType(responseMIMEType)) { + m_client->finishedLoading(loader); + return; + } +#endif + + RefPtr<Archive> archive(ArchiveFactory::create(loader->mainResourceData().get(), responseMIMEType)); + if (!archive) + return; + + loader->addAllArchiveResources(archive.get()); + + ArchiveResource* mainResource = archive->mainResource(); + loader->setParsedArchiveData(mainResource->data()); + continueLoadWithData(mainResource->data(), mainResource->mimeType(), mainResource->textEncoding(), mainResource->url()); +#else + m_client->finishedLoading(loader); +#endif +} + +bool FrameLoader::isReplacing() const +{ + return m_loadType == FrameLoadTypeReplace; +} + +void FrameLoader::setReplacing() +{ + m_loadType = FrameLoadTypeReplace; +} + +void FrameLoader::revertToProvisional(DocumentLoader* loader) +{ + m_client->revertToProvisionalState(loader); +} + +bool FrameLoader::subframeIsLoading() const +{ + // It's most likely that the last added frame is the last to load so we walk backwards. + for (Frame* child = m_frame->tree()->lastChild(); child; child = child->tree()->previousSibling()) { + FrameLoader* childLoader = child->loader(); + DocumentLoader* documentLoader = childLoader->documentLoader(); + if (documentLoader && documentLoader->isLoadingInAPISense()) + return true; + documentLoader = childLoader->provisionalDocumentLoader(); + if (documentLoader && documentLoader->isLoadingInAPISense()) + return true; + } + return false; +} + +void FrameLoader::willChangeTitle(DocumentLoader* loader) +{ + m_client->willChangeTitle(loader); +} + +FrameLoadType FrameLoader::loadType() const +{ + return m_loadType; +} + +void FrameLoader::stopPolicyCheck() +{ + m_client->cancelPolicyCheck(); + PolicyCheck check = m_policyCheck; + m_policyCheck.clear(); + check.cancel(); +} + +void FrameLoader::checkLoadCompleteForThisFrame() +{ + ASSERT(m_client->hasWebView()); + + switch (m_state) { + case FrameStateProvisional: { + if (m_delegateIsHandlingProvisionalLoadError) + return; + + RefPtr<DocumentLoader> pdl = m_provisionalDocumentLoader; + if (!pdl) + return; + + // If we've received any errors we may be stuck in the provisional state and actually complete. + const ResourceError& error = pdl->mainDocumentError(); + if (error.isNull()) + return; + + // Check all children first. + RefPtr<HistoryItem> item; + if (Page* page = m_frame->page()) + if (isBackForwardLoadType(loadType()) && m_frame == page->mainFrame()) + item = m_currentHistoryItem; + + bool shouldReset = true; + if (!pdl->isLoadingInAPISense()) { + m_delegateIsHandlingProvisionalLoadError = true; + m_client->dispatchDidFailProvisionalLoad(error); + m_delegateIsHandlingProvisionalLoadError = false; + + // FIXME: can stopping loading here possibly have any effect, if isLoading is false, + // which it must be to be in this branch of the if? And is it OK to just do a full-on + // stopAllLoaders instead of stopLoadingSubframes? + stopLoadingSubframes(); + pdl->stopLoading(); + + // Finish resetting the load state, but only if another load hasn't been started by the + // delegate callback. + if (pdl == m_provisionalDocumentLoader) + clearProvisionalLoad(); + else if (m_provisionalDocumentLoader) { + KURL unreachableURL = m_provisionalDocumentLoader->unreachableURL(); + if (!unreachableURL.isEmpty() && unreachableURL == pdl->request().url()) + shouldReset = false; + } + } + if (shouldReset && item) + if (Page* page = m_frame->page()) + page->backForwardList()->goToItem(item.get()); + return; + } + + case FrameStateCommittedPage: { + DocumentLoader* dl = m_documentLoader.get(); + if (!dl || dl->isLoadingInAPISense()) + return; + + markLoadComplete(); + + // FIXME: Is this subsequent work important if we already navigated away? + // Maybe there are bugs because of that, or extra work we can skip because + // the new page is ready. + + m_client->forceLayoutForNonHTML(); + + // If the user had a scroll point, scroll to it, overriding the anchor point if any. + if (Page* page = m_frame->page()) + if ((isBackForwardLoadType(m_loadType) || m_loadType == FrameLoadTypeReload) && page->backForwardList()) + restoreScrollPositionAndViewState(); + + if (m_creatingInitialEmptyDocument || !m_committedFirstRealDocumentLoad) + return; + + const ResourceError& error = dl->mainDocumentError(); +#ifndef NDEBUG + m_didDispatchDidCommitLoad = false; +#endif + if (!error.isNull()) + m_client->dispatchDidFailLoad(error); + else + m_client->dispatchDidFinishLoad(); + + if (Page* page = m_frame->page()) + page->progress()->progressCompleted(m_frame); + +#ifdef ANDROID_INSTRUMENT + if (!m_frame->tree()->parent()) + android::TimeCounter::report(m_URL, cache()->getLiveSize(), cache()->getDeadSize()); +#endif + return; + } + + case FrameStateComplete: + // Even if already complete, we might have set a previous item on a frame that + // didn't do any data loading on the past transaction. Make sure to clear these out. + m_client->frameLoadCompleted(); + return; + } + + ASSERT_NOT_REACHED(); +} + +void FrameLoader::continueAfterContentPolicy(PolicyAction policy) +{ + PolicyCheck check = m_policyCheck; + m_policyCheck.clear(); + check.call(policy); +} + +void FrameLoader::continueLoadAfterWillSubmitForm(PolicyAction) +{ + if (!m_provisionalDocumentLoader) + return; + + // DocumentLoader calls back to our prepareForLoadStart + m_provisionalDocumentLoader->prepareForLoadStart(); + + // The load might be cancelled inside of prepareForLoadStart(), nulling out the m_provisionalDocumentLoader, + // so we need to null check it again. + if (!m_provisionalDocumentLoader) + return; + + DocumentLoader* activeDocLoader = activeDocumentLoader(); + if (activeDocLoader && activeDocLoader->isLoadingMainResource()) + return; + + m_provisionalDocumentLoader->setLoadingFromCachedPage(false); + + unsigned long identifier = 0; + + if (Page* page = m_frame->page()) { + identifier = page->progress()->createUniqueIdentifier(); + dispatchAssignIdentifierToInitialRequest(identifier, m_provisionalDocumentLoader.get(), m_provisionalDocumentLoader->originalRequest()); + } + + if (!m_provisionalDocumentLoader->startLoadingMainResource(identifier)) + m_provisionalDocumentLoader->updateLoading(); +} + +void FrameLoader::didFirstLayout() +{ + if (Page* page = m_frame->page()) +#ifdef ANDROID_HISTORY_CLIENT + // this should match the logic in FrameStateCommittedPage, so that we + // can restore the scroll position and view state as early as possible + if ((isBackForwardLoadType(m_loadType) || m_loadType == FrameLoadTypeReload) && page->backForwardList()) +#else + if (isBackForwardLoadType(m_loadType) && page->backForwardList()) +#endif + restoreScrollPositionAndViewState(); + + m_firstLayoutDone = true; + m_client->dispatchDidFirstLayout(); +} + +void FrameLoader::frameLoadCompleted() +{ + m_client->frameLoadCompleted(); + + // After a canceled provisional load, firstLayoutDone is false. + // Reset it to true if we're displaying a page. + if (m_documentLoader) + m_firstLayoutDone = true; +} + +bool FrameLoader::firstLayoutDone() const +{ + return m_firstLayoutDone; +} + +bool FrameLoader::isQuickRedirectComing() const +{ + return m_quickRedirectComing; +} + +void FrameLoader::detachChildren() +{ + // FIXME: Is it really necessary to do this in reverse order? + Frame* previous; + for (Frame* child = m_frame->tree()->lastChild(); child; child = previous) { + previous = child->tree()->previousSibling(); + child->loader()->detachFromParent(); + } +} + +void FrameLoader::recursiveCheckLoadComplete() +{ + Vector<RefPtr<Frame>, 10> frames; + + for (RefPtr<Frame> frame = m_frame->tree()->firstChild(); frame; frame = frame->tree()->nextSibling()) + frames.append(frame); + + unsigned size = frames.size(); + for (unsigned i = 0; i < size; i++) + frames[i]->loader()->recursiveCheckLoadComplete(); + + checkLoadCompleteForThisFrame(); +} + +// Called every time a resource is completely loaded, or an error is received. +void FrameLoader::checkLoadComplete() +{ + ASSERT(m_client->hasWebView()); + + // FIXME: Always traversing the entire frame tree is a bit inefficient, but + // is currently needed in order to null out the previous history item for all frames. + if (Page* page = m_frame->page()) + page->mainFrame()->loader()->recursiveCheckLoadComplete(); +} + +int FrameLoader::numPendingOrLoadingRequests(bool recurse) const +{ + if (!recurse) + return numRequests(m_frame->document()); + + int count = 0; + for (Frame* frame = m_frame; frame; frame = frame->tree()->traverseNext(m_frame)) + count += numRequests(frame->document()); + return count; +} + +FrameLoaderClient* FrameLoader::client() const +{ + return m_client; +} + +void FrameLoader::submitForm(const FrameLoadRequest& request, Event* event) +{ + // FIXME: We'd like to remove this altogether and fix the multiple form submission issue another way. + // We do not want to submit more than one form from the same page, + // nor do we want to submit a single form more than once. + // This flag prevents these from happening; not sure how other browsers prevent this. + // The flag is reset in each time we start handle a new mouse or key down event, and + // also in setView since this part may get reused for a page from the back/forward cache. + // The form multi-submit logic here is only needed when we are submitting a form that affects this frame. + // FIXME: Frame targeting is only one of the ways the submission could end up doing something other + // than replacing this frame's content, so this check is flawed. On the other hand, the check is hardly + // needed any more now that we reset m_submittedFormURL on each mouse or key down event. + Frame* target = m_frame->tree()->find(request.frameName()); + if (m_frame->tree()->isDescendantOf(target)) { + if (m_submittedFormURL == request.resourceRequest().url()) + return; + m_submittedFormURL = request.resourceRequest().url(); + } + + // FIXME: We should probably call userGestureHint() to tell whether this form submission was the result of a user gesture. + loadFrameRequestWithFormAndValues(request, false, event, m_formAboutToBeSubmitted.get(), m_formValuesAboutToBeSubmitted); + + clearRecordedFormValues(); +} + +String FrameLoader::userAgent(const KURL& url) const +{ + return m_client->userAgent(url); +} + +void FrameLoader::tokenizerProcessedData() +{ +// ASSERT(m_frame->page()); +// ASSERT(m_frame->document()); + + checkCompleted(); +} + +void FrameLoader::didTellClientAboutLoad(const String& url) +{ + m_urlsClientKnowsAbout.add(url); +} + +bool FrameLoader::haveToldClientAboutLoad(const String& url) +{ + return m_urlsClientKnowsAbout.contains(url); +} + +void FrameLoader::handledOnloadEvents() +{ + m_client->dispatchDidHandleOnloadEvents(); +} + +void FrameLoader::frameDetached() +{ + stopAllLoaders(); + detachFromParent(); +} + +void FrameLoader::detachFromParent() +{ + RefPtr<Frame> protect(m_frame); + + closeURL(); + stopAllLoaders(); + saveScrollPositionAndViewStateToItem(currentHistoryItem()); + detachChildren(); + + if (Page* page = m_frame->page()) + page->inspectorController()->frameDetachedFromParent(m_frame); + + m_client->detachedFromParent2(); + setDocumentLoader(0); + m_client->detachedFromParent3(); + if (Frame* parent = m_frame->tree()->parent()) { + parent->tree()->removeChild(m_frame); + parent->loader()->scheduleCheckCompleted(); + } else { + m_frame->setView(0); + m_frame->pageDestroyed(); + } +} + +void FrameLoader::addExtraFieldsToRequest(ResourceRequest& request, bool mainResource, bool alwaysFromRequest) +{ + applyUserAgent(request); + + if (m_loadType == FrameLoadTypeReload) { + request.setCachePolicy(ReloadIgnoringCacheData); + request.setHTTPHeaderField("Cache-Control", "max-age=0"); + } + + // Don't set the cookie policy URL if it's already been set. + if (request.mainDocumentURL().isEmpty()) { + if (mainResource && (isLoadingMainFrame() || alwaysFromRequest)) + request.setMainDocumentURL(request.url()); + else if (Page* page = m_frame->page()) + request.setMainDocumentURL(page->mainFrame()->loader()->url()); + } + + if (mainResource) + request.setHTTPAccept("application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"); + + // Make sure we send the Origin header. + addHTTPOriginIfNeeded(request, String()); +} + +void FrameLoader::addHTTPOriginIfNeeded(ResourceRequest& request, String origin) +{ + if (!request.httpOrigin().isEmpty()) + return; // Request already has an Origin header. + + // Don't send an Origin header for GET or HEAD to avoid privacy issues. + // For example, if an intranet page has a hyperlink to an external web + // site, we don't want to include the Origin of the request because it + // will leak the internal host name. Similar privacy concerns have lead + // to the widespread suppression of the Referer header at the network + // layer. + if (request.httpMethod() == "GET" || request.httpMethod() == "HEAD") + return; + + // For non-GET and non-HEAD methods, always send an Origin header so the + // server knows we support this feature. + + if (origin.isEmpty()) { + // If we don't know what origin header to attach, we attach the value + // for an empty origin. + origin = SecurityOrigin::createEmpty()->toString(); + } + + request.setHTTPOrigin(origin); +} + +void FrameLoader::committedLoad(DocumentLoader* loader, const char* data, int length) +{ +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + if (ArchiveFactory::isArchiveMimeType(loader->response().mimeType())) + return; +#endif + m_client->committedLoad(loader, data, length); +} + +#ifdef ANDROID_USER_GESTURE +void FrameLoader::loadPostRequest(const ResourceRequest& inRequest, const String& referrer, const String& frameName, + Event* event, PassRefPtr<FormState> prpFormState, bool userGesture) +#else +void FrameLoader::loadPostRequest(const ResourceRequest& inRequest, const String& referrer, const String& frameName, + Event* event, PassRefPtr<FormState> prpFormState) +#endif +{ + RefPtr<FormState> formState = prpFormState; + + // When posting, use the NSURLRequestReloadIgnoringCacheData load flag. + // This prevents a potential bug which may cause a page with a form that uses itself + // as an action to be returned from the cache without submitting. + + // FIXME: Where's the code that implements what the comment above says? + + // Previously when this method was reached, the original FrameLoadRequest had been deconstructed to build a + // bunch of parameters that would come in here and then be built back up to a ResourceRequest. In case + // any caller depends on the immutability of the original ResourceRequest, I'm rebuilding a ResourceRequest + // from scratch as it did all along. + const KURL& url = inRequest.url(); + RefPtr<FormData> formData = inRequest.httpBody(); + const String& contentType = inRequest.httpContentType(); + String origin = inRequest.httpOrigin(); + + ResourceRequest workingResourceRequest(url); +#ifdef ANDROID_USER_GESTURE + workingResourceRequest.setUserGesture(userGesture); +#endif + + if (!referrer.isEmpty()) + workingResourceRequest.setHTTPReferrer(referrer); + workingResourceRequest.setHTTPOrigin(origin); + workingResourceRequest.setHTTPMethod("POST"); + workingResourceRequest.setHTTPBody(formData); + workingResourceRequest.setHTTPContentType(contentType); + addExtraFieldsToRequest(workingResourceRequest, true, true); + + NavigationAction action(url, FrameLoadTypeStandard, true, event); + + if (!frameName.isEmpty()) { + if (Frame* targetFrame = findFrameForNavigation(frameName)) + targetFrame->loader()->loadWithNavigationAction(workingResourceRequest, action, FrameLoadTypeStandard, formState.release()); + else + checkNewWindowPolicy(action, workingResourceRequest, formState.release(), frameName); + } else + loadWithNavigationAction(workingResourceRequest, action, FrameLoadTypeStandard, formState.release()); +} + +bool FrameLoader::isReloading() const +{ + return documentLoader()->request().cachePolicy() == ReloadIgnoringCacheData; +} + +void FrameLoader::loadEmptyDocumentSynchronously() +{ + ResourceRequest request(KURL("")); + load(request); +} + +unsigned long FrameLoader::loadResourceSynchronously(const ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& data) +{ + // Since this is a subresource, we can load any URL (we ignore the return value). + // But we still want to know whether we should hide the referrer or not, so we call the canLoad method. + String referrer = m_outgoingReferrer; + if (shouldHideReferrer(request.url(), referrer)) + referrer = String(); + + ResourceRequest initialRequest = request; + initialRequest.setTimeoutInterval(10); + + if (initialRequest.isConditional()) + initialRequest.setCachePolicy(ReloadIgnoringCacheData); + else + initialRequest.setCachePolicy(documentLoader()->request().cachePolicy()); + + if (!referrer.isEmpty()) + initialRequest.setHTTPReferrer(referrer); + addHTTPOriginIfNeeded(initialRequest, outgoingOrigin()); + + if (Page* page = m_frame->page()) + initialRequest.setMainDocumentURL(page->mainFrame()->loader()->documentLoader()->request().url()); + initialRequest.setHTTPUserAgent(client()->userAgent(request.url())); + + unsigned long identifier = 0; + ResourceRequest newRequest(initialRequest); + requestFromDelegate(newRequest, identifier, error); + + if (error.isNull()) { + ASSERT(!newRequest.isNull()); + didTellClientAboutLoad(newRequest.url().string()); + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + ApplicationCacheResource* resource; + if (documentLoader()->shouldLoadResourceFromApplicationCache(newRequest, resource)) { + if (resource) { + response = resource->response(); + data.append(resource->data()->data(), resource->data()->size()); + } else + error = cannotShowURLError(newRequest); + } else +#endif + ResourceHandle::loadResourceSynchronously(newRequest, error, response, data, m_frame); + } + + sendRemainingDelegateMessages(identifier, response, data.size(), error); + return identifier; +} + +void FrameLoader::assignIdentifierToInitialRequest(unsigned long identifier, const ResourceRequest& clientRequest) +{ + return dispatchAssignIdentifierToInitialRequest(identifier, activeDocumentLoader(), clientRequest); +} + +void FrameLoader::willSendRequest(ResourceLoader* loader, ResourceRequest& clientRequest, const ResourceResponse& redirectResponse) +{ + applyUserAgent(clientRequest); + dispatchWillSendRequest(loader->documentLoader(), loader->identifier(), clientRequest, redirectResponse); +} + +void FrameLoader::didReceiveResponse(ResourceLoader* loader, const ResourceResponse& r) +{ + activeDocumentLoader()->addResponse(r); + + if (Page* page = m_frame->page()) + page->progress()->incrementProgress(loader->identifier(), r); + dispatchDidReceiveResponse(loader->documentLoader(), loader->identifier(), r); +} + +void FrameLoader::didReceiveData(ResourceLoader* loader, const char* data, int length, int lengthReceived) +{ + if (Page* page = m_frame->page()) + page->progress()->incrementProgress(loader->identifier(), data, length); + dispatchDidReceiveContentLength(loader->documentLoader(), loader->identifier(), lengthReceived); +} + +void FrameLoader::didFailToLoad(ResourceLoader* loader, const ResourceError& error) +{ + if (Page* page = m_frame->page()) + page->progress()->completeProgress(loader->identifier()); + if (!error.isNull()) + m_client->dispatchDidFailLoading(loader->documentLoader(), loader->identifier(), error); +} + +const ResourceRequest& FrameLoader::originalRequest() const +{ + return activeDocumentLoader()->originalRequestCopy(); +} + +void FrameLoader::receivedMainResourceError(const ResourceError& error, bool isComplete) +{ + // Retain because the stop may release the last reference to it. + RefPtr<Frame> protect(m_frame); + + RefPtr<DocumentLoader> loader = activeDocumentLoader(); + + if (isComplete) { + // FIXME: Don't want to do this if an entirely new load is going, so should check + // that both data sources on the frame are either this or nil. + stop(); + if (m_client->shouldFallBack(error)) + handleFallbackContent(); + } + + if (m_state == FrameStateProvisional && m_provisionalDocumentLoader) { + KURL failedURL = m_provisionalDocumentLoader->originalRequestCopy().url(); + didNotOpenURL(failedURL); + + // We might have made a page cache item, but now we're bailing out due to an error before we ever + // transitioned to the new page (before WebFrameState == commit). The goal here is to restore any state + // so that the existing view (that wenever got far enough to replace) can continue being used. + invalidateCurrentItemCachedPage(); + + // Call clientRedirectCancelledOrFinished here so that the frame load delegate is notified that the redirect's + // status has changed, if there was a redirect. The frame load delegate may have saved some state about + // the redirect in its -webView:willPerformClientRedirectToURL:delay:fireDate:forFrame:. Since we are definitely + // not going to use this provisional resource, as it was cancelled, notify the frame load delegate that the redirect + // has ended. + if (m_sentRedirectNotification) + clientRedirectCancelledOrFinished(false); + } + + + loader->mainReceivedError(error, isComplete); +} + +void FrameLoader::callContinueFragmentScrollAfterNavigationPolicy(void* argument, + const ResourceRequest& request, PassRefPtr<FormState>, bool shouldContinue) +{ + FrameLoader* loader = static_cast<FrameLoader*>(argument); + loader->continueFragmentScrollAfterNavigationPolicy(request, shouldContinue); +} + +void FrameLoader::continueFragmentScrollAfterNavigationPolicy(const ResourceRequest& request, bool shouldContinue) +{ + // FIXME: + // some functions check m_quickRedirectComing, and others check for + // FrameLoadTypeRedirectWithLockedHistory. + bool isRedirect = m_quickRedirectComing || m_policyLoadType == FrameLoadTypeRedirectWithLockedHistory; + m_quickRedirectComing = false; + + if (!shouldContinue) + return; + + KURL url = request.url(); + + m_documentLoader->replaceRequestURLForAnchorScroll(url); + if (!isRedirect && !shouldTreatURLAsSameAsCurrent(url)) { + // NB: must happen after _setURL, since we add based on the current request. + // Must also happen before we openURL and displace the scroll position, since + // adding the BF item will save away scroll state. + + // NB2: If we were loading a long, slow doc, and the user anchor nav'ed before + // it was done, currItem is now set the that slow doc, and prevItem is whatever was + // before it. Adding the b/f item will bump the slow doc down to prevItem, even + // though its load is not yet done. I think this all works out OK, for one because + // we have already saved away the scroll and doc state for the long slow load, + // but it's not an obvious case. + + addHistoryItemForFragmentScroll(); + } + + scrollToAnchor(url); + + if (!isRedirect) + // This will clear previousItem from the rest of the frame tree that didn't + // doing any loading. We need to make a pass on this now, since for anchor nav + // we'll not go through a real load and reach Completed state. + checkLoadComplete(); + + m_client->dispatchDidChangeLocationWithinPage(); + m_client->didFinishLoad(); +} + +bool FrameLoader::shouldScrollToAnchor(bool isFormSubmission, FrameLoadType loadType, const KURL& url) +{ + // Should we do anchor navigation within the existing content? + + // We don't do this if we are submitting a form, explicitly reloading, + // currently displaying a frameset, or if the URL does not have a fragment. + // These rules were originally based on what KHTML was doing in KHTMLPart::openURL. + + // FIXME: What about load types other than Standard and Reload? + + return !isFormSubmission + && loadType != FrameLoadTypeReload + && loadType != FrameLoadTypeSame + && !shouldReload(this->url(), url) + // We don't want to just scroll if a link from within a + // frameset is trying to reload the frameset into _top. + && !m_frame->isFrameSet(); +} + +void FrameLoader::opened() +{ + if (m_loadType == FrameLoadTypeStandard && m_documentLoader->isClientRedirect()) + updateHistoryForClientRedirect(); + + if (m_documentLoader->isLoadingFromCachedPage()) { + m_frame->document()->documentDidBecomeActive(); + + // Force a layout to update view size and thereby update scrollbars. + m_client->forceLayout(); + + const ResponseVector& responses = m_documentLoader->responses(); + size_t count = responses.size(); + for (size_t i = 0; i < count; i++) { + const ResourceResponse& response = responses[i]; + // FIXME: If the WebKit client changes or cancels the request, this is not respected. + ResourceError error; + unsigned long identifier; + ResourceRequest request(response.url()); + requestFromDelegate(request, identifier, error); + // FIXME: If we get a resource with more than 2B bytes, this code won't do the right thing. + // However, with today's computers and networking speeds, this won't happen in practice. + // Could be an issue with a giant local file. + sendRemainingDelegateMessages(identifier, response, static_cast<int>(response.expectedContentLength()), error); + } + + pageCache()->remove(m_currentHistoryItem.get()); + + m_documentLoader->setPrimaryLoadComplete(true); + + // FIXME: Why only this frame and not parent frames? + checkLoadCompleteForThisFrame(); + } +} + +void FrameLoader::checkNewWindowPolicy(const NavigationAction& action, const ResourceRequest& request, + PassRefPtr<FormState> formState, const String& frameName) +{ + m_policyCheck.set(request, formState, frameName, + callContinueLoadAfterNewWindowPolicy, this); + m_client->dispatchDecidePolicyForNewWindowAction(&FrameLoader::continueAfterNewWindowPolicy, + action, request, formState, frameName); +} + +void FrameLoader::continueAfterNewWindowPolicy(PolicyAction policy) +{ + PolicyCheck check = m_policyCheck; + m_policyCheck.clear(); + + switch (policy) { + case PolicyIgnore: + check.clearRequest(); + break; + case PolicyDownload: + m_client->startDownload(check.request()); + check.clearRequest(); + break; + case PolicyUse: + break; + } + + check.call(policy == PolicyUse); +} + +void FrameLoader::checkNavigationPolicy(const ResourceRequest& request, DocumentLoader* loader, + PassRefPtr<FormState> formState, NavigationPolicyDecisionFunction function, void* argument) +{ + NavigationAction action = loader->triggeringAction(); + if (action.isEmpty()) { + action = NavigationAction(request.url(), NavigationTypeOther); + loader->setTriggeringAction(action); + } + + // Don't ask more than once for the same request or if we are loading an empty URL. + // This avoids confusion on the part of the client. + if (equalIgnoringHeaderFields(request, loader->lastCheckedRequest()) || (!request.isNull() && request.url().isEmpty())) { + function(argument, request, 0, true); + loader->setLastCheckedRequest(request); + return; + } + + // We are always willing to show alternate content for unreachable URLs; + // treat it like a reload so it maintains the right state for b/f list. + if (loader->substituteData().isValid() && !loader->substituteData().failingURL().isEmpty()) { + if (isBackForwardLoadType(m_policyLoadType)) + m_policyLoadType = FrameLoadTypeReload; + function(argument, request, 0, true); + return; + } + + loader->setLastCheckedRequest(request); + + m_policyCheck.set(request, formState.get(), function, argument); + + m_delegateIsDecidingNavigationPolicy = true; + m_client->dispatchDecidePolicyForNavigationAction(&FrameLoader::continueAfterNavigationPolicy, + action, request, formState); + m_delegateIsDecidingNavigationPolicy = false; +} + +void FrameLoader::continueAfterNavigationPolicy(PolicyAction policy) +{ + PolicyCheck check = m_policyCheck; + m_policyCheck.clear(); + + bool shouldContinue = policy == PolicyUse; + + switch (policy) { + case PolicyIgnore: + check.clearRequest(); + break; + case PolicyDownload: + m_client->startDownload(check.request()); + check.clearRequest(); + break; + case PolicyUse: { + ResourceRequest request(check.request()); + + if (!m_client->canHandleRequest(request)) { + handleUnimplementablePolicy(m_client->cannotShowURLError(check.request())); + check.clearRequest(); + shouldContinue = false; + } + break; + } + } + + check.call(shouldContinue); +} + +void FrameLoader::callContinueLoadAfterNavigationPolicy(void* argument, + const ResourceRequest& request, PassRefPtr<FormState> formState, bool shouldContinue) +{ + FrameLoader* loader = static_cast<FrameLoader*>(argument); + loader->continueLoadAfterNavigationPolicy(request, formState, shouldContinue); +} + +void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest& request, PassRefPtr<FormState> formState, bool shouldContinue) +{ + // If we loaded an alternate page to replace an unreachableURL, we'll get in here with a + // nil policyDataSource because loading the alternate page will have passed + // through this method already, nested; otherwise, policyDataSource should still be set. + ASSERT(m_policyDocumentLoader || !m_provisionalDocumentLoader->unreachableURL().isEmpty()); + + bool isTargetItem = m_provisionalHistoryItem ? m_provisionalHistoryItem->isTargetItem() : false; + + // Two reasons we can't continue: + // 1) Navigation policy delegate said we can't so request is nil. A primary case of this + // is the user responding Cancel to the form repost nag sheet. + // 2) User responded Cancel to an alert popped up by the before unload event handler. + // The "before unload" event handler runs only for the main frame. + bool canContinue = shouldContinue && (!isLoadingMainFrame() || m_frame->shouldClose()); + + if (!canContinue) { + // If we were waiting for a quick redirect, but the policy delegate decided to ignore it, then we + // need to report that the client redirect was cancelled. + if (m_quickRedirectComing) + clientRedirectCancelledOrFinished(false); + + setPolicyDocumentLoader(0); + + // If the navigation request came from the back/forward menu, and we punt on it, we have the + // problem that we have optimistically moved the b/f cursor already, so move it back. For sanity, + // we only do this when punting a navigation for the target frame or top-level frame. + if ((isTargetItem || isLoadingMainFrame()) && isBackForwardLoadType(m_policyLoadType)) + if (Page* page = m_frame->page()) { + Frame* mainFrame = page->mainFrame(); + if (HistoryItem* resetItem = mainFrame->loader()->m_currentHistoryItem.get()) + page->backForwardList()->goToItem(resetItem); + } + return; + } + + FrameLoadType type = m_policyLoadType; + stopAllLoaders(); + + // <rdar://problem/6250856> - In certain circumstances on pages with multiple frames, stopAllLoaders() + // might detach the current FrameLoader, in which case we should bail on this newly defunct load. + if (!m_frame->page()) + return; + + setProvisionalDocumentLoader(m_policyDocumentLoader.get()); + m_loadType = type; + setState(FrameStateProvisional); + + setPolicyDocumentLoader(0); + + if (isBackForwardLoadType(type) && loadProvisionalItemFromCachedPage()) + return; + + if (formState) + m_client->dispatchWillSubmitForm(&FrameLoader::continueLoadAfterWillSubmitForm, formState); + else + continueLoadAfterWillSubmitForm(); +} + + +void FrameLoader::callContinueLoadAfterNewWindowPolicy(void* argument, + const ResourceRequest& request, PassRefPtr<FormState> formState, const String& frameName, bool shouldContinue) +{ + FrameLoader* loader = static_cast<FrameLoader*>(argument); + loader->continueLoadAfterNewWindowPolicy(request, formState, frameName, shouldContinue); +} + +void FrameLoader::continueLoadAfterNewWindowPolicy(const ResourceRequest& request, + PassRefPtr<FormState> formState, const String& frameName, bool shouldContinue) +{ + if (!shouldContinue) + return; + + RefPtr<Frame> frame = m_frame; + RefPtr<Frame> mainFrame = m_client->dispatchCreatePage(); + if (!mainFrame) + return; + + if (frameName != "_blank") + mainFrame->tree()->setName(frameName); + + mainFrame->loader()->setOpenedByDOM(); + mainFrame->loader()->m_client->dispatchShow(); + mainFrame->loader()->setOpener(frame.get()); + mainFrame->loader()->loadWithNavigationAction(request, NavigationAction(), FrameLoadTypeStandard, formState); +} + +void FrameLoader::sendRemainingDelegateMessages(unsigned long identifier, const ResourceResponse& response, int length, const ResourceError& error) +{ + if (!response.isNull()) + dispatchDidReceiveResponse(m_documentLoader.get(), identifier, response); + + if (length > 0) + dispatchDidReceiveContentLength(m_documentLoader.get(), identifier, length); + + if (error.isNull()) + dispatchDidFinishLoading(m_documentLoader.get(), identifier); + else + m_client->dispatchDidFailLoading(m_documentLoader.get(), identifier, error); +} + +void FrameLoader::requestFromDelegate(ResourceRequest& request, unsigned long& identifier, ResourceError& error) +{ + ASSERT(!request.isNull()); + + identifier = 0; + if (Page* page = m_frame->page()) { + identifier = page->progress()->createUniqueIdentifier(); + dispatchAssignIdentifierToInitialRequest(identifier, m_documentLoader.get(), request); + } + + ResourceRequest newRequest(request); + dispatchWillSendRequest(m_documentLoader.get(), identifier, newRequest, ResourceResponse()); + + if (newRequest.isNull()) + error = cancelledError(request); + else + error = ResourceError(); + + request = newRequest; +} + +void FrameLoader::loadedResourceFromMemoryCache(const CachedResource* resource) +{ + ResourceRequest request(resource->url()); + const ResourceResponse& response = resource->response(); + SharedBuffer* data = resource->data(); + int length = data ? data->size() : 0; + + if (Page* page = m_frame->page()) + page->inspectorController()->didLoadResourceFromMemoryCache(m_documentLoader.get(), request, response, length); + + if (!resource->sendResourceLoadCallbacks() || haveToldClientAboutLoad(resource->url())) + return; + + if (m_client->dispatchDidLoadResourceFromMemoryCache(m_documentLoader.get(), request, response, length)) { + didTellClientAboutLoad(resource->url()); + return; + } + + unsigned long identifier; + ResourceError error; + ResourceRequest r(request); + requestFromDelegate(r, identifier, error); + sendRemainingDelegateMessages(identifier, response, length, error); + + didTellClientAboutLoad(resource->url()); +} + +void FrameLoader::applyUserAgent(ResourceRequest& request) +{ + String userAgent = client()->userAgent(request.url()); + ASSERT(!userAgent.isNull()); + request.setHTTPUserAgent(userAgent); +} + +bool FrameLoader::canGoBackOrForward(int distance) const +{ + if (Page* page = m_frame->page()) { + if (distance == 0) + return true; + if (distance > 0 && distance <= page->backForwardList()->forwardListCount()) + return true; + if (distance < 0 && -distance <= page->backForwardList()->backListCount()) + return true; + } + return false; +} + +int FrameLoader::getHistoryLength() +{ + if (Page* page = m_frame->page()) + return page->backForwardList()->backListCount() + 1; + return 0; +} + +KURL FrameLoader::historyURL(int distance) +{ + if (Page* page = m_frame->page()) { + BackForwardList* list = page->backForwardList(); + HistoryItem* item = list->itemAtIndex(distance); + if (!item) { + if (distance > 0) { + int forwardListCount = list->forwardListCount(); + if (forwardListCount > 0) + item = list->itemAtIndex(forwardListCount); + } else { + int backListCount = list->backListCount(); + if (backListCount > 0) + item = list->itemAtIndex(-backListCount); + } + } + if (item) + return item->url(); + } + return KURL(); +} + +void FrameLoader::addHistoryItemForFragmentScroll() +{ + addBackForwardItemClippedAtTarget(false); +} + +bool FrameLoader::loadProvisionalItemFromCachedPage() +{ + RefPtr<CachedPage> cachedPage = pageCache()->get(m_provisionalHistoryItem.get()); + if (!cachedPage || !cachedPage->document()) + return false; + provisionalDocumentLoader()->loadFromCachedPage(cachedPage.release()); + return true; +} + +void FrameLoader::cachePageForHistoryItem(HistoryItem* item) +{ + if (Page* page = m_frame->page()) { + RefPtr<CachedPage> cachedPage = CachedPage::create(page); + cachedPage->setTimeStampToNow(); + cachedPage->setDocumentLoader(documentLoader()); + m_client->savePlatformDataToCachedPage(cachedPage.get()); + + pageCache()->add(item, cachedPage.release()); + } +} + +bool FrameLoader::shouldTreatURLAsSameAsCurrent(const KURL& url) const +{ + if (!m_currentHistoryItem) + return false; + return url == m_currentHistoryItem->url() || url == m_currentHistoryItem->originalURL(); +} + +PassRefPtr<HistoryItem> FrameLoader::createHistoryItem(bool useOriginal) +{ + DocumentLoader* docLoader = documentLoader(); + + KURL unreachableURL = docLoader ? docLoader->unreachableURL() : KURL(); + + KURL url; + KURL originalURL; + + if (!unreachableURL.isEmpty()) { + url = unreachableURL; + originalURL = unreachableURL; + } else { + originalURL = docLoader ? docLoader->originalURL() : KURL(); + if (useOriginal) + url = originalURL; + else if (docLoader) + url = docLoader->requestURL(); + } + + LOG(History, "WebCoreHistory: Creating item for %s", url.string().ascii().data()); + + // Frames that have never successfully loaded any content + // may have no URL at all. Currently our history code can't + // deal with such things, so we nip that in the bud here. + // Later we may want to learn to live with nil for URL. + // See bug 3368236 and related bugs for more information. + if (url.isEmpty()) + url = blankURL(); + if (originalURL.isEmpty()) + originalURL = blankURL(); + + Frame* parentFrame = m_frame->tree()->parent(); + String parent = parentFrame ? parentFrame->tree()->name() : ""; + String title = docLoader ? docLoader->title() : ""; + + RefPtr<HistoryItem> item = HistoryItem::create(url, m_frame->tree()->name(), parent, title); + item->setOriginalURLString(originalURL.string()); + + // Save form state if this is a POST + if (docLoader) { + if (useOriginal) + item->setFormInfoFromRequest(docLoader->originalRequest()); + else + item->setFormInfoFromRequest(docLoader->request()); + } + + // Set the item for which we will save document state + m_previousHistoryItem = m_currentHistoryItem; + m_currentHistoryItem = item; + + return item.release(); +} + +void FrameLoader::addBackForwardItemClippedAtTarget(bool doClip) +{ + Page* page = m_frame->page(); + if (!page) + return; + + if (documentLoader()->urlForHistory().isEmpty()) + return; + + Frame* mainFrame = page->mainFrame(); + ASSERT(mainFrame); + FrameLoader* frameLoader = mainFrame->loader(); + + if (!frameLoader->m_didPerformFirstNavigation && page->backForwardList()->entries().size() == 1) { + frameLoader->m_didPerformFirstNavigation = true; + m_client->didPerformFirstNavigation(); + } + + RefPtr<HistoryItem> item = frameLoader->createHistoryItemTree(m_frame, doClip); + LOG(BackForward, "WebCoreBackForward - Adding backforward item %p for frame %s", item.get(), documentLoader()->url().string().ascii().data()); + page->backForwardList()->addItem(item); +} + +PassRefPtr<HistoryItem> FrameLoader::createHistoryItemTree(Frame* targetFrame, bool clipAtTarget) +{ + RefPtr<HistoryItem> bfItem = createHistoryItem(m_frame->tree()->parent() ? true : false); + if (m_previousHistoryItem) + saveScrollPositionAndViewStateToItem(m_previousHistoryItem.get()); + if (!(clipAtTarget && m_frame == targetFrame)) { + // save frame state for items that aren't loading (khtml doesn't save those) + saveDocumentState(); + for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) { + FrameLoader* childLoader = child->loader(); + bool hasChildLoaded = childLoader->frameHasLoaded(); + + // If the child is a frame corresponding to an <object> element that never loaded, + // we don't want to create a history item, because that causes fallback content + // to be ignored on reload. + + if (!(!hasChildLoaded && childLoader->isHostedByObjectElement())) + bfItem->addChildItem(childLoader->createHistoryItemTree(targetFrame, clipAtTarget)); + } + } + if (m_frame == targetFrame) + bfItem->setIsTargetItem(true); + return bfItem; +} + +Frame* FrameLoader::findFrameForNavigation(const AtomicString& name) +{ + Frame* frame = m_frame->tree()->find(name); + if (shouldAllowNavigation(frame)) + return frame; + return 0; +} + +void FrameLoader::saveScrollPositionAndViewStateToItem(HistoryItem* item) +{ + if (!item || !m_frame->view()) + return; + + item->setScrollPoint(m_frame->view()->scrollPosition()); + // FIXME: It would be great to work out a way to put this code in WebCore instead of calling through to the client. + m_client->saveViewStateToItem(item); +} + +/* + There is a race condition between the layout and load completion that affects restoring the scroll position. + We try to restore the scroll position at both the first layout and upon load completion. + + 1) If first layout happens before the load completes, we want to restore the scroll position then so that the + first time we draw the page is already scrolled to the right place, instead of starting at the top and later + jumping down. It is possible that the old scroll position is past the part of the doc laid out so far, in + which case the restore silent fails and we will fix it in when we try to restore on doc completion. + 2) If the layout happens after the load completes, the attempt to restore at load completion time silently + fails. We then successfully restore it when the layout happens. +*/ +void FrameLoader::restoreScrollPositionAndViewState() +{ + if (!m_committedFirstRealDocumentLoad) + return; + + ASSERT(m_currentHistoryItem); + + // FIXME: As the ASSERT attests, it seems we should always have a currentItem here. + // One counterexample is <rdar://problem/4917290> + // For now, to cover this issue in release builds, there is no technical harm to returning + // early and from a user standpoint - as in the above radar - the previous page load failed + // so there *is* no scroll or view state to restore! + if (!m_currentHistoryItem) + return; + + // FIXME: It would be great to work out a way to put this code in WebCore instead of calling + // through to the client. It's currently used only for the PDF view on Mac. + m_client->restoreViewState(); + + if (FrameView* view = m_frame->view()) + if (!view->wasScrolledByUser()) + view->setScrollPosition(m_currentHistoryItem->scrollPoint()); +} + +void FrameLoader::invalidateCurrentItemCachedPage() +{ + // When we are pre-commit, the currentItem is where the pageCache data resides + CachedPage* cachedPage = pageCache()->get(m_currentHistoryItem.get()); + + // FIXME: This is a grotesque hack to fix <rdar://problem/4059059> Crash in RenderFlow::detach + // Somehow the PageState object is not properly updated, and is holding onto a stale document. + // Both Xcode and FileMaker see this crash, Safari does not. + + ASSERT(!cachedPage || cachedPage->document() == m_frame->document()); + if (cachedPage && cachedPage->document() == m_frame->document()) { + cachedPage->document()->setInPageCache(false); + cachedPage->clear(); + } + + if (cachedPage) + pageCache()->remove(m_currentHistoryItem.get()); +} + +void FrameLoader::saveDocumentState() +{ + if (m_creatingInitialEmptyDocument) + return; + + // For a standard page load, we will have a previous item set, which will be used to + // store the form state. However, in some cases we will have no previous item, and + // the current item is the right place to save the state. One example is when we + // detach a bunch of frames because we are navigating from a site with frames to + // another site. Another is when saving the frame state of a frame that is not the + // target of the current navigation (if we even decide to save with that granularity). + + // Because of previousItem's "masking" of currentItem for this purpose, it's important + // that previousItem be cleared at the end of a page transition. We leverage the + // checkLoadComplete recursion to achieve this goal. + + HistoryItem* item = m_previousHistoryItem ? m_previousHistoryItem.get() : m_currentHistoryItem.get(); + if (!item) + return; + + Document* document = m_frame->document(); + ASSERT(document); + + if (document && item->isCurrentDocument(document)) { + LOG(Loading, "WebCoreLoading %s: saving form state to %p", m_frame->tree()->name().string().utf8().data(), item); + item->setDocumentState(document->formElementsState()); + } +} + +// Loads content into this frame, as specified by history item +void FrameLoader::loadItem(HistoryItem* item, FrameLoadType loadType) +{ + if (!m_frame->page()) + return; + + KURL itemURL = item->url(); + KURL itemOriginalURL = item->originalURL(); + KURL currentURL; + if (documentLoader()) + currentURL = documentLoader()->url(); + RefPtr<FormData> formData = item->formData(); + + // Are we navigating to an anchor within the page? + // Note if we have child frames we do a real reload, since the child frames might not + // match our current frame structure, or they might not have the right content. We could + // check for all that as an additional optimization. + // We also do not do anchor-style navigation if we're posting a form. + + if (!formData && urlsMatchItem(item)) { + // Must do this maintenance here, since we don't go through a real page reload + saveScrollPositionAndViewStateToItem(m_currentHistoryItem.get()); + + if (FrameView* view = m_frame->view()) + view->setWasScrolledByUser(false); + + m_currentHistoryItem = item; + + // FIXME: Form state might need to be saved here too. + + // We always call scrollToAnchor here, even if the URL doesn't have an + // anchor fragment. This is so we'll keep the WebCore Frame's URL up-to-date. + scrollToAnchor(item->url()); + + // must do this maintenance here, since we don't go through a real page reload + restoreScrollPositionAndViewState(); + + // Fake the URL change by updating the data source's request. This will no longer + // be necessary if we do the better fix described above. + documentLoader()->replaceRequestURLForAnchorScroll(itemURL); + + m_client->dispatchDidChangeLocationWithinPage(); + + // FrameLoaderClient::didFinishLoad() tells the internal load delegate the load finished with no error + m_client->didFinishLoad(); + } else { + // Remember this item so we can traverse any child items as child frames load + m_provisionalHistoryItem = item; + + bool inPageCache = false; + + // Check if we'll be using the page cache. We only use the page cache + // if one exists and it is less than _backForwardCacheExpirationInterval + // seconds old. If the cache is expired it gets flushed here. + if (RefPtr<CachedPage> cachedPage = pageCache()->get(item)) { + double interval = currentTime() - cachedPage->timeStamp(); + + // FIXME: 1800 should not be hardcoded, it should come from + // WebKitBackForwardCacheExpirationIntervalKey in WebKit. + // Or we should remove WebKitBackForwardCacheExpirationIntervalKey. + if (interval <= 1800) { + loadWithDocumentLoader(cachedPage->documentLoader(), loadType, 0); + inPageCache = true; + } else { + LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", m_provisionalHistoryItem->url().string().ascii().data()); + pageCache()->remove(item); + } + } + + if (!inPageCache) { + ResourceRequest request(itemURL); + + // If this was a repost that failed the page cache, we might try to repost the form. + NavigationAction action; + if (formData) { + + formData->generateFiles(m_frame->page()->chrome()->client()); + + request.setHTTPMethod("POST"); + request.setHTTPReferrer(item->formReferrer()); + request.setHTTPBody(formData); + request.setHTTPContentType(item->formContentType()); + RefPtr<SecurityOrigin> securityOrigin = SecurityOrigin::createFromString(item->formReferrer()); + addHTTPOriginIfNeeded(request, securityOrigin->toString()); + + // FIXME: Slight hack to test if the NSURL cache contains the page we're going to. + // We want to know this before talking to the policy delegate, since it affects whether + // we show the DoYouReallyWantToRepost nag. + // + // This trick has a small bug (3123893) where we might find a cache hit, but then + // have the item vanish when we try to use it in the ensuing nav. This should be + // extremely rare, but in that case the user will get an error on the navigation. + + if (ResourceHandle::willLoadFromCache(request)) + action = NavigationAction(itemURL, loadType, false); + else { + request.setCachePolicy(ReloadIgnoringCacheData); + action = NavigationAction(itemURL, NavigationTypeFormResubmitted); + } + } else { + switch (loadType) { + case FrameLoadTypeReload: + request.setCachePolicy(ReloadIgnoringCacheData); + break; + case FrameLoadTypeBack: + case FrameLoadTypeForward: + case FrameLoadTypeIndexedBackForward: + if (itemURL.protocol() != "https") + request.setCachePolicy(ReturnCacheDataElseLoad); + break; + case FrameLoadTypeStandard: + case FrameLoadTypeRedirectWithLockedHistory: + // no-op: leave as protocol default + // FIXME: I wonder if we ever hit this case + break; + case FrameLoadTypeSame: + case FrameLoadTypeReloadAllowingStaleData: + default: + ASSERT_NOT_REACHED(); + } + + action = NavigationAction(itemOriginalURL, loadType, false); + } + + addExtraFieldsToRequest(request, true, formData); + loadWithNavigationAction(request, action, loadType, 0); + } + } +} + +// Walk the frame tree and ensure that the URLs match the URLs in the item. +bool FrameLoader::urlsMatchItem(HistoryItem* item) const +{ + const KURL& currentURL = documentLoader()->url(); + if (!equalIgnoringRef(currentURL, item->url())) + return false; + + const HistoryItemVector& childItems = item->children(); + + unsigned size = childItems.size(); + for (unsigned i = 0; i < size; ++i) { + Frame* childFrame = m_frame->tree()->child(childItems[i]->target()); + if (childFrame && !childFrame->loader()->urlsMatchItem(childItems[i].get())) + return false; + } + + return true; +} + +// Main funnel for navigating to a previous location (back/forward, non-search snap-back) +// This includes recursion to handle loading into framesets properly +void FrameLoader::goToItem(HistoryItem* targetItem, FrameLoadType type) +{ + ASSERT(!m_frame->tree()->parent()); + + // shouldGoToHistoryItem is a private delegate method. This is needed to fix: + // <rdar://problem/3951283> can view pages from the back/forward cache that should be disallowed by Parental Controls + // Ultimately, history item navigations should go through the policy delegate. That's covered in: + // <rdar://problem/3979539> back/forward cache navigations should consult policy delegate + Page* page = m_frame->page(); + if (!page) + return; + if (!m_client->shouldGoToHistoryItem(targetItem)) + return; + + // Set the BF cursor before commit, which lets the user quickly click back/forward again. + // - plus, it only makes sense for the top level of the operation through the frametree, + // as opposed to happening for some/one of the page commits that might happen soon + BackForwardList* bfList = page->backForwardList(); + HistoryItem* currentItem = bfList->currentItem(); + bfList->goToItem(targetItem); + recursiveGoToItem(targetItem, currentItem, type); +} + +// The general idea here is to traverse the frame tree and the item tree in parallel, +// tracking whether each frame already has the content the item requests. If there is +// a match (by URL), we just restore scroll position and recurse. Otherwise we must +// reload that frame, and all its kids. +void FrameLoader::recursiveGoToItem(HistoryItem* item, HistoryItem* fromItem, FrameLoadType type) +{ + ASSERT(item); + ASSERT(fromItem); + + KURL itemURL = item->url(); + KURL currentURL; + if (documentLoader()) + currentURL = documentLoader()->url(); + + // Always reload the target frame of the item we're going to. This ensures that we will + // do -some- load for the transition, which means a proper notification will be posted + // to the app. + // The exact URL has to match, including fragment. We want to go through the _load + // method, even if to do a within-page navigation. + // The current frame tree and the frame tree snapshot in the item have to match. + if (!item->isTargetItem() && + itemURL == currentURL && + ((m_frame->tree()->name().isEmpty() && item->target().isEmpty()) || m_frame->tree()->name() == item->target()) && + childFramesMatchItem(item)) + { + // This content is good, so leave it alone and look for children that need reloading + // Save form state (works from currentItem, since prevItem is nil) + ASSERT(!m_previousHistoryItem); + saveDocumentState(); + saveScrollPositionAndViewStateToItem(m_currentHistoryItem.get()); + + if (FrameView* view = m_frame->view()) + view->setWasScrolledByUser(false); + + m_currentHistoryItem = item; + + // Restore form state (works from currentItem) + restoreDocumentState(); + + // Restore the scroll position (we choose to do this rather than going back to the anchor point) + restoreScrollPositionAndViewState(); + + const HistoryItemVector& childItems = item->children(); + + int size = childItems.size(); + for (int i = 0; i < size; ++i) { + String childName = childItems[i]->target(); + HistoryItem* fromChildItem = fromItem->childItemWithName(childName); + ASSERT(fromChildItem || fromItem->isTargetItem()); + Frame* childFrame = m_frame->tree()->child(childName); + ASSERT(childFrame); + childFrame->loader()->recursiveGoToItem(childItems[i].get(), fromChildItem, type); + } + } else { + loadItem(item, type); + } +} + +// helper method that determines whether the subframes described by the item's subitems +// match our own current frameset +bool FrameLoader::childFramesMatchItem(HistoryItem* item) const +{ + const HistoryItemVector& childItems = item->children(); + if (childItems.size() != m_frame->tree()->childCount()) + return false; + + unsigned size = childItems.size(); + for (unsigned i = 0; i < size; ++i) + if (!m_frame->tree()->child(childItems[i]->target())) + return false; + + // Found matches for all item targets + return true; +} + +// There are 3 things you might think of as "history", all of which are handled by these functions. +// +// 1) Back/forward: The m_currentHistoryItem is part of this mechanism. +// 2) Global history: Handled by the client. +// 3) Visited links: Handled by the PageGroup. + +void FrameLoader::updateHistoryForStandardLoad() +{ + LOG(History, "WebCoreHistory: Updating History for Standard Load in frame %s", documentLoader()->url().string().ascii().data()); + + Settings* settings = m_frame->settings(); + bool needPrivacy = !settings || settings->privateBrowsingEnabled(); + const KURL& historyURL = documentLoader()->urlForHistory(); + + // If the navigation occured during load and this is a subframe, update the current + // back/forward item rather than adding a new one and don't add the new URL to global + // history at all. But do add it to visited links. <rdar://problem/5333496> + bool frameNavigationDuringLoad = false; + if (m_navigationDuringLoad) { + HTMLFrameOwnerElement* owner = m_frame->ownerElement(); + frameNavigationDuringLoad = owner && !owner->createdByParser(); + m_navigationDuringLoad = false; + } + + if (!frameNavigationDuringLoad && !documentLoader()->isClientRedirect()) { + if (!historyURL.isEmpty()) { + addBackForwardItemClippedAtTarget(true); + if (!needPrivacy) + m_client->updateGlobalHistory(historyURL); + } + } else if (documentLoader()->unreachableURL().isEmpty() && m_currentHistoryItem) { + m_currentHistoryItem->setURL(documentLoader()->url()); + m_currentHistoryItem->setFormInfoFromRequest(documentLoader()->request()); + } + + if (!historyURL.isEmpty() && !needPrivacy) { + if (Page* page = m_frame->page()) + page->group().addVisitedLink(historyURL); + } +} + +void FrameLoader::updateHistoryForClientRedirect() +{ +#if !LOG_DISABLED + if (documentLoader()) + LOG(History, "WebCoreHistory: Updating History for client redirect in frame %s", documentLoader()->title().utf8().data()); +#endif + + // Clear out form data so we don't try to restore it into the incoming page. Must happen after + // webcore has closed the URL and saved away the form state. + if (m_currentHistoryItem) { + m_currentHistoryItem->clearDocumentState(); + m_currentHistoryItem->clearScrollPoint(); + } + + Settings* settings = m_frame->settings(); + bool needPrivacy = !settings || settings->privateBrowsingEnabled(); + const KURL& historyURL = documentLoader()->urlForHistory(); + + if (!historyURL.isEmpty() && !needPrivacy) { + if (Page* page = m_frame->page()) + page->group().addVisitedLink(historyURL); + } +} + +void FrameLoader::updateHistoryForBackForwardNavigation() +{ +#if !LOG_DISABLED + if (documentLoader()) + LOG(History, "WebCoreHistory: Updating History for back/forward navigation in frame %s", documentLoader()->title().utf8().data()); +#endif + + // Must grab the current scroll position before disturbing it + saveScrollPositionAndViewStateToItem(m_previousHistoryItem.get()); +} + +void FrameLoader::updateHistoryForReload() +{ +#if !LOG_DISABLED + if (documentLoader()) + LOG(History, "WebCoreHistory: Updating History for reload in frame %s", documentLoader()->title().utf8().data()); +#endif + + if (m_currentHistoryItem) { + pageCache()->remove(m_currentHistoryItem.get()); + + if (loadType() == FrameLoadTypeReload) + saveScrollPositionAndViewStateToItem(m_currentHistoryItem.get()); + + // Sometimes loading a page again leads to a different result because of cookies. Bugzilla 4072 + if (documentLoader()->unreachableURL().isEmpty()) + m_currentHistoryItem->setURL(documentLoader()->requestURL()); + } +} + +void FrameLoader::updateHistoryForRedirectWithLockedHistory() +{ +#if !LOG_DISABLED + if (documentLoader()) + LOG(History, "WebCoreHistory: Updating History for internal load in frame %s", documentLoader()->title().utf8().data()); +#endif + + Settings* settings = m_frame->settings(); + bool needPrivacy = !settings || settings->privateBrowsingEnabled(); + const KURL& historyURL = documentLoader()->urlForHistory(); + + if (documentLoader()->isClientRedirect()) { + if (!m_currentHistoryItem && !m_frame->tree()->parent()) { + addBackForwardItemClippedAtTarget(true); + if (!needPrivacy && !historyURL.isEmpty()) + m_client->updateGlobalHistory(historyURL); + } + if (m_currentHistoryItem) { + m_currentHistoryItem->setURL(documentLoader()->url()); + m_currentHistoryItem->setFormInfoFromRequest(documentLoader()->request()); + } + } else { + Frame* parentFrame = m_frame->tree()->parent(); + if (parentFrame && parentFrame->loader()->m_currentHistoryItem) + parentFrame->loader()->m_currentHistoryItem->addChildItem(createHistoryItem(true)); + } + + if (!historyURL.isEmpty() && !needPrivacy) { + if (Page* page = m_frame->page()) + page->group().addVisitedLink(historyURL); + } +} + +void FrameLoader::updateHistoryForCommit() +{ +#if !LOG_DISABLED + if (documentLoader()) + LOG(History, "WebCoreHistory: Updating History for commit in frame %s", documentLoader()->title().utf8().data()); +#endif + FrameLoadType type = loadType(); + if (isBackForwardLoadType(type) || + (type == FrameLoadTypeReload && !provisionalDocumentLoader()->unreachableURL().isEmpty())) { + // Once committed, we want to use current item for saving DocState, and + // the provisional item for restoring state. + // Note previousItem must be set before we close the URL, which will + // happen when the data source is made non-provisional below + m_previousHistoryItem = m_currentHistoryItem; + ASSERT(m_provisionalHistoryItem); + m_currentHistoryItem = m_provisionalHistoryItem; + m_provisionalHistoryItem = 0; + } +} + +void FrameLoader::updateHistoryForAnchorScroll() +{ + if (m_URL.isEmpty()) + return; + + Settings* settings = m_frame->settings(); + if (!settings || settings->privateBrowsingEnabled()) + return; + + Page* page = m_frame->page(); + if (!page) + return; + + page->group().addVisitedLink(m_URL); +} + +// Walk the frame tree, telling all frames to save their form state into their current +// history item. +void FrameLoader::saveDocumentAndScrollState() +{ + for (Frame* frame = m_frame; frame; frame = frame->tree()->traverseNext(m_frame)) { + frame->loader()->saveDocumentState(); + frame->loader()->saveScrollPositionAndViewStateToItem(frame->loader()->currentHistoryItem()); + } +} + +// FIXME: These 6 setter/getters are here for a dwindling number of users in WebKit, WebFrame +// being the primary one. After they're no longer needed there, they can be removed! +HistoryItem* FrameLoader::currentHistoryItem() +{ + return m_currentHistoryItem.get(); +} + +HistoryItem* FrameLoader::previousHistoryItem() +{ + return m_previousHistoryItem.get(); +} + +HistoryItem* FrameLoader::provisionalHistoryItem() +{ + return m_provisionalHistoryItem.get(); +} + +void FrameLoader::setCurrentHistoryItem(PassRefPtr<HistoryItem> item) +{ + m_currentHistoryItem = item; +} + +void FrameLoader::setPreviousHistoryItem(PassRefPtr<HistoryItem> item) +{ + m_previousHistoryItem = item; +} + +void FrameLoader::setProvisionalHistoryItem(PassRefPtr<HistoryItem> item) +{ + m_provisionalHistoryItem = item; +} + +void FrameLoader::setMainDocumentError(DocumentLoader* loader, const ResourceError& error) +{ + m_client->setMainDocumentError(loader, error); +} + +void FrameLoader::mainReceivedCompleteError(DocumentLoader* loader, const ResourceError& error) +{ + loader->setPrimaryLoadComplete(true); + m_client->dispatchDidLoadMainResource(activeDocumentLoader()); + checkCompleted(); + if (m_frame->page()) + checkLoadComplete(); +} + +void FrameLoader::mainReceivedError(const ResourceError& error, bool isComplete) +{ + activeDocumentLoader()->mainReceivedError(error, isComplete); +} + +ResourceError FrameLoader::cancelledError(const ResourceRequest& request) const +{ + ResourceError error = m_client->cancelledError(request); + error.setIsCancellation(true); + return error; +} + +ResourceError FrameLoader::blockedError(const ResourceRequest& request) const +{ + return m_client->blockedError(request); +} + +ResourceError FrameLoader::cannotShowURLError(const ResourceRequest& request) const +{ + return m_client->cannotShowURLError(request); +} + +ResourceError FrameLoader::fileDoesNotExistError(const ResourceResponse& response) const +{ + return m_client->fileDoesNotExistError(response); +} + +void FrameLoader::didFinishLoad(ResourceLoader* loader) +{ + if (Page* page = m_frame->page()) + page->progress()->completeProgress(loader->identifier()); + dispatchDidFinishLoading(loader->documentLoader(), loader->identifier()); +} + +void FrameLoader::didReceiveAuthenticationChallenge(ResourceLoader* loader, const AuthenticationChallenge& currentWebChallenge) +{ + m_client->dispatchDidReceiveAuthenticationChallenge(loader->documentLoader(), loader->identifier(), currentWebChallenge); +} + +void FrameLoader::didCancelAuthenticationChallenge(ResourceLoader* loader, const AuthenticationChallenge& currentWebChallenge) +{ + m_client->dispatchDidCancelAuthenticationChallenge(loader->documentLoader(), loader->identifier(), currentWebChallenge); +} + +PolicyCheck::PolicyCheck() + : m_navigationFunction(0) + , m_newWindowFunction(0) + , m_contentFunction(0) +{ +} + +void PolicyCheck::clear() +{ + clearRequest(); + m_navigationFunction = 0; + m_newWindowFunction = 0; + m_contentFunction = 0; +} + +void PolicyCheck::set(const ResourceRequest& request, PassRefPtr<FormState> formState, + NavigationPolicyDecisionFunction function, void* argument) +{ + m_request = request; + m_formState = formState; + m_frameName = String(); + + m_navigationFunction = function; + m_newWindowFunction = 0; + m_contentFunction = 0; + m_argument = argument; +} + +void PolicyCheck::set(const ResourceRequest& request, PassRefPtr<FormState> formState, + const String& frameName, NewWindowPolicyDecisionFunction function, void* argument) +{ + m_request = request; + m_formState = formState; + m_frameName = frameName; + + m_navigationFunction = 0; + m_newWindowFunction = function; + m_contentFunction = 0; + m_argument = argument; +} + +void PolicyCheck::set(ContentPolicyDecisionFunction function, void* argument) +{ + m_request = ResourceRequest(); + m_formState = 0; + m_frameName = String(); + + m_navigationFunction = 0; + m_newWindowFunction = 0; + m_contentFunction = function; + m_argument = argument; +} + +void PolicyCheck::call(bool shouldContinue) +{ + if (m_navigationFunction) + m_navigationFunction(m_argument, m_request, m_formState.get(), shouldContinue); + if (m_newWindowFunction) + m_newWindowFunction(m_argument, m_request, m_formState.get(), m_frameName, shouldContinue); + ASSERT(!m_contentFunction); +} + +void PolicyCheck::call(PolicyAction action) +{ + ASSERT(!m_navigationFunction); + ASSERT(!m_newWindowFunction); + ASSERT(m_contentFunction); + m_contentFunction(m_argument, action); +} + +void PolicyCheck::clearRequest() +{ + m_request = ResourceRequest(); + m_formState = 0; + m_frameName = String(); +} + +void PolicyCheck::cancel() +{ + clearRequest(); + if (m_navigationFunction) + m_navigationFunction(m_argument, m_request, m_formState.get(), false); + if (m_newWindowFunction) + m_newWindowFunction(m_argument, m_request, m_formState.get(), m_frameName, false); + if (m_contentFunction) + m_contentFunction(m_argument, PolicyIgnore); +} + +void FrameLoader::setTitle(const String& title) +{ + documentLoader()->setTitle(title); +} + +KURL FrameLoader::originalRequestURL() const +{ + return activeDocumentLoader()->originalRequest().url(); +} + +String FrameLoader::referrer() const +{ + return documentLoader()->request().httpReferrer(); +} + +void FrameLoader::dispatchWindowObjectAvailable() +{ + if (!m_frame->script()->isEnabled() || !m_frame->script()->haveWindowShell()) + return; + + m_client->windowObjectCleared(); + + if (Page* page = m_frame->page()) { + if (InspectorController* inspector = page->inspectorController()) + inspector->inspectedWindowScriptObjectCleared(m_frame); + if (InspectorController* inspector = page->parentInspectorController()) + inspector->windowScriptObjectAvailable(); + } +} + +Widget* FrameLoader::createJavaAppletWidget(const IntSize& size, Element* element, const HashMap<String, String>& args) +{ + String baseURLString; + Vector<String> paramNames; + Vector<String> paramValues; + HashMap<String, String>::const_iterator end = args.end(); + for (HashMap<String, String>::const_iterator it = args.begin(); it != end; ++it) { + if (equalIgnoringCase(it->first, "baseurl")) + baseURLString = it->second; + paramNames.append(it->first); + paramValues.append(it->second); + } + + if (baseURLString.isEmpty()) + baseURLString = m_frame->document()->baseURL().string(); + KURL baseURL = completeURL(baseURLString); + + Widget* widget = m_client->createJavaAppletWidget(size, element, baseURL, paramNames, paramValues); + if (widget) + m_containsPlugIns = true; + + return widget; +} + +void FrameLoader::didChangeTitle(DocumentLoader* loader) +{ + m_client->didChangeTitle(loader); + + // The title doesn't get communicated to the WebView until we are committed. + if (loader->isCommitted()) { + // Must update the entries in the back-forward list too. + if (m_currentHistoryItem) + m_currentHistoryItem->setTitle(loader->title()); + // This must go through the WebFrame because it has the right notion of the current b/f item. + m_client->setTitle(loader->title(), loader->urlForHistory()); + m_client->setMainFrameDocumentReady(true); // update observers with new DOMDocument + m_client->dispatchDidReceiveTitle(loader->title()); + } +} + +void FrameLoader::continueLoadWithData(SharedBuffer* buffer, const String& mimeType, const String& textEncoding, const KURL& url) +{ + m_responseMIMEType = mimeType; + didOpenURL(url); + + String encoding; + if (m_frame) + encoding = documentLoader()->overrideEncoding(); + bool userChosen = !encoding.isNull(); + if (encoding.isNull()) + encoding = textEncoding; + setEncoding(encoding, userChosen); + + ASSERT(m_frame->document()); + + addData(buffer->data(), buffer->size()); +} + +void FrameLoader::registerURLSchemeAsLocal(const String& scheme) +{ + localSchemes().add(scheme); +} + +bool FrameLoader::shouldTreatURLAsLocal(const String& url) +{ + // This avoids an allocation of another String and the HashSet contains() + // call for the file: and http: schemes. + if (url.length() >= 5) { + const UChar* s = url.characters(); + if (s[0] == 'h' && s[1] == 't' && s[2] == 't' && s[3] == 'p' && s[4] == ':') + return false; + if (s[0] == 'f' && s[1] == 'i' && s[2] == 'l' && s[3] == 'e' && s[4] == ':') + return true; + } + + int loc = url.find(':'); + if (loc == -1) + return false; + + String scheme = url.left(loc); + return localSchemes().contains(scheme); +} + +bool FrameLoader::shouldTreatSchemeAsLocal(const String& scheme) +{ + // This avoids an allocation of another String and the HashSet contains() + // call for the file: and http: schemes. + if (scheme.length() == 4) { + const UChar* s = scheme.characters(); + if (s[0] == 'h' && s[1] == 't' && s[2] == 't' && s[3] == 'p') + return false; + if (s[0] == 'f' && s[1] == 'i' && s[2] == 'l' && s[3] == 'e') + return true; + } + + if (scheme.isEmpty()) + return false; + + return localSchemes().contains(scheme); +} + +void FrameLoader::dispatchDidCommitLoad() +{ + if (m_creatingInitialEmptyDocument) + return; + +#ifndef NDEBUG + m_didDispatchDidCommitLoad = true; +#endif + + m_client->dispatchDidCommitLoad(); + + if (Page* page = m_frame->page()) + page->inspectorController()->didCommitLoad(m_documentLoader.get()); +} + +void FrameLoader::dispatchAssignIdentifierToInitialRequest(unsigned long identifier, DocumentLoader* loader, const ResourceRequest& request) +{ + m_client->assignIdentifierToInitialRequest(identifier, loader, request); + + if (Page* page = m_frame->page()) + page->inspectorController()->identifierForInitialRequest(identifier, loader, request); +} + +void FrameLoader::dispatchWillSendRequest(DocumentLoader* loader, unsigned long identifier, ResourceRequest& request, const ResourceResponse& redirectResponse) +{ + m_client->dispatchWillSendRequest(loader, identifier, request, redirectResponse); + + if (Page* page = m_frame->page()) + page->inspectorController()->willSendRequest(loader, identifier, request, redirectResponse); +} + +void FrameLoader::dispatchDidReceiveResponse(DocumentLoader* loader, unsigned long identifier, const ResourceResponse& r) +{ + m_client->dispatchDidReceiveResponse(loader, identifier, r); + + if (Page* page = m_frame->page()) + page->inspectorController()->didReceiveResponse(loader, identifier, r); +} + +void FrameLoader::dispatchDidReceiveContentLength(DocumentLoader* loader, unsigned long identifier, int length) +{ + m_client->dispatchDidReceiveContentLength(loader, identifier, length); + + if (Page* page = m_frame->page()) + page->inspectorController()->didReceiveContentLength(loader, identifier, length); +} + +void FrameLoader::dispatchDidFinishLoading(DocumentLoader* loader, unsigned long identifier) +{ + m_client->dispatchDidFinishLoading(loader, identifier); + + if (Page* page = m_frame->page()) + page->inspectorController()->didFinishLoading(loader, identifier); +} + +#if USE(LOW_BANDWIDTH_DISPLAY) + +bool FrameLoader::addLowBandwidthDisplayRequest(CachedResource* cache) +{ + if (m_frame->document()->inLowBandwidthDisplay() == false) + return false; + + // if cache is loaded, don't add to the list, where notifyFinished() is expected. + if (cache->isLoaded()) + return false; + + switch (cache->type()) { + case CachedResource::CSSStyleSheet: + case CachedResource::Script: + m_needToSwitchOutLowBandwidthDisplay = true; + m_externalRequestsInLowBandwidthDisplay.add(cache); + cache->addClient(this); + return true; + case CachedResource::ImageResource: + case CachedResource::FontResource: +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: +#endif +#if ENABLE(XBL) + case CachedResource::XBLStyleSheet: +#endif + return false; + } + + ASSERT_NOT_REACHED(); + return false; +} + +void FrameLoader::removeAllLowBandwidthDisplayRequests() +{ + HashSet<CachedResource*>::iterator end = m_externalRequestsInLowBandwidthDisplay.end(); + for (HashSet<CachedResource*>::iterator it = m_externalRequestsInLowBandwidthDisplay.begin(); it != end; ++it) + (*it)->removeClient(this); + m_externalRequestsInLowBandwidthDisplay.clear(); +} + +void FrameLoader::notifyFinished(CachedResource* script) +{ + HashSet<CachedResource*>::iterator it = m_externalRequestsInLowBandwidthDisplay.find(script); + if (it != m_externalRequestsInLowBandwidthDisplay.end()) { + (*it)->removeClient(this); + m_externalRequestsInLowBandwidthDisplay.remove(it); + switchOutLowBandwidthDisplayIfReady(); + } +} + +void FrameLoader::switchOutLowBandwidthDisplayIfReady() +{ + RefPtr<Document> oldDoc = m_frame->document(); + if (oldDoc->inLowBandwidthDisplay()) { + if (!m_needToSwitchOutLowBandwidthDisplay) { + // no need to switch, just reset state + oldDoc->setLowBandwidthDisplay(false); + removeAllLowBandwidthDisplayRequests(); + m_pendingSourceInLowBandwidthDisplay = String(); + m_finishedParsingDuringLowBandwidthDisplay = false; + return; + } else if (m_externalRequestsInLowBandwidthDisplay.isEmpty() || + m_pendingSourceInLowBandwidthDisplay.length() > cMaxPendingSourceLengthInLowBandwidthDisplay) { + // clear the flag first + oldDoc->setLowBandwidthDisplay(false); + + // similar to clear(), should be refactored to share more code + oldDoc->cancelParsing(); + oldDoc->detach(); + if (m_frame->script()->isEnabled()) + m_frame->script()->clearWindowShell(); + if (m_frame->view()) + m_frame->view()->clear(); + + // similar to begin(), should be refactored to share more code + RefPtr<Document> newDoc = DOMImplementation::createDocument(m_responseMIMEType, m_frame, m_frame->inViewSourceMode()); + m_frame->setDocument(newDoc); + newDoc->setURL(m_URL); + if (m_decoder) + newDoc->setDecoder(m_decoder.get()); + restoreDocumentState(); + dispatchWindowObjectAvailable(); + newDoc->implicitOpen(); + + // swap DocLoader ownership + DocLoader* docLoader = newDoc->docLoader(); + newDoc->setDocLoader(oldDoc->docLoader()); + newDoc->docLoader()->replaceDocument(newDoc.get()); + docLoader->replaceDocument(oldDoc.get()); + oldDoc->setDocLoader(docLoader); + + // drop the old doc + oldDoc = 0; + + // write decoded data to the new doc, similar to write() + if (m_pendingSourceInLowBandwidthDisplay.length()) { + // set cachePolicy to Cache to use the loaded resource + newDoc->docLoader()->setCachePolicy(CachePolicyCache); + if (m_decoder->encoding().usesVisualOrdering()) + newDoc->setVisuallyOrdered(); + newDoc->recalcStyle(Node::Force); + newDoc->tokenizer()->write(m_pendingSourceInLowBandwidthDisplay, true); + + if (m_finishedParsingDuringLowBandwidthDisplay) + newDoc->finishParsing(); + } + + // update rendering + newDoc->updateRendering(); + + // reset states + removeAllLowBandwidthDisplayRequests(); + m_pendingSourceInLowBandwidthDisplay = String(); + m_finishedParsingDuringLowBandwidthDisplay = false; + m_needToSwitchOutLowBandwidthDisplay = false; + } + } +} + +#endif + +} // namespace WebCore diff --git a/WebCore/loader/FrameLoader.h b/WebCore/loader/FrameLoader.h new file mode 100644 index 0000000..47a56af --- /dev/null +++ b/WebCore/loader/FrameLoader.h @@ -0,0 +1,701 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 FrameLoader_h +#define FrameLoader_h + +#include "CachedResource.h" +#include "CachePolicy.h" +#include "FormState.h" +#include "FrameLoaderTypes.h" +#include "KURL.h" +#include "StringHash.h" +#include "Timer.h" +#include <wtf/Forward.h> +#include <wtf/HashSet.h> +#include <wtf/HashMap.h> +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> +#include <wtf/RefPtr.h> +#include "ResourceRequest.h" +#if USE(LOW_BANDWIDTH_DISPLAY) +#include "CachedResourceClient.h" +#endif + +namespace WebCore { + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + class Archive; + class ArchiveResource; +#endif + class AuthenticationChallenge; + class CachedPage; + class Document; + class DocumentLoader; + class Element; + class Event; + class FormData; + class Frame; + class FrameLoaderClient; + class HistoryItem; + class HTMLFormElement; + class HTMLFrameOwnerElement; + class IconLoader; + class IntSize; + class NavigationAction; + class Node; + class Page; + class RenderPart; + class ResourceError; + class ResourceLoader; + class ResourceRequest; + class ResourceResponse; + class SecurityOrigin; + class SharedBuffer; + class SubstituteData; + class TextResourceDecoder; + class Widget; + + struct FormSubmission; + struct FrameLoadRequest; + struct ScheduledRedirection; + struct WindowFeatures; + + bool isBackForwardLoadType(FrameLoadType); + + typedef void (*NavigationPolicyDecisionFunction)(void* argument, + const ResourceRequest&, PassRefPtr<FormState>, bool shouldContinue); + typedef void (*NewWindowPolicyDecisionFunction)(void* argument, + const ResourceRequest&, PassRefPtr<FormState>, const String& frameName, bool shouldContinue); + typedef void (*ContentPolicyDecisionFunction)(void* argument, PolicyAction); + + class PolicyCheck { + public: + PolicyCheck(); + + void clear(); + void set(const ResourceRequest&, PassRefPtr<FormState>, + NavigationPolicyDecisionFunction, void* argument); + void set(const ResourceRequest&, PassRefPtr<FormState>, const String& frameName, + NewWindowPolicyDecisionFunction, void* argument); + void set(ContentPolicyDecisionFunction, void* argument); + + const ResourceRequest& request() const { return m_request; } + void clearRequest(); + + void call(bool shouldContinue); + void call(PolicyAction); + void cancel(); + + private: + ResourceRequest m_request; + RefPtr<FormState> m_formState; + String m_frameName; + + NavigationPolicyDecisionFunction m_navigationFunction; + NewWindowPolicyDecisionFunction m_newWindowFunction; + ContentPolicyDecisionFunction m_contentFunction; + void* m_argument; + }; + + class FrameLoader : Noncopyable +#if USE(LOW_BANDWIDTH_DISPLAY) + , private CachedResourceClient +#endif + { + public: + FrameLoader(Frame*, FrameLoaderClient*); + ~FrameLoader(); + + void init(); + + Frame* frame() const { return m_frame; } + + // FIXME: This is not cool, people. We should aim to consolidate these variety of loading related methods into a smaller set, + // and try to reuse more of the same logic by extracting common code paths. + void prepareForLoadStart(); + void setupForReplace(); + void setupForReplaceByMIMEType(const String& newMIMEType); + + void loadWithDocumentLoader(DocumentLoader*, FrameLoadType, PassRefPtr<FormState>); // Calls continueLoadAfterNavigationPolicy + void load(DocumentLoader*); // Calls loadWithDocumentLoader + + void loadWithNavigationAction(const ResourceRequest&, const NavigationAction&, // Calls loadWithDocumentLoader() + FrameLoadType, PassRefPtr<FormState>); + +#ifdef ANDROID_USER_GESTURE + void loadPostRequest(const ResourceRequest& inRequest, const String& referrer, // Called by loadFrameRequestWithFormAndValues(), calls loadWithNavigationAction + const String& frameName, Event* event, PassRefPtr<FormState> prpFormState, bool userGesture); + + void loadURL(const KURL& newURL, const String& referrer, const String& frameName, // Called by loadFrameRequestWithFormAndValues(), calls loadWithNavigationAction or else dispatches to navigation policy delegate + FrameLoadType, Event* event, PassRefPtr<FormState> prpFormState, bool userGesture); +#else + void loadPostRequest(const ResourceRequest& inRequest, const String& referrer, // Called by loadFrameRequestWithFormAndValues(), calls loadWithNavigationAction + const String& frameName, Event* event, PassRefPtr<FormState> prpFormState); + + void loadURL(const KURL& newURL, const String& referrer, const String& frameName, // Called by loadFrameRequestWithFormAndValues(), calls loadWithNavigationAction or else dispatches to navigation policy delegate + FrameLoadType, Event* event, PassRefPtr<FormState> prpFormState); +#endif + void loadURLIntoChildFrame(const KURL&, const String& referer, Frame*); + + void loadFrameRequestWithFormState(const FrameLoadRequest&, bool lockHistory, Event*, PassRefPtr<FormState>); + void loadFrameRequestWithFormAndValues(const FrameLoadRequest&, bool lockHistory, // Called by submitForm, calls loadPostRequest() + Event*, HTMLFormElement*, const HashMap<String, String>& formValues); + + void load(const ResourceRequest&); // Called by WebFrame, calls (ResourceRequest, SubstituteData) + void load(const ResourceRequest&, const SubstituteData&); // Called both by WebFrame and internally, calls (DocumentLoader*) + void load(const ResourceRequest&, const String& frameName); // Called by WebPluginController + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + void loadArchive(PassRefPtr<Archive> archive); +#endif + + // Returns true for any non-local URL. If Document parameter is supplied, its local load policy dictates, + // otherwise if referrer is non-empty and represents a local file, then the local load is allowed. + static bool canLoad(const KURL&, const String& referrer, const Document* theDocument = 0); + static void reportLocalLoadFailed(Frame*, const String& url); + + static bool shouldHideReferrer(const KURL& url, const String& referrer); + + // Called by createWindow in JSDOMWindowBase.cpp, e.g. to fulfill a modal dialog creation + Frame* createWindow(FrameLoader* frameLoaderForFrameLookup, const FrameLoadRequest&, const WindowFeatures&, bool& created); + + unsigned long loadResourceSynchronously(const ResourceRequest&, ResourceError&, ResourceResponse&, Vector<char>& data); + + bool canHandleRequest(const ResourceRequest&); + + // Also not cool. + void stopAllLoaders(); + void stopForUserCancel(bool deferCheckLoadComplete = false); + + bool isLoadingMainResource() const { return m_isLoadingMainResource; } + bool isLoading() const; + bool frameHasLoaded() const; + + int numPendingOrLoadingRequests(bool recurse) const; + bool isReloading() const; + String referrer() const; + String outgoingReferrer() const; + String outgoingOrigin() const; + void loadEmptyDocumentSynchronously(); + + DocumentLoader* activeDocumentLoader() const; + DocumentLoader* documentLoader() const; + DocumentLoader* policyDocumentLoader() const; + DocumentLoader* provisionalDocumentLoader() const; + FrameState state() const; + static double timeOfLastCompletedLoad(); + + void didReceiveAuthenticationChallenge(ResourceLoader*, const AuthenticationChallenge&); + void didCancelAuthenticationChallenge(ResourceLoader*, const AuthenticationChallenge&); + + void assignIdentifierToInitialRequest(unsigned long identifier, const ResourceRequest&); + void willSendRequest(ResourceLoader*, ResourceRequest&, const ResourceResponse& redirectResponse); + void didReceiveResponse(ResourceLoader*, const ResourceResponse&); + void didReceiveData(ResourceLoader*, const char*, int, int lengthReceived); + void didFinishLoad(ResourceLoader*); + void didFailToLoad(ResourceLoader*, const ResourceError&); + const ResourceRequest& originalRequest() const; + const ResourceRequest& initialRequest() const; + void receivedMainResourceError(const ResourceError&, bool isComplete); + void receivedData(const char*, int); + + void handleFallbackContent(); + bool isStopping() const; + + void finishedLoading(); + + ResourceError cancelledError(const ResourceRequest&) const; + ResourceError fileDoesNotExistError(const ResourceResponse&) const; + ResourceError blockedError(const ResourceRequest&) const; + ResourceError cannotShowURLError(const ResourceRequest&) const; + + void cannotShowMIMEType(const ResourceResponse&); + ResourceError interruptionForPolicyChangeError(const ResourceRequest&); + + bool isHostedByObjectElement() const; + bool isLoadingMainFrame() const; + bool canShowMIMEType(const String& MIMEType) const; + bool representationExistsForURLScheme(const String& URLScheme); + String generatedMIMETypeForURLScheme(const String& URLScheme); + + void notifyIconChanged(); + + void checkNavigationPolicy(const ResourceRequest&, NavigationPolicyDecisionFunction function, void* argument); + void checkContentPolicy(const String& MIMEType, ContentPolicyDecisionFunction, void* argument); + void cancelContentPolicyCheck(); + + void reload(); + void reloadAllowingStaleData(const String& overrideEncoding); + + void didReceiveServerRedirectForProvisionalLoadForFrame(); + void finishedLoadingDocument(DocumentLoader*); + void committedLoad(DocumentLoader*, const char*, int); + bool isReplacing() const; + void setReplacing(); + void revertToProvisional(DocumentLoader*); + void setMainDocumentError(DocumentLoader*, const ResourceError&); + void mainReceivedCompleteError(DocumentLoader*, const ResourceError&); + bool subframeIsLoading() const; + void willChangeTitle(DocumentLoader*); + void didChangeTitle(DocumentLoader*); + + FrameLoadType loadType() const; + + void didFirstLayout(); + bool firstLayoutDone() const; + + void clientRedirectCancelledOrFinished(bool cancelWithLoadInProgress); + void clientRedirected(const KURL&, double delay, double fireDate, bool lockHistory, bool isJavaScriptFormAction); + bool shouldReload(const KURL& currentURL, const KURL& destinationURL); + + bool isQuickRedirectComing() const; + + void sendRemainingDelegateMessages(unsigned long identifier, const ResourceResponse&, int length, const ResourceError&); + void requestFromDelegate(ResourceRequest&, unsigned long& identifier, ResourceError&); + void loadedResourceFromMemoryCache(const CachedResource*); + + void recursiveCheckLoadComplete(); + void checkLoadComplete(); + void detachFromParent(); + void detachChildren(); + + void addExtraFieldsToRequest(ResourceRequest&, bool isMainResource, bool alwaysFromRequest); + static void addHTTPOriginIfNeeded(ResourceRequest&, String origin); + + FrameLoaderClient* client() const; + + void setDefersLoading(bool); + + void changeLocation(const String& url, const String& referrer, bool lockHistory = true, bool userGesture = false); + void changeLocation(const KURL&, const String& referrer, bool lockHistory = true, bool userGesture = false); + void urlSelected(const ResourceRequest&, const String& target, Event*, bool lockHistory, bool userGesture); + void urlSelected(const FrameLoadRequest&, Event*, bool lockHistory); + + bool requestFrame(HTMLFrameOwnerElement*, const String& url, const AtomicString& frameName); + Frame* loadSubframe(HTMLFrameOwnerElement*, const KURL&, const String& name, const String& referrer); + + void submitForm(const char* action, const String& url, PassRefPtr<FormData>, const String& target, const String& contentType, const String& boundary, Event*); + void submitFormAgain(); + void submitForm(const FrameLoadRequest&, Event*); + + void stop(); + void stopLoading(bool sendUnload); + bool closeURL(); + + void didExplicitOpen(); + + KURL iconURL(); + void commitIconURLToIconDatabase(const KURL&); + + KURL baseURL() const; + String baseTarget() const; + KURL dataURLBaseFromRequest(const ResourceRequest& request) const; + + bool isScheduledLocationChangePending() const { return m_scheduledRedirection && isLocationChange(*m_scheduledRedirection); } + void scheduleHTTPRedirection(double delay, const String& url); + void scheduleLocationChange(const String& url, const String& referrer, bool lockHistory = true, bool userGesture = false); + void scheduleRefresh(bool userGesture = false); + void scheduleHistoryNavigation(int steps); + + bool canGoBackOrForward(int distance) const; + void goBackOrForward(int distance); + int getHistoryLength(); + KURL historyURL(int distance); + + void begin(); + void begin(const KURL&, bool dispatchWindowObjectAvailable = true, SecurityOrigin* forcedSecurityOrigin = 0); + + void write(const char* str, int len = -1, bool flush = false); + void write(const String&); + void end(); + void endIfNotLoadingMainResource(); + + void setEncoding(const String& encoding, bool userChosen); + String encoding() const; + + // Returns true if url is a JavaScript URL. + bool executeIfJavaScriptURL(const KURL& url, bool userGesture = false, bool replaceDocument = true); + + JSC::JSValue* executeScript(const String& url, int baseLine, const String& script); + JSC::JSValue* executeScript(const String& script, bool forceUserGesture = false); + + void gotoAnchor(); + bool gotoAnchor(const String& name); // returns true if the anchor was found + void scrollToAnchor(const KURL&); + + void tokenizerProcessedData(); + + void handledOnloadEvents(); + String userAgent(const KURL&) const; + + Widget* createJavaAppletWidget(const IntSize&, Element*, const HashMap<String, String>& args); + + void dispatchWindowObjectAvailable(); + void restoreDocumentState(); + + Frame* opener(); + void setOpener(Frame*); + bool openedByDOM() const; + void setOpenedByDOM(); + + void provisionalLoadStarted(); + + bool userGestureHint(); + + void resetMultipleFormSubmissionProtection(); + void didNotOpenURL(const KURL&); + + void addData(const char* bytes, int length); + + bool canCachePage(); + + void checkCallImplicitClose(); + bool didOpenURL(const KURL&); + + void frameDetached(); + + const KURL& url() const { return m_URL; } + + void updateBaseURLForEmptyDocument(); + + void setResponseMIMEType(const String&); + const String& responseMIMEType() const; + + bool containsPlugins() const; + + void loadDone(); + void finishedParsing(); + void checkCompleted(); + void scheduleCheckCompleted(); + void scheduleCheckLoadComplete(); + + void clearRecordedFormValues(); + void setFormAboutToBeSubmitted(PassRefPtr<HTMLFormElement> element); + void recordFormValue(const String& name, const String& value); + + bool isComplete() const; + + bool requestObject(RenderPart* frame, const String& url, const AtomicString& frameName, + const String& serviceType, const Vector<String>& paramNames, const Vector<String>& paramValues); + + KURL completeURL(const String& url); + + void didTellClientAboutLoad(const String& url); + bool haveToldClientAboutLoad(const String& url); + + KURL originalRequestURL() const; + + void cancelAndClear(); + + void setTitle(const String&); + + bool shouldTreatURLAsSameAsCurrent(const KURL&) const; + + void commitProvisionalLoad(PassRefPtr<CachedPage>); + + void goToItem(HistoryItem*, FrameLoadType); + void saveDocumentAndScrollState(); + void saveScrollPositionAndViewStateToItem(HistoryItem*); + + // FIXME: These accessors are here for a dwindling number of users in WebKit, WebFrame + // being the primary one. After they're no longer needed there, they can be removed! + HistoryItem* currentHistoryItem(); + HistoryItem* previousHistoryItem(); + HistoryItem* provisionalHistoryItem(); + void setCurrentHistoryItem(PassRefPtr<HistoryItem>); + void setPreviousHistoryItem(PassRefPtr<HistoryItem>); + void setProvisionalHistoryItem(PassRefPtr<HistoryItem>); + + void continueLoadWithData(SharedBuffer*, const String& mimeType, const String& textEncoding, const KURL&); + + enum LocalLoadPolicy { + AllowLocalLoadsForAll, // No restriction on local loads. + AllowLocalLoadsForLocalAndSubstituteData, + AllowLocalLoadsForLocalOnly, + }; + static void setLocalLoadPolicy(LocalLoadPolicy); + static bool restrictAccessToLocal(); + static bool allowSubstituteDataAccessToLocal(); + + static void registerURLSchemeAsLocal(const String& scheme); + static bool shouldTreatURLAsLocal(const String&); + static bool shouldTreatSchemeAsLocal(const String&); + +#if USE(LOW_BANDWIDTH_DISPLAY) + bool addLowBandwidthDisplayRequest(CachedResource*); + void needToSwitchOutLowBandwidthDisplay() { m_needToSwitchOutLowBandwidthDisplay = true; } + + // Client can control whether to use low bandwidth display on a per frame basis. + // However, this should only be used for the top frame, not sub-frame. + void setUseLowBandwidthDisplay(bool lowBandwidth) { m_useLowBandwidthDisplay = lowBandwidth; } + bool useLowBandwidthDisplay() const { return m_useLowBandwidthDisplay; } +#endif + + bool committingFirstRealLoad() const { return !m_creatingInitialEmptyDocument && !m_committedFirstRealDocumentLoad; } + + void iconLoadDecisionAvailable(); + + bool shouldAllowNavigation(Frame* targetFrame) const; + Frame* findFrameForNavigation(const AtomicString& name); + + void startIconLoader(); + + void applyUserAgent(ResourceRequest& request); + + private: + PassRefPtr<HistoryItem> createHistoryItem(bool useOriginal); + PassRefPtr<HistoryItem> createHistoryItemTree(Frame* targetFrame, bool clipAtTarget); + + void addBackForwardItemClippedAtTarget(bool doClip); + void restoreScrollPositionAndViewState(); + void saveDocumentState(); + void loadItem(HistoryItem*, FrameLoadType); + bool urlsMatchItem(HistoryItem*) const; + void invalidateCurrentItemCachedPage(); + void recursiveGoToItem(HistoryItem*, HistoryItem*, FrameLoadType); + bool childFramesMatchItem(HistoryItem*) const; + + void updateHistoryForBackForwardNavigation(); + void updateHistoryForReload(); + void updateHistoryForStandardLoad(); + void updateHistoryForRedirectWithLockedHistory(); + void updateHistoryForClientRedirect(); + void updateHistoryForCommit(); + void updateHistoryForAnchorScroll(); + + void redirectionTimerFired(Timer<FrameLoader>*); + void checkCompletedTimerFired(Timer<FrameLoader>*); + void checkLoadCompleteTimerFired(Timer<FrameLoader>*); + + void cancelRedirection(bool newLoadInProgress = false); + + void started(); + + void completed(); + void parentCompleted(); + + bool shouldUsePlugin(const KURL&, const String& mimeType, bool hasFallback, bool& useFallback); + bool loadPlugin(RenderPart*, const KURL&, const String& mimeType, + const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback); + + bool loadProvisionalItemFromCachedPage(); + void cachePageForHistoryItem(HistoryItem*); + + void receivedFirstData(); + + void updatePolicyBaseURL(); + void setPolicyBaseURL(const KURL&); + + // Also not cool. + void stopLoadingSubframes(); + + void clearProvisionalLoad(); + void markLoadComplete(); + void transitionToCommitted(PassRefPtr<CachedPage>); + void frameLoadCompleted(); + + void mainReceivedError(const ResourceError&, bool isComplete); + + void setLoadType(FrameLoadType); + + void checkNavigationPolicy(const ResourceRequest&, DocumentLoader*, PassRefPtr<FormState>, + NavigationPolicyDecisionFunction, void* argument); + void checkNewWindowPolicy(const NavigationAction&, const ResourceRequest&, + PassRefPtr<FormState>, const String& frameName); + + void continueAfterNavigationPolicy(PolicyAction); + void continueAfterNewWindowPolicy(PolicyAction); + void continueAfterContentPolicy(PolicyAction); + void continueLoadAfterWillSubmitForm(PolicyAction = PolicyUse); + + static void callContinueLoadAfterNavigationPolicy(void*, const ResourceRequest&, PassRefPtr<FormState>, bool shouldContinue); + void continueLoadAfterNavigationPolicy(const ResourceRequest&, PassRefPtr<FormState>, bool shouldContinue); + static void callContinueLoadAfterNewWindowPolicy(void*, const ResourceRequest&, PassRefPtr<FormState>, const String& frameName, bool shouldContinue); + void continueLoadAfterNewWindowPolicy(const ResourceRequest&, PassRefPtr<FormState>, const String& frameName, bool shouldContinue); + static void callContinueFragmentScrollAfterNavigationPolicy(void*, const ResourceRequest&, PassRefPtr<FormState>, bool shouldContinue); + void continueFragmentScrollAfterNavigationPolicy(const ResourceRequest&, bool shouldContinue); + bool shouldScrollToAnchor(bool isFormSubmission, FrameLoadType loadType, const KURL& url); + void addHistoryItemForFragmentScroll(); + + void stopPolicyCheck(); + + void closeDocument(); + + void checkLoadCompleteForThisFrame(); + + void setDocumentLoader(DocumentLoader*); + void setPolicyDocumentLoader(DocumentLoader*); + void setProvisionalDocumentLoader(DocumentLoader*); + + void setState(FrameState); + + void closeOldDataSources(); + void open(CachedPage&); + void opened(); + void updateHistoryAfterClientRedirect(); + + void clear(bool clearWindowProperties = true, bool clearScriptObjects = true); + + bool shouldReloadToHandleUnreachableURL(DocumentLoader*); + void handleUnimplementablePolicy(const ResourceError&); + + void scheduleRedirection(ScheduledRedirection*); + void startRedirectionTimer(); + void stopRedirectionTimer(); + +#if USE(LOW_BANDWIDTH_DISPLAY) + // implementation of CachedResourceClient + virtual void notifyFinished(CachedResource*); + + void removeAllLowBandwidthDisplayRequests(); + void switchOutLowBandwidthDisplayIfReady(); +#endif + + void dispatchDidCommitLoad(); + void dispatchAssignIdentifierToInitialRequest(unsigned long identifier, DocumentLoader*, const ResourceRequest&); + void dispatchWillSendRequest(DocumentLoader*, unsigned long identifier, ResourceRequest&, const ResourceResponse& redirectResponse); + void dispatchDidReceiveResponse(DocumentLoader*, unsigned long identifier, const ResourceResponse&); + void dispatchDidReceiveContentLength(DocumentLoader*, unsigned long identifier, int length); + void dispatchDidFinishLoading(DocumentLoader*, unsigned long identifier); + + static bool isLocationChange(const ScheduledRedirection&); + + Frame* m_frame; + FrameLoaderClient* m_client; + + FrameState m_state; + FrameLoadType m_loadType; + + // Document loaders for the three phases of frame loading. Note that while + // a new request is being loaded, the old document loader may still be referenced. + // E.g. while a new request is in the "policy" state, the old document loader may + // be consulted in particular as it makes sense to imply certain settings on the new loader. + RefPtr<DocumentLoader> m_documentLoader; + RefPtr<DocumentLoader> m_provisionalDocumentLoader; + RefPtr<DocumentLoader> m_policyDocumentLoader; + + // This identifies the type of navigation action which prompted this load. Note + // that WebKit conveys this value as the WebActionNavigationTypeKey value + // on navigation action delegate callbacks. + FrameLoadType m_policyLoadType; + PolicyCheck m_policyCheck; + + bool m_delegateIsHandlingProvisionalLoadError; + bool m_delegateIsDecidingNavigationPolicy; + bool m_delegateIsHandlingUnimplementablePolicy; + + bool m_firstLayoutDone; + bool m_quickRedirectComing; + bool m_sentRedirectNotification; + bool m_inStopAllLoaders; + bool m_navigationDuringLoad; + + String m_outgoingReferrer; + + CachePolicy m_cachePolicy; + + HashSet<String> m_urlsClientKnowsAbout; + + OwnPtr<FormSubmission> m_deferredFormSubmission; + + bool m_isExecutingJavaScriptFormAction; + bool m_isRunningScript; + + String m_responseMIMEType; + + bool m_didCallImplicitClose; + bool m_wasUnloadEventEmitted; + bool m_isComplete; + bool m_isLoadingMainResource; + + KURL m_URL; + KURL m_workingURL; + + OwnPtr<IconLoader> m_iconLoader; + bool m_mayLoadIconLater; + + bool m_cancellingWithLoadInProgress; + + OwnPtr<ScheduledRedirection> m_scheduledRedirection; + + bool m_needsClear; + bool m_receivedData; + + bool m_encodingWasChosenByUser; + String m_encoding; + RefPtr<TextResourceDecoder> m_decoder; + + bool m_containsPlugIns; + + RefPtr<HTMLFormElement> m_formAboutToBeSubmitted; + HashMap<String, String> m_formValuesAboutToBeSubmitted; + KURL m_submittedFormURL; + + Timer<FrameLoader> m_redirectionTimer; + Timer<FrameLoader> m_checkCompletedTimer; + Timer<FrameLoader> m_checkLoadCompleteTimer; + + Frame* m_opener; + HashSet<Frame*> m_openedFrames; + + bool m_openedByDOM; + + bool m_creatingInitialEmptyDocument; + bool m_isDisplayingInitialEmptyDocument; + bool m_committedFirstRealDocumentLoad; + + RefPtr<HistoryItem> m_currentHistoryItem; + RefPtr<HistoryItem> m_previousHistoryItem; + RefPtr<HistoryItem> m_provisionalHistoryItem; + + bool m_didPerformFirstNavigation; + +#ifndef NDEBUG + bool m_didDispatchDidCommitLoad; +#endif + +#if USE(LOW_BANDWIDTH_DISPLAY) + // whether to use low bandwidth dislay, set by client + bool m_useLowBandwidthDisplay; + + // whether to call finishParsing() in switchOutLowBandwidthDisplayIfReady() + bool m_finishedParsingDuringLowBandwidthDisplay; + + // whether to call switchOutLowBandwidthDisplayIfReady; + // true if there is external css, javascript, or subframe/plugin + bool m_needToSwitchOutLowBandwidthDisplay; + + String m_pendingSourceInLowBandwidthDisplay; + HashSet<CachedResource*> m_externalRequestsInLowBandwidthDisplay; +#endif + }; + +} + +#endif diff --git a/WebCore/loader/FrameLoaderClient.h b/WebCore/loader/FrameLoaderClient.h new file mode 100644 index 0000000..6e2aba9 --- /dev/null +++ b/WebCore/loader/FrameLoaderClient.h @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 FrameLoaderClient_h +#define FrameLoaderClient_h + +#include "FrameLoaderTypes.h" +#include <wtf/Forward.h> +#include <wtf/Platform.h> +#include <wtf/Vector.h> + +typedef class _jobject* jobject; + +#if PLATFORM(MAC) && !defined(__OBJC__) +class NSCachedURLResponse; +class NSView; +#endif + +namespace WebCore { + + class AuthenticationChallenge; + class CachedPage; + class DocumentLoader; + class Element; + class FormState; + class Frame; + class FrameLoader; + class HistoryItem; + class HTMLFrameOwnerElement; + class IntSize; + class KURL; + class NavigationAction; + class ResourceError; + class ResourceHandle; + class ResourceLoader; + class ResourceResponse; + class SharedBuffer; + class SubstituteData; + class String; + class Widget; + + class ResourceRequest; + +#ifdef ANDROID_HISTORY_CLIENT + class BackForwardList; +#endif + + typedef void (FrameLoader::*FramePolicyFunction)(PolicyAction); + + class FrameLoaderClient { + public: + virtual ~FrameLoaderClient() { } + virtual void frameLoaderDestroyed() = 0; + + virtual bool hasWebView() const = 0; // mainly for assertions + + virtual bool hasHTMLView() const { return true; } + + virtual void makeRepresentation(DocumentLoader*) = 0; + virtual void forceLayout() = 0; + virtual void forceLayoutForNonHTML() = 0; + + virtual void setCopiesOnScroll() = 0; + + virtual void detachedFromParent2() = 0; + virtual void detachedFromParent3() = 0; + + virtual void assignIdentifierToInitialRequest(unsigned long identifier, DocumentLoader*, const ResourceRequest&) = 0; + + virtual void dispatchWillSendRequest(DocumentLoader*, unsigned long identifier, ResourceRequest&, const ResourceResponse& redirectResponse) = 0; + virtual void dispatchDidReceiveAuthenticationChallenge(DocumentLoader*, unsigned long identifier, const AuthenticationChallenge&) = 0; + virtual void dispatchDidCancelAuthenticationChallenge(DocumentLoader*, unsigned long identifier, const AuthenticationChallenge&) = 0; + virtual void dispatchDidReceiveResponse(DocumentLoader*, unsigned long identifier, const ResourceResponse&) = 0; + virtual void dispatchDidReceiveContentLength(DocumentLoader*, unsigned long identifier, int lengthReceived) = 0; + virtual void dispatchDidFinishLoading(DocumentLoader*, unsigned long identifier) = 0; + virtual void dispatchDidFailLoading(DocumentLoader*, unsigned long identifier, const ResourceError&) = 0; + virtual bool dispatchDidLoadResourceFromMemoryCache(DocumentLoader*, const ResourceRequest&, const ResourceResponse&, int length) = 0; + + virtual void dispatchDidHandleOnloadEvents() = 0; + virtual void dispatchDidReceiveServerRedirectForProvisionalLoad() = 0; + virtual void dispatchDidCancelClientRedirect() = 0; + virtual void dispatchWillPerformClientRedirect(const KURL&, double interval, double fireDate) = 0; + virtual void dispatchDidChangeLocationWithinPage() = 0; + virtual void dispatchWillClose() = 0; + virtual void dispatchDidReceiveIcon() = 0; + virtual void dispatchDidStartProvisionalLoad() = 0; + virtual void dispatchDidReceiveTitle(const String& title) = 0; + virtual void dispatchDidCommitLoad() = 0; + virtual void dispatchDidFailProvisionalLoad(const ResourceError&) = 0; + virtual void dispatchDidFailLoad(const ResourceError&) = 0; + virtual void dispatchDidFinishDocumentLoad() = 0; + virtual void dispatchDidFinishLoad() = 0; + virtual void dispatchDidFirstLayout() = 0; + + virtual Frame* dispatchCreatePage() = 0; + virtual void dispatchShow() = 0; + + virtual void dispatchDecidePolicyForMIMEType(FramePolicyFunction, const String& MIMEType, const ResourceRequest&) = 0; + virtual void dispatchDecidePolicyForNewWindowAction(FramePolicyFunction, const NavigationAction&, const ResourceRequest&, PassRefPtr<FormState>, const String& frameName) = 0; + virtual void dispatchDecidePolicyForNavigationAction(FramePolicyFunction, const NavigationAction&, const ResourceRequest&, PassRefPtr<FormState>) = 0; + virtual void cancelPolicyCheck() = 0; + + virtual void dispatchUnableToImplementPolicy(const ResourceError&) = 0; + + virtual void dispatchWillSubmitForm(FramePolicyFunction, PassRefPtr<FormState>) = 0; + + virtual void dispatchDidLoadMainResource(DocumentLoader*) = 0; + virtual void revertToProvisionalState(DocumentLoader*) = 0; + virtual void setMainDocumentError(DocumentLoader*, const ResourceError&) = 0; + + // Maybe these should go into a ProgressTrackerClient some day + virtual void willChangeEstimatedProgress() { } + virtual void didChangeEstimatedProgress() { } + virtual void postProgressStartedNotification() = 0; + virtual void postProgressEstimateChangedNotification() = 0; + virtual void postProgressFinishedNotification() = 0; + + virtual void setMainFrameDocumentReady(bool) = 0; + + virtual void startDownload(const ResourceRequest&) = 0; + + virtual void willChangeTitle(DocumentLoader*) = 0; + virtual void didChangeTitle(DocumentLoader*) = 0; + + virtual void committedLoad(DocumentLoader*, const char*, int) = 0; + virtual void finishedLoading(DocumentLoader*) = 0; + + virtual void updateGlobalHistory(const KURL&) = 0; + virtual bool shouldGoToHistoryItem(HistoryItem*) const = 0; +#ifdef ANDROID_HISTORY_CLIENT + virtual void dispatchDidAddHistoryItem(HistoryItem*) const = 0; + virtual void dispatchDidRemoveHistoryItem(HistoryItem*, int) const = 0; + virtual void dispatchDidChangeHistoryIndex(BackForwardList*) const = 0; +#endif + + virtual ResourceError cancelledError(const ResourceRequest&) = 0; + virtual ResourceError blockedError(const ResourceRequest&) = 0; + virtual ResourceError cannotShowURLError(const ResourceRequest&) = 0; + virtual ResourceError interruptForPolicyChangeError(const ResourceRequest&) = 0; + + virtual ResourceError cannotShowMIMETypeError(const ResourceResponse&) = 0; + virtual ResourceError fileDoesNotExistError(const ResourceResponse&) = 0; + virtual ResourceError pluginWillHandleLoadError(const ResourceResponse&) = 0; + + virtual bool shouldFallBack(const ResourceError&) = 0; + + virtual bool canHandleRequest(const ResourceRequest&) const = 0; + virtual bool canShowMIMEType(const String& MIMEType) const = 0; + virtual bool representationExistsForURLScheme(const String& URLScheme) const = 0; + virtual String generatedMIMETypeForURLScheme(const String& URLScheme) const = 0; + + virtual void frameLoadCompleted() = 0; + virtual void saveViewStateToItem(HistoryItem*) = 0; + virtual void restoreViewState() = 0; + virtual void provisionalLoadStarted() = 0; + virtual void didFinishLoad() = 0; + virtual void prepareForDataSourceReplacement() = 0; + + virtual PassRefPtr<DocumentLoader> createDocumentLoader(const ResourceRequest&, const SubstituteData&) = 0; + virtual void setTitle(const String& title, const KURL&) = 0; + + virtual String userAgent(const KURL&) = 0; + + virtual void savePlatformDataToCachedPage(CachedPage*) = 0; + virtual void transitionToCommittedFromCachedPage(CachedPage*) = 0; + virtual void transitionToCommittedForNewPage() = 0; + + virtual bool canCachePage() const = 0; + virtual void download(ResourceHandle*, const ResourceRequest&, const ResourceRequest&, const ResourceResponse&) = 0; + + virtual PassRefPtr<Frame> createFrame(const KURL& url, const String& name, HTMLFrameOwnerElement* ownerElement, + const String& referrer, bool allowsScrolling, int marginWidth, int marginHeight) = 0; + virtual Widget* createPlugin(const IntSize&, Element*, const KURL&, const Vector<String>&, const Vector<String>&, const String&, bool loadManually) = 0; + virtual void redirectDataToPlugin(Widget* pluginWidget) = 0; + + virtual Widget* createJavaAppletWidget(const IntSize&, Element*, const KURL& baseURL, const Vector<String>& paramNames, const Vector<String>& paramValues) = 0; + + virtual ObjectContentType objectContentType(const KURL& url, const String& mimeType) = 0; + virtual String overrideMediaType() const = 0; + + virtual void windowObjectCleared() = 0; + virtual void didPerformFirstNavigation() const = 0; // "Navigation" here means a transition from one page to another that ends up in the back/forward list. + + virtual void registerForIconNotification(bool listen = true) = 0; + +#if PLATFORM(MAC) +#if ENABLE(MAC_JAVA_BRIDGE) + virtual jobject javaApplet(NSView*) { return 0; } +#endif + virtual NSCachedURLResponse* willCacheResponse(DocumentLoader*, unsigned long identifier, NSCachedURLResponse*) const = 0; +#endif + }; + +} // namespace WebCore + +#endif // FrameLoaderClient_h diff --git a/WebCore/loader/FrameLoaderTypes.h b/WebCore/loader/FrameLoaderTypes.h new file mode 100644 index 0000000..86d8a5b --- /dev/null +++ b/WebCore/loader/FrameLoaderTypes.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 FrameLoaderTypes_h +#define FrameLoaderTypes_h + +namespace WebCore { + + enum FrameState { + FrameStateProvisional, + // This state indicates we are ready to commit to a page, + // which means the view will transition to use the new data source. + FrameStateCommittedPage, + FrameStateComplete + }; + + enum PolicyAction { + PolicyUse, + PolicyDownload, + PolicyIgnore, + }; + + enum FrameLoadType { + FrameLoadTypeStandard, + FrameLoadTypeBack, + FrameLoadTypeForward, + FrameLoadTypeIndexedBackForward, // a multi-item hop in the backforward list + FrameLoadTypeReload, + FrameLoadTypeReloadAllowingStaleData, + FrameLoadTypeSame, // user loads same URL again (but not reload button) + FrameLoadTypeRedirectWithLockedHistory, + FrameLoadTypeReplace + }; + + enum NavigationType { + NavigationTypeLinkClicked, + NavigationTypeFormSubmitted, + NavigationTypeBackForward, + NavigationTypeReload, + NavigationTypeFormResubmitted, + NavigationTypeOther + }; + + enum ObjectContentType { + ObjectContentNone, + ObjectContentImage, + ObjectContentFrame, + ObjectContentNetscapePlugin, + ObjectContentOtherPlugin + }; +} + +#endif diff --git a/WebCore/loader/ImageDocument.cpp b/WebCore/loader/ImageDocument.cpp new file mode 100644 index 0000000..39b1db6 --- /dev/null +++ b/WebCore/loader/ImageDocument.cpp @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * 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. + */ + +#include "config.h" +#include "ImageDocument.h" + +#include "CachedImage.h" +#include "DocumentLoader.h" +#include "Element.h" +#include "EventListener.h" +#include "EventNames.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameView.h" +#include "HTMLImageElement.h" +#include "HTMLNames.h" +#include "LocalizedStrings.h" +#include "MouseEvent.h" +#include "Page.h" +#include "SegmentedString.h" +#include "Settings.h" +#include "Text.h" +#include "XMLTokenizer.h" + +using std::min; + +namespace WebCore { + +using namespace HTMLNames; + +class ImageEventListener : public EventListener { +public: + static PassRefPtr<ImageEventListener> create(ImageDocument* document) { return adoptRef(new ImageEventListener(document)); } + virtual void handleEvent(Event*, bool isWindowEvent); + +private: + ImageEventListener(ImageDocument* document) : m_doc(document) { } + ImageDocument* m_doc; +}; + +class ImageTokenizer : public Tokenizer { +public: + ImageTokenizer(ImageDocument* doc) : m_doc(doc) {} + + virtual bool write(const SegmentedString&, bool appendData); + virtual void finish(); + virtual bool isWaitingForScripts() const; + + virtual bool wantsRawData() const { return true; } + virtual bool writeRawData(const char* data, int len); + +private: + ImageDocument* m_doc; +}; + +class ImageDocumentElement : public HTMLImageElement { +public: + ImageDocumentElement(ImageDocument* doc) : HTMLImageElement(doc), m_imageDocument(doc) { } + virtual ~ImageDocumentElement(); + virtual void willMoveToNewOwnerDocument(); + +private: + ImageDocument* m_imageDocument; +}; + +// -------- + +bool ImageTokenizer::write(const SegmentedString& s, bool appendData) +{ + ASSERT_NOT_REACHED(); + return false; +} + +bool ImageTokenizer::writeRawData(const char* data, int len) +{ + CachedImage* cachedImage = m_doc->cachedImage(); + cachedImage->data(m_doc->frame()->loader()->documentLoader()->mainResourceData(), false); + + m_doc->imageChanged(); + + return false; +} + +void ImageTokenizer::finish() +{ + if (!m_parserStopped && m_doc->imageElement()) { + CachedImage* cachedImage = m_doc->cachedImage(); + RefPtr<SharedBuffer> data = m_doc->frame()->loader()->documentLoader()->mainResourceData(); + + // If this is a multipart image, make a copy of the current part, since the resource data + // will be overwritten by the next part. + if (m_doc->frame()->loader()->documentLoader()->isLoadingMultipartContent()) + data = data->copy(); + + cachedImage->data(data.release(), true); + cachedImage->finish(); + + cachedImage->setResponse(m_doc->frame()->loader()->documentLoader()->response()); + + IntSize size = cachedImage->imageSize(m_doc->frame()->pageZoomFactor()); + if (size.width()) { + // Compute the title, we use the filename of the resource, falling + // back on the hostname if there is no path. + String fileName = m_doc->url().lastPathComponent(); + if (fileName.isEmpty()) + fileName = m_doc->url().host(); + m_doc->setTitle(imageTitle(fileName, size)); + } + + m_doc->imageChanged(); + } + + m_doc->finishedParsing(); +} + +bool ImageTokenizer::isWaitingForScripts() const +{ + // An image document is never waiting for scripts + return false; +} + +// -------- + +ImageDocument::ImageDocument(Frame* frame) + : HTMLDocument(frame) + , m_imageElement(0) + , m_imageSizeIsKnown(false) + , m_didShrinkImage(false) + , m_shouldShrinkImage(shouldShrinkToFit()) +{ + setParseMode(Compat); +} + +Tokenizer* ImageDocument::createTokenizer() +{ + return new ImageTokenizer(this); +} + +void ImageDocument::createDocumentStructure() +{ + ExceptionCode ec; + + RefPtr<Element> rootElement = createElementNS(xhtmlNamespaceURI, "html", ec); + appendChild(rootElement, ec); + + RefPtr<Element> body = createElementNS(xhtmlNamespaceURI, "body", ec); + body->setAttribute(styleAttr, "margin: 0px;"); + + rootElement->appendChild(body, ec); + + RefPtr<ImageDocumentElement> imageElement = new ImageDocumentElement(this); + + imageElement->setAttribute(styleAttr, "-webkit-user-select: none"); + imageElement->setLoadManually(true); + imageElement->setSrc(url().string()); + + body->appendChild(imageElement, ec); + + if (shouldShrinkToFit()) { + // Add event listeners + RefPtr<EventListener> listener = ImageEventListener::create(this); + addWindowEventListener("resize", listener, false); + imageElement->addEventListener("click", listener.release(), false); + } + + m_imageElement = imageElement.get(); +} + +float ImageDocument::scale() const +{ + if (!m_imageElement) + return 1.0f; + + IntSize imageSize = m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor()); + IntSize windowSize = IntSize(frame()->view()->width(), frame()->view()->height()); + + float widthScale = (float)windowSize.width() / imageSize.width(); + float heightScale = (float)windowSize.height() / imageSize.height(); + + return min(widthScale, heightScale); +} + +void ImageDocument::resizeImageToFit() +{ + if (!m_imageElement) + return; + + IntSize imageSize = m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor()); + + float scale = this->scale(); + m_imageElement->setWidth(static_cast<int>(imageSize.width() * scale)); + m_imageElement->setHeight(static_cast<int>(imageSize.height() * scale)); + + ExceptionCode ec; + m_imageElement->style()->setProperty("cursor", "-webkit-zoom-in", ec); +} + +void ImageDocument::imageClicked(int x, int y) +{ + if (!m_imageSizeIsKnown || imageFitsInWindow()) + return; + + m_shouldShrinkImage = !m_shouldShrinkImage; + + if (m_shouldShrinkImage) + windowSizeChanged(); + else { + restoreImageSize(); + + updateLayout(); + + float scale = this->scale(); + + int scrollX = static_cast<int>(x / scale - (float)frame()->view()->width() / 2); + int scrollY = static_cast<int>(y / scale - (float)frame()->view()->height() / 2); + + frame()->view()->setScrollPosition(IntPoint(scrollX, scrollY)); + } +} + +void ImageDocument::imageChanged() +{ + ASSERT(m_imageElement); + + if (m_imageSizeIsKnown) + return; + + if (m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor()).isEmpty()) + return; + + m_imageSizeIsKnown = true; + + if (shouldShrinkToFit()) { + // Force resizing of the image + windowSizeChanged(); + } +} + +void ImageDocument::restoreImageSize() +{ + if (!m_imageElement || !m_imageSizeIsKnown) + return; + + m_imageElement->setWidth(m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor()).width()); + m_imageElement->setHeight(m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor()).height()); + + ExceptionCode ec; + if (imageFitsInWindow()) + m_imageElement->style()->removeProperty("cursor", ec); + else + m_imageElement->style()->setProperty("cursor", "-webkit-zoom-out", ec); + + m_didShrinkImage = false; +} + +bool ImageDocument::imageFitsInWindow() const +{ + if (!m_imageElement) + return true; + + IntSize imageSize = m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor()); + IntSize windowSize = IntSize(frame()->view()->width(), frame()->view()->height()); + + return imageSize.width() <= windowSize.width() && imageSize.height() <= windowSize.height(); +} + +void ImageDocument::windowSizeChanged() +{ + if (!m_imageElement || !m_imageSizeIsKnown) + return; + + bool fitsInWindow = imageFitsInWindow(); + + // If the image has been explicitly zoomed in, restore the cursor if the image fits + // and set it to a zoom out cursor if the image doesn't fit + if (!m_shouldShrinkImage) { + ExceptionCode ec; + + if (fitsInWindow) + m_imageElement->style()->removeProperty("cursor", ec); + else + m_imageElement->style()->setProperty("cursor", "-webkit-zoom-out", ec); + return; + } + + if (m_didShrinkImage) { + // If the window has been resized so that the image fits, restore the image size + // otherwise update the restored image size. + if (fitsInWindow) + restoreImageSize(); + else + resizeImageToFit(); + } else { + // If the image isn't resized but needs to be, then resize it. + if (!fitsInWindow) { + resizeImageToFit(); + m_didShrinkImage = true; + } + } +} + +CachedImage* ImageDocument::cachedImage() +{ + if (!m_imageElement) + createDocumentStructure(); + + return m_imageElement->cachedImage(); +} + +bool ImageDocument::shouldShrinkToFit() const +{ + return frame()->page()->settings()->shrinksStandaloneImagesToFit() && + frame()->page()->mainFrame() == frame(); +} + +// -------- + +void ImageEventListener::handleEvent(Event* event, bool isWindowEvent) +{ + if (event->type() == eventNames().resizeEvent) + m_doc->windowSizeChanged(); + else if (event->type() == eventNames().clickEvent) { + MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); + m_doc->imageClicked(mouseEvent->x(), mouseEvent->y()); + } +} + +// -------- + +ImageDocumentElement::~ImageDocumentElement() +{ + if (m_imageDocument) + m_imageDocument->disconnectImageElement(); +} + +void ImageDocumentElement::willMoveToNewOwnerDocument() +{ + if (m_imageDocument) { + m_imageDocument->disconnectImageElement(); + m_imageDocument = 0; + } + HTMLImageElement::willMoveToNewOwnerDocument(); +} + +} diff --git a/WebCore/loader/ImageDocument.h b/WebCore/loader/ImageDocument.h new file mode 100644 index 0000000..1bc5245 --- /dev/null +++ b/WebCore/loader/ImageDocument.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * 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 ImageDocument_h +#define ImageDocument_h + +#include "HTMLDocument.h" + +namespace WebCore { + +class ImageDocumentElement; + +class ImageDocument : public HTMLDocument { +public: + static PassRefPtr<ImageDocument> create(Frame* frame) + { + return new ImageDocument(frame); + } + + CachedImage* cachedImage(); + ImageDocumentElement* imageElement() const { return m_imageElement; } + void disconnectImageElement() { m_imageElement = 0; } + + void windowSizeChanged(); + void imageChanged(); + void imageClicked(int x, int y); + +private: + ImageDocument(Frame*); + + virtual Tokenizer* createTokenizer(); + virtual bool isImageDocument() const { return true; } + + void createDocumentStructure(); + void resizeImageToFit(); + void restoreImageSize(); + bool imageFitsInWindow() const; + bool shouldShrinkToFit() const; + float scale() const; + + ImageDocumentElement* m_imageElement; + + // Whether enough of the image has been loaded to determine its size + bool m_imageSizeIsKnown; + + // Whether the image is shrunk to fit or not + bool m_didShrinkImage; + + // Whether the image should be shrunk or not + bool m_shouldShrinkImage; +}; + +} + +#endif // ImageDocument_h diff --git a/WebCore/loader/ImageLoader.cpp b/WebCore/loader/ImageLoader.cpp new file mode 100644 index 0000000..da159b4 --- /dev/null +++ b/WebCore/loader/ImageLoader.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "ImageLoader.h" + +#include "CSSHelper.h" +#include "CachedImage.h" +#include "DocLoader.h" +#include "Document.h" +#include "Element.h" +#include "RenderImage.h" + +namespace WebCore { + +ImageLoader::ImageLoader(Element* elt) + : m_element(elt) + , m_image(0) + , m_firedLoad(true) + , m_imageComplete(true) + , m_loadManually(false) +{ +} + +ImageLoader::~ImageLoader() +{ + if (m_image) + m_image->removeClient(this); + m_element->document()->removeImage(this); +} + +void ImageLoader::setImage(CachedImage* newImage) +{ + CachedImage* oldImage = m_image.get(); + if (newImage != oldImage) { + setLoadingImage(newImage); + m_firedLoad = true; + m_imageComplete = true; + if (newImage) + newImage->addClient(this); + if (oldImage) + oldImage->removeClient(this); + } + + if (RenderObject* renderer = element()->renderer()) { + if (!renderer->isImage()) + return; + + static_cast<RenderImage*>(renderer)->resetAnimation(); + } +} + +void ImageLoader::setLoadingImage(CachedImage* loadingImage) +{ + m_firedLoad = false; + m_imageComplete = false; + m_image = loadingImage; +} + +void ImageLoader::updateFromElement() +{ + // If we're not making renderers for the page, then don't load images. We don't want to slow + // down the raw HTML parsing case by loading images we don't intend to display. + Element* elem = element(); + Document* doc = elem->document(); + if (!doc->renderer()) + return; + + AtomicString attr = elem->getAttribute(elem->imageSourceAttributeName()); + + // Do not load any image if the 'src' attribute is missing or if it is + // an empty string referring to a local file. The latter condition is + // a quirk that preserves old behavior that Dashboard widgets + // need (<rdar://problem/5994621>). + CachedImage* newImage = 0; + if (!(attr.isNull() || attr.isEmpty() && doc->baseURI().isLocalFile())) { + if (m_loadManually) { + doc->docLoader()->setAutoLoadImages(false); + newImage = new CachedImage(sourceURI(attr)); + newImage->setLoading(true); + newImage->setDocLoader(doc->docLoader()); + doc->docLoader()->m_docResources.set(newImage->url(), newImage); + } else + newImage = doc->docLoader()->requestImage(sourceURI(attr)); + } + + CachedImage* oldImage = m_image.get(); + if (newImage != oldImage) { + setLoadingImage(newImage); + if (newImage) + newImage->addClient(this); + if (oldImage) + oldImage->removeClient(this); + } + + if (RenderObject* renderer = elem->renderer()) { + if (!renderer->isImage()) + return; + + static_cast<RenderImage*>(renderer)->resetAnimation(); + } +} + +void ImageLoader::notifyFinished(CachedResource *image) +{ + m_imageComplete = true; + + Element* elem = element(); + elem->document()->dispatchImageLoadEventSoon(this); + + if (RenderObject* renderer = elem->renderer()) { + if (!renderer->isImage()) + return; + + static_cast<RenderImage*>(renderer)->setCachedImage(m_image.get()); + } +} + +} diff --git a/WebCore/loader/ImageLoader.h b/WebCore/loader/ImageLoader.h new file mode 100644 index 0000000..39b5cc4 --- /dev/null +++ b/WebCore/loader/ImageLoader.h @@ -0,0 +1,73 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004 Apple Computer, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#ifndef ImageLoader_h +#define ImageLoader_h + +#include "CachedResourceClient.h" +#include "CachedResourceHandle.h" + +namespace WebCore { + +class AtomicString; +class Element; + +class ImageLoader : public CachedResourceClient { +public: + ImageLoader(Element*); + virtual ~ImageLoader(); + + void updateFromElement(); + + virtual void dispatchLoadEvent() = 0; + virtual String sourceURI(const AtomicString&) const = 0; + + Element* element() const { return m_element; } + bool imageComplete() const { return m_imageComplete; } + + CachedImage* image() const { return m_image.get(); } + void setImage(CachedImage*); + + void setLoadManually(bool loadManually) { m_loadManually = loadManually; } + + // CachedResourceClient API + virtual void notifyFinished(CachedResource*); + + bool haveFiredLoadEvent() const { return m_firedLoad; } +protected: + void setLoadingImage(CachedImage*); + + void setHaveFiredLoadEvent(bool firedLoad) { m_firedLoad = firedLoad; } + +private: + Element* m_element; + CachedResourceHandle<CachedImage> m_image; + bool m_firedLoad : 1; + bool m_imageComplete : 1; + bool m_loadManually : 1; +}; + +} + +#endif diff --git a/WebCore/loader/MainResourceLoader.cpp b/WebCore/loader/MainResourceLoader.cpp new file mode 100644 index 0000000..079bd79 --- /dev/null +++ b/WebCore/loader/MainResourceLoader.cpp @@ -0,0 +1,496 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "MainResourceLoader.h" + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) +#include "ApplicationCache.h" +#include "ApplicationCacheGroup.h" +#include "ApplicationCacheResource.h" +#endif +#include "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "HTMLFormElement.h" +#include "Page.h" +#include "ResourceError.h" +#include "ResourceHandle.h" +#include "Settings.h" + +// FIXME: More that is in common with SubresourceLoader should move up into ResourceLoader. + +namespace WebCore { + +MainResourceLoader::MainResourceLoader(Frame* frame) + : ResourceLoader(frame, true, true) + , m_dataLoadTimer(this, &MainResourceLoader::handleDataLoadNow) + , m_loadingMultipartContent(false) + , m_waitingForContentPolicy(false) +{ +} + +MainResourceLoader::~MainResourceLoader() +{ +} + +PassRefPtr<MainResourceLoader> MainResourceLoader::create(Frame* frame) +{ + return adoptRef(new MainResourceLoader(frame)); +} + +void MainResourceLoader::receivedError(const ResourceError& error) +{ + // Calling receivedMainResourceError will likely result in the last reference to this object to go away. + RefPtr<MainResourceLoader> protect(this); + RefPtr<Frame> protectFrame(m_frame); + + // It is important that we call FrameLoader::receivedMainResourceError before calling + // FrameLoader::didFailToLoad because receivedMainResourceError clears out the relevant + // document loaders. Also, receivedMainResourceError ends up calling a FrameLoadDelegate method + // and didFailToLoad calls a ResourceLoadDelegate method and they need to be in the correct order. + frameLoader()->receivedMainResourceError(error, true); + + if (!cancelled()) { + ASSERT(!reachedTerminalState()); + frameLoader()->didFailToLoad(this, error); + + releaseResources(); + } + + ASSERT(reachedTerminalState()); +} + +void MainResourceLoader::didCancel(const ResourceError& error) +{ + m_dataLoadTimer.stop(); + + // Calling receivedMainResourceError will likely result in the last reference to this object to go away. + RefPtr<MainResourceLoader> protect(this); + + if (m_waitingForContentPolicy) { + frameLoader()->cancelContentPolicyCheck(); + ASSERT(m_waitingForContentPolicy); + m_waitingForContentPolicy = false; + deref(); // balances ref in didReceiveResponse + } + frameLoader()->receivedMainResourceError(error, true); + ResourceLoader::didCancel(error); +} + +ResourceError MainResourceLoader::interruptionForPolicyChangeError() const +{ + return frameLoader()->interruptionForPolicyChangeError(request()); +} + +void MainResourceLoader::stopLoadingForPolicyChange() +{ + cancel(interruptionForPolicyChangeError()); +} + +void MainResourceLoader::callContinueAfterNavigationPolicy(void* argument, const ResourceRequest& request, PassRefPtr<FormState>, bool shouldContinue) +{ + static_cast<MainResourceLoader*>(argument)->continueAfterNavigationPolicy(request, shouldContinue); +} + +void MainResourceLoader::continueAfterNavigationPolicy(const ResourceRequest& request, bool shouldContinue) +{ + if (!shouldContinue) + stopLoadingForPolicyChange(); + deref(); // balances ref in willSendRequest +} + +bool MainResourceLoader::isPostOrRedirectAfterPost(const ResourceRequest& newRequest, const ResourceResponse& redirectResponse) +{ + if (newRequest.httpMethod() == "POST") + return true; + + int status = redirectResponse.httpStatusCode(); + if (((status >= 301 && status <= 303) || status == 307) + && frameLoader()->initialRequest().httpMethod() == "POST") + return true; + + return false; +} + +void MainResourceLoader::addData(const char* data, int length, bool allAtOnce) +{ + ResourceLoader::addData(data, length, allAtOnce); + frameLoader()->receivedData(data, length); +} + +void MainResourceLoader::willSendRequest(ResourceRequest& newRequest, const ResourceResponse& redirectResponse) +{ + // Note that there are no asserts here as there are for the other callbacks. This is due to the + // fact that this "callback" is sent when starting every load, and the state of callback + // deferrals plays less of a part in this function in preventing the bad behavior deferring + // callbacks is meant to prevent. + ASSERT(!newRequest.isNull()); + + // The additional processing can do anything including possibly removing the last + // reference to this object; one example of this is 3266216. + RefPtr<MainResourceLoader> protect(this); + + // Update cookie policy base URL as URL changes, except for subframes, which use the + // URL of the main frame which doesn't change when we redirect. + if (frameLoader()->isLoadingMainFrame()) + newRequest.setMainDocumentURL(newRequest.url()); + + // If we're fielding a redirect in response to a POST, force a load from origin, since + // this is a common site technique to return to a page viewing some data that the POST + // just modified. + // Also, POST requests always load from origin, but this does not affect subresources. + if (newRequest.cachePolicy() == UseProtocolCachePolicy && isPostOrRedirectAfterPost(newRequest, redirectResponse)) + newRequest.setCachePolicy(ReloadIgnoringCacheData); + + ResourceLoader::willSendRequest(newRequest, redirectResponse); + + // Don't set this on the first request. It is set when the main load was started. + m_documentLoader->setRequest(newRequest); + + // FIXME: Ideally we'd stop the I/O until we hear back from the navigation policy delegate + // listener. But there's no way to do that in practice. So instead we cancel later if the + // listener tells us to. In practice that means the navigation policy needs to be decided + // synchronously for these redirect cases. + + ref(); // balanced by deref in continueAfterNavigationPolicy + frameLoader()->checkNavigationPolicy(newRequest, callContinueAfterNavigationPolicy, this); +} + +static bool shouldLoadAsEmptyDocument(const KURL& url) +{ + return url.isEmpty() || equalIgnoringCase(String(url.protocol()), "about"); +} + +void MainResourceLoader::continueAfterContentPolicy(PolicyAction contentPolicy, const ResourceResponse& r) +{ + KURL url = request().url(); + const String& mimeType = r.mimeType(); + + switch (contentPolicy) { + case PolicyUse: { + // Prevent remote web archives from loading because they can claim to be from any domain and thus avoid cross-domain security checks (4120255). + bool isRemoteWebArchive = equalIgnoringCase("application/x-webarchive", mimeType) && !m_substituteData.isValid() && !url.isLocalFile(); + if (!frameLoader()->canShowMIMEType(mimeType) || isRemoteWebArchive) { + frameLoader()->cannotShowMIMEType(r); + // Check reachedTerminalState since the load may have already been cancelled inside of _handleUnimplementablePolicyWithErrorCode::. + if (!reachedTerminalState()) + stopLoadingForPolicyChange(); + return; + } + break; + } + + case PolicyDownload: + frameLoader()->client()->download(m_handle.get(), request(), m_handle.get()->request(), r); + // It might have gone missing + if (frameLoader()) + receivedError(interruptionForPolicyChangeError()); + return; + + case PolicyIgnore: + stopLoadingForPolicyChange(); + return; + + default: + ASSERT_NOT_REACHED(); + } + + RefPtr<MainResourceLoader> protect(this); + + if (r.isHTTP()) { + int status = r.httpStatusCode(); + if (status < 200 || status >= 300) { + bool hostedByObject = frameLoader()->isHostedByObjectElement(); + + frameLoader()->handleFallbackContent(); + // object elements are no longer rendered after we fallback, so don't + // keep trying to process data from their load + + if (hostedByObject) + cancel(); + } + } + + // we may have cancelled this load as part of switching to fallback content + if (!reachedTerminalState()) + ResourceLoader::didReceiveResponse(r); + + if (frameLoader() && !frameLoader()->isStopping()) + if (m_substituteData.isValid()) { + if (m_substituteData.content()->size()) + didReceiveData(m_substituteData.content()->data(), m_substituteData.content()->size(), m_substituteData.content()->size(), true); + if (frameLoader() && !frameLoader()->isStopping()) + didFinishLoading(); + } else if (shouldLoadAsEmptyDocument(url) || frameLoader()->representationExistsForURLScheme(url.protocol())) + didFinishLoading(); +} + +void MainResourceLoader::callContinueAfterContentPolicy(void* argument, PolicyAction policy) +{ + static_cast<MainResourceLoader*>(argument)->continueAfterContentPolicy(policy); +} + +void MainResourceLoader::continueAfterContentPolicy(PolicyAction policy) +{ + ASSERT(m_waitingForContentPolicy); + m_waitingForContentPolicy = false; + if (frameLoader() && !frameLoader()->isStopping()) + continueAfterContentPolicy(policy, m_response); + deref(); // balances ref in didReceiveResponse +} + +void MainResourceLoader::didReceiveResponse(const ResourceResponse& r) +{ + // There is a bug in CFNetwork where callbacks can be dispatched even when loads are deferred. + // See <rdar://problem/6304600> for more details. +#if !PLATFORM(CF) + ASSERT(shouldLoadAsEmptyDocument(r.url()) || !defersLoading()); +#endif + + if (m_loadingMultipartContent) { + frameLoader()->setupForReplaceByMIMEType(r.mimeType()); + clearResourceData(); + } + + if (r.isMultipart()) + m_loadingMultipartContent = true; + + // The additional processing can do anything including possibly removing the last + // reference to this object; one example of this is 3266216. + RefPtr<MainResourceLoader> protect(this); + + m_documentLoader->setResponse(r); + + m_response = r; + + ASSERT(!m_waitingForContentPolicy); + m_waitingForContentPolicy = true; + ref(); // balanced by deref in continueAfterContentPolicy and didCancel + frameLoader()->checkContentPolicy(m_response.mimeType(), callContinueAfterContentPolicy, this); +} + +void MainResourceLoader::didReceiveData(const char* data, int length, long long lengthReceived, bool allAtOnce) +{ + ASSERT(data); + ASSERT(length != 0); + + // There is a bug in CFNetwork where callbacks can be dispatched even when loads are deferred. + // See <rdar://problem/6304600> for more details. +#if !PLATFORM(CF) + ASSERT(!defersLoading()); +#endif + + // The additional processing can do anything including possibly removing the last + // reference to this object; one example of this is 3266216. + RefPtr<MainResourceLoader> protect(this); + + ResourceLoader::didReceiveData(data, length, lengthReceived, allAtOnce); +} + +void MainResourceLoader::didFinishLoading() +{ + // There is a bug in CFNetwork where callbacks can be dispatched even when loads are deferred. + // See <rdar://problem/6304600> for more details. +#if !PLATFORM(CF) + ASSERT(shouldLoadAsEmptyDocument(frameLoader()->activeDocumentLoader()->url()) || !defersLoading()); +#endif + + // The additional processing can do anything including possibly removing the last + // reference to this object. + RefPtr<MainResourceLoader> protect(this); + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + RefPtr<DocumentLoader> dl = documentLoader(); +#endif + + frameLoader()->finishedLoading(); + ResourceLoader::didFinishLoading(); + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + ApplicationCacheGroup* group = dl->candidateApplicationCacheGroup(); + if (!group && dl->applicationCache() && !dl->mainResourceApplicationCache()) + group = dl->applicationCache()->group(); + + if (group) + group->finishedLoadingMainResource(dl.get()); +#endif +} + +void MainResourceLoader::didFail(const ResourceError& error) +{ + // There is a bug in CFNetwork where callbacks can be dispatched even when loads are deferred. + // See <rdar://problem/6304600> for more details. +#if !PLATFORM(CF) + ASSERT(!defersLoading()); +#endif + + receivedError(error); +} + +void MainResourceLoader::handleEmptyLoad(const KURL& url, bool forURLScheme) +{ + String mimeType; + if (forURLScheme) + mimeType = frameLoader()->generatedMIMETypeForURLScheme(url.protocol()); + else + mimeType = "text/html"; + + ResourceResponse response(url, mimeType, 0, String(), String()); + didReceiveResponse(response); +} + +void MainResourceLoader::handleDataLoadNow(Timer<MainResourceLoader>*) +{ + RefPtr<MainResourceLoader> protect(this); + + KURL url = m_substituteData.responseURL(); + if (url.isEmpty()) + url = m_initialRequest.url(); + + ResourceResponse response(url, m_substituteData.mimeType(), m_substituteData.content()->size(), m_substituteData.textEncoding(), ""); + didReceiveResponse(response); +} + +void MainResourceLoader::handleDataLoadSoon(ResourceRequest& r) +{ + m_initialRequest = r; + + if (m_documentLoader->deferMainResourceDataLoad()) + m_dataLoadTimer.startOneShot(0); + else + handleDataLoadNow(0); +} + +bool MainResourceLoader::loadNow(ResourceRequest& r) +{ + bool shouldLoadEmptyBeforeRedirect = shouldLoadAsEmptyDocument(r.url()); + + ASSERT(!m_handle); + ASSERT(shouldLoadEmptyBeforeRedirect || !defersLoading()); + + // Send this synthetic delegate callback since clients expect it, and + // we no longer send the callback from within NSURLConnection for + // initial requests. + willSendRequest(r, ResourceResponse()); + + // <rdar://problem/4801066> + // willSendRequest() is liable to make the call to frameLoader() return NULL, so we need to check that here + if (!frameLoader()) + return false; + + const KURL& url = r.url(); + bool shouldLoadEmpty = shouldLoadAsEmptyDocument(url) && !m_substituteData.isValid(); + + if (shouldLoadEmptyBeforeRedirect && !shouldLoadEmpty && defersLoading()) + return true; + + if (m_substituteData.isValid()) + handleDataLoadSoon(r); + else if (shouldLoadEmpty || frameLoader()->representationExistsForURLScheme(url.protocol())) + handleEmptyLoad(url, !shouldLoadEmpty); + else + m_handle = ResourceHandle::create(r, this, m_frame.get(), false, true, true); + + return false; +} + +bool MainResourceLoader::load(const ResourceRequest& r, const SubstituteData& substituteData) +{ + ASSERT(!m_handle); + + m_substituteData = substituteData; + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + // Check if this request should be loaded from the application cache + if (!m_substituteData.isValid() && frameLoader()->frame()->settings() && frameLoader()->frame()->settings()->offlineWebApplicationCacheEnabled()) { + ASSERT(!m_applicationCache); + + if (Page* page = frameLoader()->frame()->page()) { + if (frameLoader()->frame() == page->mainFrame()) + m_applicationCache = ApplicationCacheGroup::cacheForMainRequest(r, m_documentLoader.get()); + else + m_applicationCache = frameLoader()->documentLoader()->topLevelApplicationCache(); + } + + if (m_applicationCache) { + // Get the resource from the application cache. + // FIXME: If the resource does not exist, the load should fail. + if (ApplicationCacheResource* resource = m_applicationCache->resourceForRequest(r)) { + m_substituteData = SubstituteData(resource->data(), + resource->response().mimeType(), + resource->response().textEncodingName(), KURL()); + } + } + } +#endif + + ResourceRequest request(r); + bool defer = defersLoading(); + if (defer) { + bool shouldLoadEmpty = shouldLoadAsEmptyDocument(r.url()); + if (shouldLoadEmpty) + defer = false; + } + if (!defer) { + if (loadNow(request)) { + // Started as an empty document, but was redirected to something non-empty. + ASSERT(defersLoading()); + defer = true; + } + } + if (defer) + m_initialRequest = request; + + return true; +} + +void MainResourceLoader::setDefersLoading(bool defers) +{ + ResourceLoader::setDefersLoading(defers); + + if (defers) { + if (m_dataLoadTimer.isActive()) + m_dataLoadTimer.stop(); + } else { + if (m_initialRequest.isNull()) + return; + + if (m_substituteData.isValid() && + m_documentLoader->deferMainResourceDataLoad()) + m_dataLoadTimer.startOneShot(0); + else { + ResourceRequest r(m_initialRequest); + m_initialRequest = ResourceRequest(); + loadNow(r); + } + } +} + +} diff --git a/WebCore/loader/MainResourceLoader.h b/WebCore/loader/MainResourceLoader.h new file mode 100644 index 0000000..6c69c1f --- /dev/null +++ b/WebCore/loader/MainResourceLoader.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2005, 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "FrameLoaderTypes.h" +#include "ResourceLoader.h" +#include "SubstituteData.h" +#include "Timer.h" +#include <wtf/Forward.h> + +namespace WebCore { + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + class ApplicationCache; +#endif + class FormState; + class ResourceRequest; + + class MainResourceLoader : public ResourceLoader { + public: + static PassRefPtr<MainResourceLoader> create(Frame*); + virtual ~MainResourceLoader(); + + virtual bool load(const ResourceRequest&, const SubstituteData&); + virtual void addData(const char*, int, bool allAtOnce); + + virtual void setDefersLoading(bool); + + virtual void willSendRequest(ResourceRequest&, const ResourceResponse& redirectResponse); + virtual void didReceiveResponse(const ResourceResponse&); + virtual void didReceiveData(const char*, int, long long lengthReceived, bool allAtOnce); + virtual void didFinishLoading(); + virtual void didFail(const ResourceError&); + + void handleDataLoadNow(Timer<MainResourceLoader>*); + + bool isLoadingMultipartContent() const { return m_loadingMultipartContent; } + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + ApplicationCache* applicationCache() const { return m_applicationCache.get(); } +#endif + + private: + MainResourceLoader(Frame*); + + virtual void didCancel(const ResourceError&); + + bool loadNow(ResourceRequest&); + + void handleEmptyLoad(const KURL&, bool forURLScheme); + void handleDataLoadSoon(ResourceRequest& r); + + void handleDataLoad(ResourceRequest&); + + void receivedError(const ResourceError&); + ResourceError interruptionForPolicyChangeError() const; + void stopLoadingForPolicyChange(); + bool isPostOrRedirectAfterPost(const ResourceRequest& newRequest, const ResourceResponse& redirectResponse); + + static void callContinueAfterNavigationPolicy(void*, const ResourceRequest&, PassRefPtr<FormState>, bool shouldContinue); + void continueAfterNavigationPolicy(const ResourceRequest&, bool shouldContinue); + + static void callContinueAfterContentPolicy(void*, PolicyAction); + void continueAfterContentPolicy(PolicyAction); + void continueAfterContentPolicy(PolicyAction, const ResourceResponse&); + + ResourceRequest m_initialRequest; + SubstituteData m_substituteData; + Timer<MainResourceLoader> m_dataLoadTimer; + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + // The application cache that the main resource was loaded from (if any). + RefPtr<ApplicationCache> m_applicationCache; +#endif + + bool m_loadingMultipartContent; + bool m_waitingForContentPolicy; + }; + +} diff --git a/WebCore/loader/MediaDocument.cpp b/WebCore/loader/MediaDocument.cpp new file mode 100644 index 0000000..5689457 --- /dev/null +++ b/WebCore/loader/MediaDocument.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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. + */ + +#include "config.h" + +#if ENABLE(VIDEO) +#include "MediaDocument.h" + +#include "DocumentLoader.h" +#include "Element.h" +#include "Event.h" +#include "EventNames.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "HTMLEmbedElement.h" +#include "HTMLNames.h" +#include "HTMLVideoElement.h" +#include "MainResourceLoader.h" +#include "Page.h" +#include "SegmentedString.h" +#include "Settings.h" +#include "Text.h" +#include "XMLTokenizer.h" + +namespace WebCore { + +using namespace HTMLNames; + +class MediaTokenizer : public Tokenizer { +public: + MediaTokenizer(Document* doc) : m_doc(doc), m_mediaElement(0) {} + + virtual bool write(const SegmentedString&, bool appendData); + virtual void stopParsing(); + virtual void finish(); + virtual bool isWaitingForScripts() const; + + virtual bool wantsRawData() const { return true; } + virtual bool writeRawData(const char* data, int len); + + void createDocumentStructure(); +private: + Document* m_doc; + HTMLMediaElement* m_mediaElement; +}; + +bool MediaTokenizer::write(const SegmentedString& s, bool appendData) +{ + ASSERT_NOT_REACHED(); + return false; +} + +void MediaTokenizer::createDocumentStructure() +{ + ExceptionCode ec; + RefPtr<Element> rootElement = m_doc->createElementNS(xhtmlNamespaceURI, "html", ec); + m_doc->appendChild(rootElement, ec); + + RefPtr<Element> body = m_doc->createElementNS(xhtmlNamespaceURI, "body", ec); + body->setAttribute(styleAttr, "background-color: rgb(38,38,38);"); + + rootElement->appendChild(body, ec); + + RefPtr<Element> mediaElement = m_doc->createElementNS(xhtmlNamespaceURI, "video", ec); + + m_mediaElement = static_cast<HTMLVideoElement*>(mediaElement.get()); + m_mediaElement->setAttribute(controlsAttr, ""); + m_mediaElement->setAttribute(autoplayAttr, ""); + m_mediaElement->setAttribute(styleAttr, "margin: auto; position: absolute; top: 0; right: 0; bottom: 0; left: 0;"); + + m_mediaElement->setAttribute(nameAttr, "media"); + m_mediaElement->setSrc(m_doc->url()); + + body->appendChild(mediaElement, ec); + + Frame* frame = m_doc->frame(); + if (!frame) + return; + + frame->loader()->activeDocumentLoader()->mainResourceLoader()->setShouldBufferData(false); +} + +bool MediaTokenizer::writeRawData(const char* data, int len) +{ + ASSERT(!m_mediaElement); + if (m_mediaElement) + return false; + + createDocumentStructure(); + finish(); + return false; +} + +void MediaTokenizer::stopParsing() +{ + Tokenizer::stopParsing(); +} + +void MediaTokenizer::finish() +{ + if (!m_parserStopped) + m_doc->finishedParsing(); +} + +bool MediaTokenizer::isWaitingForScripts() const +{ + // A media document is never waiting for scripts + return false; +} + +MediaDocument::MediaDocument(Frame* frame) + : HTMLDocument(frame) +{ + setParseMode(Compat); +} + +Tokenizer* MediaDocument::createTokenizer() +{ + return new MediaTokenizer(this); +} + +void MediaDocument::defaultEventHandler(Event* event) +{ + // Match the default Quicktime plugin behavior to allow + // clicking and double-clicking to pause and play the media. + EventTargetNode* targetNode = event->target()->toNode(); + if (targetNode && targetNode->hasTagName(videoTag)) { + HTMLVideoElement* video = static_cast<HTMLVideoElement*>(targetNode); + ExceptionCode ec; + if (event->type() == eventNames().clickEvent) { + if (!video->canPlay()) { + video->pause(ec); + event->setDefaultHandled(); + } + } else if (event->type() == eventNames().dblclickEvent) { + if (video->canPlay()) { + video->play(ec); + event->setDefaultHandled(); + } + } + } +} + +} +#endif diff --git a/WebCore/loader/MediaDocument.h b/WebCore/loader/MediaDocument.h new file mode 100644 index 0000000..e5167e4 --- /dev/null +++ b/WebCore/loader/MediaDocument.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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 MediaDocument_h +#define MediaDocument_h + +#if ENABLE(VIDEO) + +#include "HTMLDocument.h" + +namespace WebCore { + +class MediaDocument : public HTMLDocument { +public: + static PassRefPtr<MediaDocument> create(Frame* frame) + { + return new MediaDocument(frame); + } + + virtual void defaultEventHandler(Event*); + +private: + MediaDocument(Frame*); + + virtual bool isMediaDocument() const { return true; } + virtual Tokenizer* createTokenizer(); +}; + +} + +#endif +#endif diff --git a/WebCore/loader/NavigationAction.cpp b/WebCore/loader/NavigationAction.cpp new file mode 100644 index 0000000..6bd65ae --- /dev/null +++ b/WebCore/loader/NavigationAction.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "NavigationAction.h" + +#include "Event.h" +#include "FrameLoader.h" + +namespace WebCore { + +static NavigationType navigationType(FrameLoadType frameLoadType, bool isFormSubmission, bool haveEvent) +{ + if (isFormSubmission) + return NavigationTypeFormSubmitted; + if (haveEvent) + return NavigationTypeLinkClicked; + if (frameLoadType == FrameLoadTypeReload) + return NavigationTypeReload; + if (isBackForwardLoadType(frameLoadType)) + return NavigationTypeBackForward; + return NavigationTypeOther; +} + +NavigationAction::NavigationAction() + : m_type(NavigationTypeOther) +{ +} + +NavigationAction::NavigationAction(const KURL& url, NavigationType type) + : m_URL(url) + , m_type(type) +{ +} + +NavigationAction::NavigationAction(const KURL& url, FrameLoadType frameLoadType, + bool isFormSubmission) + : m_URL(url) + , m_type(navigationType(frameLoadType, isFormSubmission, 0)) +{ +} + +NavigationAction::NavigationAction(const KURL& url, NavigationType type, PassRefPtr<Event> event) + : m_URL(url) + , m_type(type) + , m_event(event) +{ +} + +NavigationAction::NavigationAction(const KURL& url, FrameLoadType frameLoadType, + bool isFormSubmission, PassRefPtr<Event> event) + : m_URL(url) + , m_type(navigationType(frameLoadType, isFormSubmission, event)) + , m_event(event) +{ +} + +} diff --git a/WebCore/loader/NavigationAction.h b/WebCore/loader/NavigationAction.h new file mode 100644 index 0000000..0477c6e --- /dev/null +++ b/WebCore/loader/NavigationAction.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 NavigationAction_h +#define NavigationAction_h + +#include "Event.h" +#include "FrameLoaderTypes.h" +#include "KURL.h" +#include <wtf/Forward.h> + +namespace WebCore { + + class NavigationAction { + public: + NavigationAction(); + NavigationAction(const KURL&, NavigationType); + NavigationAction(const KURL&, FrameLoadType, bool isFormSubmission); + NavigationAction(const KURL&, NavigationType, PassRefPtr<Event>); + NavigationAction(const KURL&, FrameLoadType, bool isFormSubmission, PassRefPtr<Event>); + + bool isEmpty() const { return m_URL.isEmpty(); } + + KURL url() const { return m_URL; } + NavigationType type() const { return m_type; } + const Event* event() const { return m_event.get(); } + + private: + KURL m_URL; + NavigationType m_type; + RefPtr<Event> m_event; + }; + +} + +#endif diff --git a/WebCore/loader/NetscapePlugInStreamLoader.cpp b/WebCore/loader/NetscapePlugInStreamLoader.cpp new file mode 100644 index 0000000..e12ed07 --- /dev/null +++ b/WebCore/loader/NetscapePlugInStreamLoader.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "NetscapePlugInStreamLoader.h" + +#include "FrameLoader.h" +#include "DocumentLoader.h" + +namespace WebCore { + +NetscapePlugInStreamLoader::NetscapePlugInStreamLoader(Frame* frame, NetscapePlugInStreamLoaderClient* client) + : ResourceLoader(frame, true, true) + , m_client(client) +{ +} + +NetscapePlugInStreamLoader::~NetscapePlugInStreamLoader() +{ +} + +PassRefPtr<NetscapePlugInStreamLoader> NetscapePlugInStreamLoader::create(Frame* frame, NetscapePlugInStreamLoaderClient* client) +{ + return adoptRef(new NetscapePlugInStreamLoader(frame, client)); +} + +bool NetscapePlugInStreamLoader::isDone() const +{ + return !m_client; +} + +void NetscapePlugInStreamLoader::releaseResources() +{ + m_client = 0; + ResourceLoader::releaseResources(); +} + +void NetscapePlugInStreamLoader::didReceiveResponse(const ResourceResponse& response) +{ + RefPtr<NetscapePlugInStreamLoader> protect(this); + + m_client->didReceiveResponse(this, response); + + // Don't continue if the stream is cancelled + if (!m_client) + return; + + ResourceLoader::didReceiveResponse(response); + + // Don't continue if the stream is cancelled + if (!m_client) + return; + + if (!response.isHTTP()) + return; + + if (m_client->wantsAllStreams()) + return; + + if (response.httpStatusCode() < 100 || response.httpStatusCode() >= 400) + didCancel(frameLoader()->fileDoesNotExistError(response)); +} + +void NetscapePlugInStreamLoader::didReceiveData(const char* data, int length, long long lengthReceived, bool allAtOnce) +{ + RefPtr<NetscapePlugInStreamLoader> protect(this); + + m_client->didReceiveData(this, data, length); + + ResourceLoader::didReceiveData(data, length, lengthReceived, allAtOnce); +} + +void NetscapePlugInStreamLoader::didFinishLoading() +{ + RefPtr<NetscapePlugInStreamLoader> protect(this); + + m_documentLoader->removePlugInStreamLoader(this); + m_client->didFinishLoading(this); + ResourceLoader::didFinishLoading(); +} + +void NetscapePlugInStreamLoader::didFail(const ResourceError& error) +{ + RefPtr<NetscapePlugInStreamLoader> protect(this); + + m_documentLoader->removePlugInStreamLoader(this); + m_client->didFail(this, error); + ResourceLoader::didFail(error); +} + +void NetscapePlugInStreamLoader::didCancel(const ResourceError& error) +{ + RefPtr<NetscapePlugInStreamLoader> protect(this); + + m_client->didFail(this, error); + + // We need to remove the stream loader after the call to didFail, since didFail can + // spawn a new run loop and if the loader has been removed it won't be deferred when + // the document loader is asked to defer loading. + m_documentLoader->removePlugInStreamLoader(this); + + ResourceLoader::didCancel(error); +} + +} diff --git a/WebCore/loader/NetscapePlugInStreamLoader.h b/WebCore/loader/NetscapePlugInStreamLoader.h new file mode 100644 index 0000000..092c6fc --- /dev/null +++ b/WebCore/loader/NetscapePlugInStreamLoader.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2005, 2006, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "ResourceLoader.h" +#include <wtf/Forward.h> + +namespace WebCore { + + class NetscapePlugInStreamLoader; + + class NetscapePlugInStreamLoaderClient { + public: + virtual void didReceiveResponse(NetscapePlugInStreamLoader*, const ResourceResponse&) = 0; + virtual void didReceiveData(NetscapePlugInStreamLoader*, const char*, int) = 0; + virtual void didFail(NetscapePlugInStreamLoader*, const ResourceError&) = 0; + virtual void didFinishLoading(NetscapePlugInStreamLoader*) { } + virtual bool wantsAllStreams() const { return false; } + + protected: + virtual ~NetscapePlugInStreamLoaderClient() { } + }; + + class NetscapePlugInStreamLoader : public ResourceLoader { + public: + static PassRefPtr<NetscapePlugInStreamLoader> create(Frame*, NetscapePlugInStreamLoaderClient*); + virtual ~NetscapePlugInStreamLoader(); + + bool isDone() const; + + private: + virtual void didReceiveResponse(const ResourceResponse&); + virtual void didReceiveData(const char*, int, long long lengthReceived, bool allAtOnce); + virtual void didFinishLoading(); + virtual void didFail(const ResourceError&); + + virtual void releaseResources(); + + NetscapePlugInStreamLoader(Frame*, NetscapePlugInStreamLoaderClient*); + + virtual void didCancel(const ResourceError& error); + + NetscapePlugInStreamLoaderClient* m_client; + }; + +} diff --git a/WebCore/loader/PluginDocument.cpp b/WebCore/loader/PluginDocument.cpp new file mode 100644 index 0000000..8be6ae2 --- /dev/null +++ b/WebCore/loader/PluginDocument.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * 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. + */ + +#include "config.h" +#include "PluginDocument.h" + +#include "DocumentLoader.h" +#include "Element.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "HTMLEmbedElement.h" +#include "HTMLNames.h" +#include "MainResourceLoader.h" +#include "Page.h" +#include "RenderWidget.h" +#include "SegmentedString.h" +#include "Settings.h" +#include "Text.h" +#include "XMLTokenizer.h" + +namespace WebCore { + +using namespace HTMLNames; + +class PluginTokenizer : public Tokenizer { +public: + PluginTokenizer(Document* doc) : m_doc(doc), m_embedElement(0) {} + + virtual bool write(const SegmentedString&, bool appendData); + virtual void stopParsing(); + virtual void finish(); + virtual bool isWaitingForScripts() const; + + virtual bool wantsRawData() const { return true; } + virtual bool writeRawData(const char* data, int len); + + void createDocumentStructure(); +private: + Document* m_doc; + HTMLEmbedElement* m_embedElement; +}; + +bool PluginTokenizer::write(const SegmentedString& s, bool appendData) +{ + ASSERT_NOT_REACHED(); + return false; +} + +void PluginTokenizer::createDocumentStructure() +{ + ExceptionCode ec; + RefPtr<Element> rootElement = m_doc->createElementNS(xhtmlNamespaceURI, "html", ec); + m_doc->appendChild(rootElement, ec); + + RefPtr<Element> body = m_doc->createElementNS(xhtmlNamespaceURI, "body", ec); + body->setAttribute(marginwidthAttr, "0"); + body->setAttribute(marginheightAttr, "0"); + body->setAttribute(bgcolorAttr, "rgb(38,38,38)"); + + rootElement->appendChild(body, ec); + + RefPtr<Element> embedElement = m_doc->createElementNS(xhtmlNamespaceURI, "embed", ec); + + m_embedElement = static_cast<HTMLEmbedElement*>(embedElement.get()); + m_embedElement->setAttribute(widthAttr, "100%"); + m_embedElement->setAttribute(heightAttr, "100%"); + + m_embedElement->setAttribute(nameAttr, "plugin"); + m_embedElement->setSrc(m_doc->url().string()); + m_embedElement->setType(m_doc->frame()->loader()->responseMIMEType()); + + body->appendChild(embedElement, ec); +} + +bool PluginTokenizer::writeRawData(const char* data, int len) +{ + ASSERT(!m_embedElement); + if (m_embedElement) + return false; + + createDocumentStructure(); + + if (Frame* frame = m_doc->frame()) { + Settings* settings = frame->settings(); + if (settings && settings->arePluginsEnabled()) { + m_doc->updateLayout(); + + if (RenderWidget* renderer = static_cast<RenderWidget*>(m_embedElement->renderer())) { + frame->loader()->client()->redirectDataToPlugin(renderer->widget()); + frame->loader()->activeDocumentLoader()->mainResourceLoader()->setShouldBufferData(false); + } + + finish(); + } + } + + return false; +} + +void PluginTokenizer::stopParsing() +{ + Tokenizer::stopParsing(); +} + +void PluginTokenizer::finish() +{ + if (!m_parserStopped) + m_doc->finishedParsing(); +} + +bool PluginTokenizer::isWaitingForScripts() const +{ + // A plugin document is never waiting for scripts + return false; +} + +PluginDocument::PluginDocument(Frame* frame) + : HTMLDocument(frame) +{ + setParseMode(Compat); +} + +Tokenizer* PluginDocument::createTokenizer() +{ + return new PluginTokenizer(this); +} + +} diff --git a/WebCore/loader/PluginDocument.h b/WebCore/loader/PluginDocument.h new file mode 100644 index 0000000..35e4038 --- /dev/null +++ b/WebCore/loader/PluginDocument.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * 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 PluginDocument_h +#define PluginDocument_h + +#include "HTMLDocument.h" + +namespace WebCore { + +class PluginDocument : public HTMLDocument { +public: + static PassRefPtr<PluginDocument> create(Frame* frame) + { + return new PluginDocument(frame); + } + +private: + PluginDocument(Frame*); + + virtual bool isPluginDocument() const { return true; } + virtual Tokenizer* createTokenizer(); +}; + +} + +#endif // PluginDocument_h diff --git a/WebCore/loader/ProgressTracker.cpp b/WebCore/loader/ProgressTracker.cpp new file mode 100644 index 0000000..56aa976 --- /dev/null +++ b/WebCore/loader/ProgressTracker.cpp @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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. + */ + +#include "config.h" +#include "ProgressTracker.h" + +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "ResourceResponse.h" +#include "SystemTime.h" + +using std::min; + +namespace WebCore { + +// Always start progress at initialProgressValue. This helps provide feedback as +// soon as a load starts. +static const double initialProgressValue = 0.1; + +// Similarly, always leave space at the end. This helps show the user that we're not done +// until we're done. +static const double finalProgressValue = 0.9; // 1.0 - initialProgressValue + +static const int progressItemDefaultEstimatedLength = 1024 * 16; + +struct ProgressItem { + ProgressItem(long long length) + : bytesReceived(0) + , estimatedLength(length) { } + + long long bytesReceived; + long long estimatedLength; +}; + +ProgressTracker::ProgressTracker() + : m_uniqueIdentifier(0) + , m_totalPageAndResourceBytesToLoad(0) + , m_totalBytesReceived(0) + , m_lastNotifiedProgressValue(0) + , m_lastNotifiedProgressTime(0) + , m_progressNotificationInterval(0.02) + , m_progressNotificationTimeInterval(0.1) + , m_finalProgressChangedSent(false) + , m_progressValue(0) + , m_numProgressTrackedFrames(0) +{ +} + +ProgressTracker::~ProgressTracker() +{ + deleteAllValues(m_progressItems); +} + +double ProgressTracker::estimatedProgress() const +{ + return m_progressValue; +} + +void ProgressTracker::reset() +{ + deleteAllValues(m_progressItems); + m_progressItems.clear(); + + m_totalPageAndResourceBytesToLoad = 0; + m_totalBytesReceived = 0; + m_progressValue = 0; + m_lastNotifiedProgressValue = 0; + m_lastNotifiedProgressTime = 0; + m_finalProgressChangedSent = false; + m_numProgressTrackedFrames = 0; + m_originatingProgressFrame = 0; +} + +void ProgressTracker::progressStarted(Frame* frame) +{ + // LOG (Progress, "frame %p(%@), _private->numProgressTrackedFrames %d, _private->originatingProgressFrame %p", frame, [frame name], _private->numProgressTrackedFrames, _private->originatingProgressFrame); + + frame->loader()->client()->willChangeEstimatedProgress(); + + if (m_numProgressTrackedFrames == 0 || m_originatingProgressFrame == frame) { + reset(); + m_progressValue = initialProgressValue; + m_originatingProgressFrame = frame; + + m_originatingProgressFrame->loader()->client()->postProgressStartedNotification(); + } + m_numProgressTrackedFrames++; + + frame->loader()->client()->didChangeEstimatedProgress(); +} + +void ProgressTracker::progressCompleted(Frame* frame) +{ + // LOG (Progress, "frame %p(%@), _private->numProgressTrackedFrames %d, _private->originatingProgressFrame %p", frame, [frame name], _private->numProgressTrackedFrames, _private->originatingProgressFrame); + + if (m_numProgressTrackedFrames <= 0) + return; + + frame->loader()->client()->willChangeEstimatedProgress(); + + m_numProgressTrackedFrames--; + if (m_numProgressTrackedFrames == 0 || + (frame == m_originatingProgressFrame && m_numProgressTrackedFrames != 0)) + finalProgressComplete(); + + frame->loader()->client()->didChangeEstimatedProgress(); +} + +void ProgressTracker::finalProgressComplete() +{ + // LOG (Progress, ""); + + RefPtr<Frame> frame = m_originatingProgressFrame.release(); + + // Before resetting progress value be sure to send client a least one notification + // with final progress value. + if (!m_finalProgressChangedSent) { + m_progressValue = 1; + frame->loader()->client()->postProgressEstimateChangedNotification(); + } + + reset(); + + frame->loader()->client()->setMainFrameDocumentReady(true); + frame->loader()->client()->postProgressFinishedNotification(); +} + +void ProgressTracker::incrementProgress(unsigned long identifier, const ResourceResponse& response) +{ + // LOG (Progress, "_private->numProgressTrackedFrames %d, _private->originatingProgressFrame %p", _private->numProgressTrackedFrames, _private->originatingProgressFrame); + + if (m_numProgressTrackedFrames <= 0) + return; + + long long estimatedLength = response.expectedContentLength(); + if (estimatedLength < 0) + estimatedLength = progressItemDefaultEstimatedLength; + + m_totalPageAndResourceBytesToLoad += estimatedLength; + + if (ProgressItem* item = m_progressItems.get(identifier)) { + item->bytesReceived = 0; + item->estimatedLength = estimatedLength; + } else + m_progressItems.set(identifier, new ProgressItem(estimatedLength)); +} + +void ProgressTracker::incrementProgress(unsigned long identifier, const char*, int length) +{ + ProgressItem* item = m_progressItems.get(identifier); + + // FIXME: Can this ever happen? + if (!item) + return; + + m_originatingProgressFrame->loader()->client()->willChangeEstimatedProgress(); + + unsigned bytesReceived = length; + double increment, percentOfRemainingBytes; + long long remainingBytes, estimatedBytesForPendingRequests; + + item->bytesReceived += bytesReceived; + if (item->bytesReceived > item->estimatedLength) { + m_totalPageAndResourceBytesToLoad += ((item->bytesReceived * 2) - item->estimatedLength); + item->estimatedLength = item->bytesReceived * 2; + } + + int numPendingOrLoadingRequests = m_originatingProgressFrame->loader()->numPendingOrLoadingRequests(true); + estimatedBytesForPendingRequests = progressItemDefaultEstimatedLength * numPendingOrLoadingRequests; + remainingBytes = ((m_totalPageAndResourceBytesToLoad + estimatedBytesForPendingRequests) - m_totalBytesReceived); + if (remainingBytes > 0) // Prevent divide by 0. + percentOfRemainingBytes = (double)bytesReceived / (double)remainingBytes; + else + percentOfRemainingBytes = 1.0; + + // Treat the first layout as the half-way point. + double maxProgressValue = m_originatingProgressFrame->loader()->firstLayoutDone() ? finalProgressValue : .5; + increment = (maxProgressValue - m_progressValue) * percentOfRemainingBytes; + m_progressValue += increment; + m_progressValue = min(m_progressValue, maxProgressValue); + ASSERT(m_progressValue >= initialProgressValue); + + m_totalBytesReceived += bytesReceived; + + double now = currentTime(); + double notifiedProgressTimeDelta = now - m_lastNotifiedProgressTime; + + // LOG (Progress, "_private->progressValue %g, _private->numProgressTrackedFrames %d", _private->progressValue, _private->numProgressTrackedFrames); + double notificationProgressDelta = m_progressValue - m_lastNotifiedProgressValue; + if ((notificationProgressDelta >= m_progressNotificationInterval || + notifiedProgressTimeDelta >= m_progressNotificationTimeInterval) && + m_numProgressTrackedFrames > 0) { + if (!m_finalProgressChangedSent) { + if (m_progressValue == 1) + m_finalProgressChangedSent = true; + + m_originatingProgressFrame->loader()->client()->postProgressEstimateChangedNotification(); + + m_lastNotifiedProgressValue = m_progressValue; + m_lastNotifiedProgressTime = now; + } + } + + m_originatingProgressFrame->loader()->client()->didChangeEstimatedProgress(); +} + +void ProgressTracker::completeProgress(unsigned long identifier) +{ + ProgressItem* item = m_progressItems.get(identifier); + + // FIXME: Can this happen? + if (!item) + return; + + // Adjust the total expected bytes to account for any overage/underage. + long long delta = item->bytesReceived - item->estimatedLength; + m_totalPageAndResourceBytesToLoad += delta; + item->estimatedLength = item->bytesReceived; + + m_progressItems.remove(identifier); + delete item; +} + +unsigned long ProgressTracker::createUniqueIdentifier() +{ + return ++m_uniqueIdentifier; +} + + +} diff --git a/WebCore/loader/ProgressTracker.h b/WebCore/loader/ProgressTracker.h new file mode 100644 index 0000000..b8d4532 --- /dev/null +++ b/WebCore/loader/ProgressTracker.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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 ProgressTracker_h +#define ProgressTracker_h + +#include <wtf/HashMap.h> +#include <wtf/Noncopyable.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +class Frame; +class ResourceResponse; +struct ProgressItem; + +class ProgressTracker : Noncopyable { +public: + ProgressTracker(); + ~ProgressTracker(); + + unsigned long createUniqueIdentifier(); + + double estimatedProgress() const; + + void progressStarted(Frame*); + void progressCompleted(Frame*); + + void incrementProgress(unsigned long identifier, const ResourceResponse&); + void incrementProgress(unsigned long identifier, const char*, int); + void completeProgress(unsigned long identifier); + + long long totalPageAndResourceBytesToLoad() const { return m_totalPageAndResourceBytesToLoad; } + long long totalBytesReceived() const { return m_totalBytesReceived; } + +private: + void reset(); + void finalProgressComplete(); + + unsigned long m_uniqueIdentifier; + + long long m_totalPageAndResourceBytesToLoad; + long long m_totalBytesReceived; + double m_lastNotifiedProgressValue; + double m_lastNotifiedProgressTime; + double m_progressNotificationInterval; + double m_progressNotificationTimeInterval; + bool m_finalProgressChangedSent; + double m_progressValue; + RefPtr<Frame> m_originatingProgressFrame; + + int m_numProgressTrackedFrames; + HashMap<unsigned long, ProgressItem*> m_progressItems; +}; + +} + +#endif diff --git a/WebCore/loader/Request.cpp b/WebCore/loader/Request.cpp new file mode 100644 index 0000000..7791a48 --- /dev/null +++ b/WebCore/loader/Request.cpp @@ -0,0 +1,47 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#include "Request.h" + +#include "CachedResource.h" + +namespace WebCore { + +Request::Request(DocLoader* docLoader, CachedResource* object, bool incremental, bool shouldSkipCanLoadCheck, bool sendResourceLoadCallbacks) + : m_object(object) + , m_docLoader(docLoader) + , m_incremental(incremental) + , m_multipart(false) + , m_shouldSkipCanLoadCheck(shouldSkipCanLoadCheck) + , m_sendResourceLoadCallbacks(sendResourceLoadCallbacks) +{ + m_object->setRequest(this); +} + +Request::~Request() +{ + m_object->setRequest(0); +} + +} //namespace WebCore diff --git a/WebCore/loader/Request.h b/WebCore/loader/Request.h new file mode 100644 index 0000000..07d1b82 --- /dev/null +++ b/WebCore/loader/Request.h @@ -0,0 +1,63 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef Request_h +#define Request_h + +#include <wtf/Vector.h> + +namespace WebCore { + + class CachedResource; + class DocLoader; + + class Request { + public: + Request(DocLoader*, CachedResource*, bool incremental, bool skipCanLoadCheck, bool sendResourceLoadCallbacks); + ~Request(); + + Vector<char>& buffer() { return m_buffer; } + CachedResource* cachedResource() { return m_object; } + DocLoader* docLoader() { return m_docLoader; } + + bool isIncremental() { return m_incremental; } + void setIsIncremental(bool b = true) { m_incremental = b; } + + bool isMultipart() { return m_multipart; } + void setIsMultipart(bool b = true) { m_multipart = b; } + + bool shouldSkipCanLoadCheck() const { return m_shouldSkipCanLoadCheck; } + bool sendResourceLoadCallbacks() const { return m_sendResourceLoadCallbacks; } + + private: + Vector<char> m_buffer; + CachedResource* m_object; + DocLoader* m_docLoader; + bool m_incremental; + bool m_multipart; + bool m_shouldSkipCanLoadCheck; + bool m_sendResourceLoadCallbacks; + }; + +} //namespace WebCore + +#endif // Request_h diff --git a/WebCore/loader/ResourceLoader.cpp b/WebCore/loader/ResourceLoader.cpp new file mode 100644 index 0000000..a6f90b3 --- /dev/null +++ b/WebCore/loader/ResourceLoader.cpp @@ -0,0 +1,446 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * (C) 2007 Graham Dennis (graham.dennis@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "ResourceLoader.h" + +#include "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "Page.h" +#include "ProgressTracker.h" +#include "ResourceHandle.h" +#include "ResourceError.h" +#include "Settings.h" +#include "SharedBuffer.h" + +namespace WebCore { + +PassRefPtr<SharedBuffer> ResourceLoader::resourceData() +{ + if (m_resourceData) + return m_resourceData; + + if (ResourceHandle::supportsBufferedData() && m_handle) + return m_handle->bufferedData(); + + return 0; +} + +ResourceLoader::ResourceLoader(Frame* frame, bool sendResourceLoadCallbacks, bool shouldContentSniff) + : m_frame(frame) + , m_documentLoader(frame->loader()->activeDocumentLoader()) + , m_identifier(0) + , m_reachedTerminalState(false) + , m_cancelled(false) + , m_calledDidFinishLoad(false) + , m_sendResourceLoadCallbacks(sendResourceLoadCallbacks) + , m_shouldContentSniff(shouldContentSniff) + , m_shouldBufferData(true) + , m_defersLoading(frame->page()->defersLoading()) +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + , m_wasLoadedFromApplicationCache(false) +#endif +{ +} + +ResourceLoader::~ResourceLoader() +{ + ASSERT(m_reachedTerminalState); +} + +void ResourceLoader::releaseResources() +{ + ASSERT(!m_reachedTerminalState); + + // It's possible that when we release the handle, it will be + // deallocated and release the last reference to this object. + // We need to retain to avoid accessing the object after it + // has been deallocated and also to avoid reentering this method. + RefPtr<ResourceLoader> protector(this); + + m_frame = 0; + m_documentLoader = 0; + + // We need to set reachedTerminalState to true before we release + // the resources to prevent a double dealloc of WebView <rdar://problem/4372628> + m_reachedTerminalState = true; + + m_identifier = 0; + + if (m_handle) { + // Clear out the ResourceHandle's client so that it doesn't try to call + // us back after we release it. + m_handle->setClient(0); + m_handle = 0; + } + + m_resourceData = 0; + m_deferredRequest = ResourceRequest(); +} + +bool ResourceLoader::load(const ResourceRequest& r) +{ + ASSERT(!m_handle); + ASSERT(m_deferredRequest.isNull()); + ASSERT(!m_documentLoader->isSubstituteLoadPending(this)); + + ResourceRequest clientRequest(r); + willSendRequest(clientRequest, ResourceResponse()); + if (clientRequest.isNull()) { + didFail(frameLoader()->cancelledError(r)); + return false; + } + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + if (m_documentLoader->scheduleArchiveLoad(this, clientRequest, r.url())) + return true; +#endif + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + if (m_documentLoader->scheduleApplicationCacheLoad(this, clientRequest, r.url())) { + m_wasLoadedFromApplicationCache = true; + return true; + } +#endif + + if (m_defersLoading) { + m_deferredRequest = clientRequest; + return true; + } + + m_handle = ResourceHandle::create(clientRequest, this, m_frame.get(), m_defersLoading, m_shouldContentSniff, true); + + return true; +} + +void ResourceLoader::setDefersLoading(bool defers) +{ + m_defersLoading = defers; + if (m_handle) + m_handle->setDefersLoading(defers); + if (!defers && !m_deferredRequest.isNull()) { + ResourceRequest request(m_deferredRequest); + m_deferredRequest = ResourceRequest(); + load(request); + } +} + +FrameLoader* ResourceLoader::frameLoader() const +{ + if (!m_frame) + return 0; + return m_frame->loader(); +} + +void ResourceLoader::setShouldBufferData(bool shouldBufferData) +{ + m_shouldBufferData = shouldBufferData; + + // Reset any already buffered data + if (!m_shouldBufferData) + m_resourceData = 0; +} + + +void ResourceLoader::addData(const char* data, int length, bool allAtOnce) +{ + if (!m_shouldBufferData) + return; + + if (allAtOnce) { + m_resourceData = SharedBuffer::create(data, length); + return; + } + + if (ResourceHandle::supportsBufferedData()) { + // Buffer data only if the connection has handed us the data because is has stopped buffering it. + if (m_resourceData) + m_resourceData->append(data, length); + } else { + if (!m_resourceData) + m_resourceData = SharedBuffer::create(data, length); + else + m_resourceData->append(data, length); + } +} + +void ResourceLoader::clearResourceData() +{ + if (m_resourceData) + m_resourceData->clear(); +} + +void ResourceLoader::willSendRequest(ResourceRequest& request, const ResourceResponse& redirectResponse) +{ + // Protect this in this delegate method since the additional processing can do + // anything including possibly derefing this; one example of this is Radar 3266216. + RefPtr<ResourceLoader> protector(this); + + ASSERT(!m_reachedTerminalState); + + if (m_sendResourceLoadCallbacks) { + if (!m_identifier) { + m_identifier = m_frame->page()->progress()->createUniqueIdentifier(); + frameLoader()->assignIdentifierToInitialRequest(m_identifier, request); + } + + frameLoader()->willSendRequest(this, request, redirectResponse); + } + + m_request = request; +} + +void ResourceLoader::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent) +{ +} + +void ResourceLoader::didReceiveResponse(const ResourceResponse& r) +{ + ASSERT(!m_reachedTerminalState); + + // Protect this in this delegate method since the additional processing can do + // anything including possibly derefing this; one example of this is Radar 3266216. + RefPtr<ResourceLoader> protector(this); + + m_response = r; + + if (FormData* data = m_request.httpBody()) + data->removeGeneratedFilesIfNeeded(); + + if (m_sendResourceLoadCallbacks) + frameLoader()->didReceiveResponse(this, m_response); +} + +void ResourceLoader::didReceiveData(const char* data, int length, long long lengthReceived, bool allAtOnce) +{ + // The following assertions are not quite valid here, since a subclass + // might override didReceiveData in a way that invalidates them. This + // happens with the steps listed in 3266216 + // ASSERT(con == connection); + // ASSERT(!m_reachedTerminalState); + + // Protect this in this delegate method since the additional processing can do + // anything including possibly derefing this; one example of this is Radar 3266216. + RefPtr<ResourceLoader> protector(this); + + addData(data, length, allAtOnce); + // FIXME: If we get a resource with more than 2B bytes, this code won't do the right thing. + // However, with today's computers and networking speeds, this won't happen in practice. + // Could be an issue with a giant local file. + if (m_sendResourceLoadCallbacks && m_frame) + frameLoader()->didReceiveData(this, data, length, static_cast<int>(lengthReceived)); +} + +void ResourceLoader::willStopBufferingData(const char* data, int length) +{ + if (!m_shouldBufferData) + return; + + ASSERT(!m_resourceData); + m_resourceData = SharedBuffer::create(data, length); +} + +void ResourceLoader::didFinishLoading() +{ + // If load has been cancelled after finishing (which could happen with a + // JavaScript that changes the window location), do nothing. + if (m_cancelled) + return; + ASSERT(!m_reachedTerminalState); + + didFinishLoadingOnePart(); + releaseResources(); +} + +void ResourceLoader::didFinishLoadingOnePart() +{ + if (m_cancelled) + return; + ASSERT(!m_reachedTerminalState); + + if (m_calledDidFinishLoad) + return; + m_calledDidFinishLoad = true; + if (m_sendResourceLoadCallbacks) + frameLoader()->didFinishLoad(this); +} + +void ResourceLoader::didFail(const ResourceError& error) +{ + if (m_cancelled) + return; + ASSERT(!m_reachedTerminalState); + + // Protect this in this delegate method since the additional processing can do + // anything including possibly derefing this; one example of this is Radar 3266216. + RefPtr<ResourceLoader> protector(this); + + if (FormData* data = m_request.httpBody()) + data->removeGeneratedFilesIfNeeded(); + + if (m_sendResourceLoadCallbacks && !m_calledDidFinishLoad) + frameLoader()->didFailToLoad(this, error); + + releaseResources(); +} + +void ResourceLoader::didCancel(const ResourceError& error) +{ + ASSERT(!m_cancelled); + ASSERT(!m_reachedTerminalState); + + if (FormData* data = m_request.httpBody()) + data->removeGeneratedFilesIfNeeded(); + + // This flag prevents bad behavior when loads that finish cause the + // load itself to be cancelled (which could happen with a javascript that + // changes the window location). This is used to prevent both the body + // of this method and the body of connectionDidFinishLoading: running + // for a single delegate. Cancelling wins. + m_cancelled = true; + + if (m_handle) + m_handle->clearAuthentication(); + + m_documentLoader->cancelPendingSubstituteLoad(this); + if (m_handle) { + m_handle->cancel(); + m_handle = 0; + } + if (m_sendResourceLoadCallbacks && !m_calledDidFinishLoad) + frameLoader()->didFailToLoad(this, error); + + releaseResources(); +} + +void ResourceLoader::cancel() +{ + cancel(ResourceError()); +} + +void ResourceLoader::cancel(const ResourceError& error) +{ + if (m_reachedTerminalState) + return; + if (!error.isNull()) + didCancel(error); + else + didCancel(cancelledError()); +} + +const ResourceResponse& ResourceLoader::response() const +{ + return m_response; +} + +ResourceError ResourceLoader::cancelledError() +{ + return frameLoader()->cancelledError(m_request); +} + +ResourceError ResourceLoader::blockedError() +{ + return frameLoader()->blockedError(m_request); +} + +ResourceError ResourceLoader::cannotShowURLError() +{ + return frameLoader()->cannotShowURLError(m_request); +} + +void ResourceLoader::willSendRequest(ResourceHandle*, ResourceRequest& request, const ResourceResponse& redirectResponse) +{ + willSendRequest(request, redirectResponse); +} + +void ResourceLoader::didSendData(ResourceHandle*, unsigned long long bytesSent, unsigned long long totalBytesToBeSent) +{ + didSendData(bytesSent, totalBytesToBeSent); +} + +void ResourceLoader::didReceiveResponse(ResourceHandle*, const ResourceResponse& response) +{ + didReceiveResponse(response); +} + +void ResourceLoader::didReceiveData(ResourceHandle*, const char* data, int length, int lengthReceived) +{ + didReceiveData(data, length, lengthReceived, false); +} + +void ResourceLoader::didFinishLoading(ResourceHandle*) +{ + didFinishLoading(); +} + +void ResourceLoader::didFail(ResourceHandle*, const ResourceError& error) +{ + didFail(error); +} + +void ResourceLoader::wasBlocked(ResourceHandle*) +{ + didFail(blockedError()); +} + +void ResourceLoader::cannotShowURL(ResourceHandle*) +{ + didFail(cannotShowURLError()); +} + +void ResourceLoader::didReceiveAuthenticationChallenge(const AuthenticationChallenge& challenge) +{ + // Protect this in this delegate method since the additional processing can do + // anything including possibly derefing this; one example of this is Radar 3266216. + RefPtr<ResourceLoader> protector(this); + frameLoader()->didReceiveAuthenticationChallenge(this, challenge); +} + +void ResourceLoader::didCancelAuthenticationChallenge(const AuthenticationChallenge& challenge) +{ + // Protect this in this delegate method since the additional processing can do + // anything including possibly derefing this; one example of this is Radar 3266216. + RefPtr<ResourceLoader> protector(this); + frameLoader()->didCancelAuthenticationChallenge(this, challenge); +} + +void ResourceLoader::receivedCancellation(const AuthenticationChallenge&) +{ + cancel(); +} + +void ResourceLoader::willCacheResponse(ResourceHandle*, CacheStoragePolicy& policy) +{ + // When in private browsing mode, prevent caching to disk + if (policy == StorageAllowed && m_frame->settings()->privateBrowsingEnabled()) + policy = StorageAllowedInMemoryOnly; +} + +} diff --git a/WebCore/loader/ResourceLoader.h b/WebCore/loader/ResourceLoader.h new file mode 100644 index 0000000..722f5fc --- /dev/null +++ b/WebCore/loader/ResourceLoader.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 ResourceLoader_h +#define ResourceLoader_h + +#include "ResourceHandleClient.h" +#include "ResourceRequest.h" +#include "ResourceResponse.h" +#include <wtf/RefCounted.h> +#include "AuthenticationChallenge.h" +#include "KURL.h" + +#include <wtf/Forward.h> + +namespace WebCore { + + class DocumentLoader; + class Frame; + class FrameLoader; + class ResourceHandle; + class SharedBuffer; + + class ResourceLoader : public RefCounted<ResourceLoader>, protected ResourceHandleClient { + public: + virtual ~ResourceLoader(); + + void cancel(); + + virtual bool load(const ResourceRequest&); + + FrameLoader* frameLoader() const; + DocumentLoader* documentLoader() const { return m_documentLoader.get(); } + + virtual void cancel(const ResourceError&); + ResourceError cancelledError(); + ResourceError blockedError(); + ResourceError cannotShowURLError(); + + virtual void setDefersLoading(bool); + + void setIdentifier(unsigned long identifier) { m_identifier = identifier; } + unsigned long identifier() const { return m_identifier; } + + virtual void releaseResources(); + const ResourceResponse& response() const; + + virtual void addData(const char*, int, bool allAtOnce); + virtual PassRefPtr<SharedBuffer> resourceData(); + void clearResourceData(); + + virtual void willSendRequest(ResourceRequest&, const ResourceResponse& redirectResponse); + virtual void didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent); + virtual void didReceiveResponse(const ResourceResponse&); + virtual void didReceiveData(const char*, int, long long lengthReceived, bool allAtOnce); + void willStopBufferingData(const char*, int); + virtual void didFinishLoading(); + virtual void didFail(const ResourceError&); + + virtual void didReceiveAuthenticationChallenge(const AuthenticationChallenge&); + void didCancelAuthenticationChallenge(const AuthenticationChallenge&); + virtual void receivedCancellation(const AuthenticationChallenge&); + + // ResourceHandleClient + virtual void willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse& redirectResponse); + virtual void didSendData(ResourceHandle*, unsigned long long bytesSent, unsigned long long totalBytesToBeSent); + virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&); + virtual void didReceiveData(ResourceHandle*, const char*, int, int lengthReceived); + virtual void didFinishLoading(ResourceHandle*); + virtual void didFail(ResourceHandle*, const ResourceError&); + virtual void wasBlocked(ResourceHandle*); + virtual void cannotShowURL(ResourceHandle*); + virtual void willStopBufferingData(ResourceHandle*, const char* data, int length) { willStopBufferingData(data, length); } + virtual void didReceiveAuthenticationChallenge(ResourceHandle*, const AuthenticationChallenge& challenge) { didReceiveAuthenticationChallenge(challenge); } + virtual void didCancelAuthenticationChallenge(ResourceHandle*, const AuthenticationChallenge& challenge) { didCancelAuthenticationChallenge(challenge); } + virtual void receivedCancellation(ResourceHandle*, const AuthenticationChallenge& challenge) { receivedCancellation(challenge); } + virtual void willCacheResponse(ResourceHandle*, CacheStoragePolicy&); +#if PLATFORM(MAC) + virtual NSCachedURLResponse* willCacheResponse(ResourceHandle*, NSCachedURLResponse*); +#endif + + ResourceHandle* handle() const { return m_handle.get(); } + bool sendResourceLoadCallbacks() const { return m_sendResourceLoadCallbacks; } + + void setShouldBufferData(bool shouldBufferData); + + protected: + ResourceLoader(Frame*, bool sendResourceLoadCallbacks, bool shouldContentSniff); + + virtual void didCancel(const ResourceError&); + void didFinishLoadingOnePart(); + + const ResourceRequest& request() const { return m_request; } + bool reachedTerminalState() const { return m_reachedTerminalState; } + bool cancelled() const { return m_cancelled; } + bool defersLoading() const { return m_defersLoading; } + + RefPtr<ResourceHandle> m_handle; + RefPtr<Frame> m_frame; + RefPtr<DocumentLoader> m_documentLoader; + ResourceResponse m_response; + + private: + ResourceRequest m_request; + RefPtr<SharedBuffer> m_resourceData; + + unsigned long m_identifier; + + bool m_reachedTerminalState; + bool m_cancelled; + bool m_calledDidFinishLoad; + + bool m_sendResourceLoadCallbacks; + bool m_shouldContentSniff; + bool m_shouldBufferData; + bool m_defersLoading; +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + bool m_wasLoadedFromApplicationCache; +#endif + ResourceRequest m_deferredRequest; + }; + +} + +#endif diff --git a/WebCore/loader/SubresourceLoader.cpp b/WebCore/loader/SubresourceLoader.cpp new file mode 100644 index 0000000..bff48b2 --- /dev/null +++ b/WebCore/loader/SubresourceLoader.cpp @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "SubresourceLoader.h" + +#include "Document.h" +#include "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "Logging.h" +#include "ResourceHandle.h" +#include "ResourceRequest.h" +#include "SubresourceLoaderClient.h" +#include "SharedBuffer.h" +#include <wtf/RefCountedLeakCounter.h> + +namespace WebCore { + +#ifndef NDEBUG +static WTF::RefCountedLeakCounter subresourceLoaderCounter("SubresourceLoader"); +#endif + +SubresourceLoader::SubresourceLoader(Frame* frame, SubresourceLoaderClient* client, bool sendResourceLoadCallbacks, bool shouldContentSniff) + : ResourceLoader(frame, sendResourceLoadCallbacks, shouldContentSniff) + , m_client(client) + , m_loadingMultipartContent(false) +{ +#ifndef NDEBUG + subresourceLoaderCounter.increment(); +#endif + m_documentLoader->addSubresourceLoader(this); +} + +SubresourceLoader::~SubresourceLoader() +{ +#ifndef NDEBUG + subresourceLoaderCounter.decrement(); +#endif +} + +bool SubresourceLoader::load(const ResourceRequest& r) +{ + m_frame->loader()->didTellClientAboutLoad(r.url().string()); + + return ResourceLoader::load(r); +} + +PassRefPtr<SubresourceLoader> SubresourceLoader::create(Frame* frame, SubresourceLoaderClient* client, const ResourceRequest& request, bool skipCanLoadCheck, bool sendResourceLoadCallbacks, bool shouldContentSniff) +{ + if (!frame) + return 0; + + FrameLoader* fl = frame->loader(); + if (!skipCanLoadCheck && fl->state() == FrameStateProvisional) + return 0; + + ResourceRequest newRequest = request; + + if (!skipCanLoadCheck + && FrameLoader::restrictAccessToLocal() + && !FrameLoader::canLoad(request.url(), String(), frame->document())) { + FrameLoader::reportLocalLoadFailed(frame, request.url().string()); + return 0; + } + + if (FrameLoader::shouldHideReferrer(request.url(), fl->outgoingReferrer())) + newRequest.clearHTTPReferrer(); + else if (!request.httpReferrer()) + newRequest.setHTTPReferrer(fl->outgoingReferrer()); + FrameLoader::addHTTPOriginIfNeeded(newRequest, fl->outgoingOrigin()); + + // Use the original request's cache policy for two reasons: + // 1. For POST requests, we mutate the cache policy for the main resource, + // but we do not want this to apply to subresources + // 2. Delegates that modify the cache policy using willSendRequest: should + // not affect any other resources. Such changes need to be done + // per request. + if (newRequest.isConditional()) + newRequest.setCachePolicy(ReloadIgnoringCacheData); + else + newRequest.setCachePolicy(fl->originalRequest().cachePolicy()); + + fl->addExtraFieldsToRequest(newRequest, false, false); + + RefPtr<SubresourceLoader> subloader(adoptRef(new SubresourceLoader(frame, client, sendResourceLoadCallbacks, shouldContentSniff))); + if (!subloader->load(newRequest)) + return 0; + + return subloader.release(); +} + +void SubresourceLoader::willSendRequest(ResourceRequest& newRequest, const ResourceResponse& redirectResponse) +{ + // Store the previous URL because the call to ResourceLoader::willSendRequest will modify it. + KURL previousURL = request().url(); + + ResourceLoader::willSendRequest(newRequest, redirectResponse); + if (!previousURL.isNull() && !newRequest.isNull() && previousURL != newRequest.url() && m_client) + m_client->willSendRequest(this, newRequest, redirectResponse); +} + +void SubresourceLoader::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent) +{ + RefPtr<SubresourceLoader> protect(this); + + if (m_client) + m_client->didSendData(this, bytesSent, totalBytesToBeSent); +} + +void SubresourceLoader::didReceiveResponse(const ResourceResponse& r) +{ + ASSERT(!r.isNull()); + + if (r.isMultipart()) + m_loadingMultipartContent = true; + + // Reference the object in this method since the additional processing can do + // anything including removing the last reference to this object; one example of this is 3266216. + RefPtr<SubresourceLoader> protect(this); + + if (m_client) + m_client->didReceiveResponse(this, r); + + // The loader can cancel a load if it receives a multipart response for a non-image + if (reachedTerminalState()) + return; + ResourceLoader::didReceiveResponse(r); + + RefPtr<SharedBuffer> buffer = resourceData(); + if (m_loadingMultipartContent && buffer && buffer->size()) { + // Since a subresource loader does not load multipart sections progressively, + // deliver the previously received data to the loader all at once now. + // Then clear the data to make way for the next multipart section. + if (m_client) + m_client->didReceiveData(this, buffer->data(), buffer->size()); + clearResourceData(); + + // After the first multipart section is complete, signal to delegates that this load is "finished" + m_documentLoader->subresourceLoaderFinishedLoadingOnePart(this); + didFinishLoadingOnePart(); + } +} + +void SubresourceLoader::didReceiveData(const char* data, int length, long long lengthReceived, bool allAtOnce) +{ + // Reference the object in this method since the additional processing can do + // anything including removing the last reference to this object; one example of this is 3266216. + RefPtr<SubresourceLoader> protect(this); + + ResourceLoader::didReceiveData(data, length, lengthReceived, allAtOnce); + + // A subresource loader does not load multipart sections progressively. + // So don't deliver any data to the loader yet. + if (!m_loadingMultipartContent && m_client) + m_client->didReceiveData(this, data, length); +} + +void SubresourceLoader::didFinishLoading() +{ + if (cancelled()) + return; + ASSERT(!reachedTerminalState()); + + // Calling removeSubresourceLoader will likely result in a call to deref, so we must protect ourselves. + RefPtr<SubresourceLoader> protect(this); + + if (m_client) + m_client->didFinishLoading(this); + + m_handle = 0; + + if (cancelled()) + return; + m_documentLoader->removeSubresourceLoader(this); + ResourceLoader::didFinishLoading(); +} + +void SubresourceLoader::didFail(const ResourceError& error) +{ + if (cancelled()) + return; + ASSERT(!reachedTerminalState()); + + // Calling removeSubresourceLoader will likely result in a call to deref, so we must protect ourselves. + RefPtr<SubresourceLoader> protect(this); + + if (m_client) + m_client->didFail(this, error); + + m_handle = 0; + + if (cancelled()) + return; + m_documentLoader->removeSubresourceLoader(this); + ResourceLoader::didFail(error); +} + +void SubresourceLoader::didCancel(const ResourceError& error) +{ + ASSERT(!reachedTerminalState()); + + // Calling removeSubresourceLoader will likely result in a call to deref, so we must protect ourselves. + RefPtr<SubresourceLoader> protect(this); + + if (m_client) + m_client->didFail(this, error); + + if (cancelled()) + return; + m_documentLoader->removeSubresourceLoader(this); + ResourceLoader::didCancel(error); +} + +void SubresourceLoader::didReceiveAuthenticationChallenge(const AuthenticationChallenge& challenge) +{ + RefPtr<SubresourceLoader> protect(this); + + if (m_client) + m_client->didReceiveAuthenticationChallenge(this, challenge); + + // The SubResourceLoaderClient may have cancelled this ResourceLoader in response to the challenge. + // If that's the case, don't call didReceiveAuthenticationChallenge + if (reachedTerminalState()) + return; + + ResourceLoader::didReceiveAuthenticationChallenge(challenge); +} + +void SubresourceLoader::receivedCancellation(const AuthenticationChallenge& challenge) +{ + ASSERT(!reachedTerminalState()); + + RefPtr<SubresourceLoader> protect(this); + + if (m_client) + m_client->receivedCancellation(this, challenge); + + ResourceLoader::receivedCancellation(challenge); +} + + +} diff --git a/WebCore/loader/SubresourceLoader.h b/WebCore/loader/SubresourceLoader.h new file mode 100644 index 0000000..b5bd34a --- /dev/null +++ b/WebCore/loader/SubresourceLoader.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 SubresourceLoader_h +#define SubresourceLoader_h + +#include "ResourceHandleClient.h" +#include "ResourceLoader.h" +#include <wtf/PassRefPtr.h> + +namespace WebCore { + + class FormData; + class String; + class ResourceHandle; + class ResourceRequest; + class SubresourceLoaderClient; + + class SubresourceLoader : public ResourceLoader { + public: + static PassRefPtr<SubresourceLoader> create(Frame*, SubresourceLoaderClient*, const ResourceRequest&, bool skipCanLoadCheck = false, bool sendResourceLoadCallbacks = true, bool shouldContentSniff = true); + + virtual ~SubresourceLoader(); + + virtual bool load(const ResourceRequest&); + + virtual void willSendRequest(ResourceRequest&, const ResourceResponse& redirectResponse); + virtual void didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent); + virtual void didReceiveResponse(const ResourceResponse&); + virtual void didReceiveData(const char*, int, long long lengthReceived, bool allAtOnce); + virtual void didFinishLoading(); + virtual void didFail(const ResourceError&); + virtual void didReceiveAuthenticationChallenge(const AuthenticationChallenge&); + virtual void receivedCancellation(const AuthenticationChallenge&); + + void clearClient() { m_client = 0; } + + private: + SubresourceLoader(Frame*, SubresourceLoaderClient*, bool sendResourceLoadCallbacks, bool shouldContentSniff); + + virtual void didCancel(const ResourceError&); + SubresourceLoaderClient* m_client; + bool m_loadingMultipartContent; + }; + +} + +#endif // SubresourceLoader_h diff --git a/WebCore/loader/SubresourceLoaderClient.h b/WebCore/loader/SubresourceLoaderClient.h new file mode 100644 index 0000000..d2b9a12 --- /dev/null +++ b/WebCore/loader/SubresourceLoaderClient.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 SubresourceLoaderClient_h +#define SubresourceLoaderClient_h + +namespace WebCore { + +class AuthenticationChallenge; +class ResourceError; +class ResourceRequest; +class ResourceResponse; +class SubresourceLoader; + +class SubresourceLoaderClient { +public: + virtual ~SubresourceLoaderClient() { } + + // request may be modified + virtual void willSendRequest(SubresourceLoader*, ResourceRequest&, const ResourceResponse& redirectResponse) { } + virtual void didSendData(SubresourceLoader*, unsigned long long bytesSent, unsigned long long totalBytesToBeSent) { } + + virtual void didReceiveResponse(SubresourceLoader*, const ResourceResponse&) { } + virtual void didReceiveData(SubresourceLoader*, const char*, int) { } + virtual void didFinishLoading(SubresourceLoader*) { } + virtual void didFail(SubresourceLoader*, const ResourceError&) { } + + virtual void didReceiveAuthenticationChallenge(SubresourceLoader*, const AuthenticationChallenge&) { } + virtual void receivedCancellation(SubresourceLoader*, const AuthenticationChallenge&) { } + +}; + +} // namespace WebCore + +#endif // SubresourceLoaderClient_h diff --git a/WebCore/loader/SubstituteData.h b/WebCore/loader/SubstituteData.h new file mode 100644 index 0000000..0b87b62 --- /dev/null +++ b/WebCore/loader/SubstituteData.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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 SubstituteData_h +#define SubstituteData_h + +#include "KURL.h" +#include "SharedBuffer.h" +#include "PlatformString.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + + class SubstituteData { + public: + SubstituteData() { } + + SubstituteData(PassRefPtr<SharedBuffer> content, const String& mimeType, const String& textEncoding, const KURL& failingURL, const KURL& responseURL = KURL()) + : m_content(content) + , m_mimeType(mimeType) + , m_textEncoding(textEncoding) + , m_failingURL(failingURL) + , m_responseURL(responseURL) + { + } + + bool isValid() const { return m_content != 0; } + + const SharedBuffer* content() const { return m_content.get(); } + const String& mimeType() const { return m_mimeType; } + const String& textEncoding() const { return m_textEncoding; } + const KURL& failingURL() const { return m_failingURL; } + const KURL& responseURL() const { return m_responseURL; } + + private: + RefPtr<SharedBuffer> m_content; + String m_mimeType; + String m_textEncoding; + KURL m_failingURL; + KURL m_responseURL; + }; + +} + +#endif // SubstituteData_h + diff --git a/WebCore/loader/SubstituteResource.h b/WebCore/loader/SubstituteResource.h new file mode 100644 index 0000000..15cbc6f --- /dev/null +++ b/WebCore/loader/SubstituteResource.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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 SubstituteResource_h +#define SubstituteResource_h + +#include <wtf/RefCounted.h> + +#include "KURL.h" +#include "ResourceResponse.h" +#include "SharedBuffer.h" + +#include <wtf/RefPtr.h> + +namespace WebCore { + +class SubstituteResource : public RefCounted<SubstituteResource> { +public: + virtual ~SubstituteResource() { } + + const KURL& url() const { return m_url; } + const ResourceResponse& response() const { return m_response; } + SharedBuffer* data() const { return m_data.get(); } + +protected: + SubstituteResource(const KURL& url, const ResourceResponse& response, PassRefPtr<SharedBuffer> data) + : m_url(url) + , m_response(response) + , m_data(data) + { + ASSERT(m_data); + } + +private: + KURL m_url; + ResourceResponse m_response; + RefPtr<SharedBuffer> m_data; +}; + +} + +#endif // SubstituteResource_h diff --git a/WebCore/loader/TextDocument.cpp b/WebCore/loader/TextDocument.cpp new file mode 100644 index 0000000..6b06ede --- /dev/null +++ b/WebCore/loader/TextDocument.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * 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. + */ + +#include "config.h" +#include "TextDocument.h" + +#include "Element.h" +#include "HTMLNames.h" +#include "HTMLViewSourceDocument.h" +#include "SegmentedString.h" +#include "Text.h" +#include "XMLTokenizer.h" + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +class TextTokenizer : public Tokenizer { +public: + TextTokenizer(Document*); + TextTokenizer(HTMLViewSourceDocument*); + + virtual bool write(const SegmentedString&, bool appendData); + virtual void finish(); + virtual bool isWaitingForScripts() const; + + inline void checkBuffer(int len = 10) + { + if ((m_dest - m_buffer) > m_size - len) { + // Enlarge buffer + int newSize = std::max(m_size * 2, m_size + len); + int oldOffset = m_dest - m_buffer; + m_buffer = static_cast<UChar*>(fastRealloc(m_buffer, newSize * sizeof(UChar))); + m_dest = m_buffer + oldOffset; + m_size = newSize; + } + } + +private: + Document* m_doc; + Element* m_preElement; + + bool m_skipLF; + + int m_size; + UChar* m_buffer; + UChar* m_dest; +}; + +TextTokenizer::TextTokenizer(Document* doc) + : m_doc(doc) + , m_preElement(0) + , m_skipLF(false) +{ + // Allocate buffer + m_size = 254; + m_buffer = static_cast<UChar*>(fastMalloc(sizeof(UChar) * m_size)); + m_dest = m_buffer; +} + +TextTokenizer::TextTokenizer(HTMLViewSourceDocument* doc) + : Tokenizer(true) + , m_doc(doc) + , m_preElement(0) + , m_skipLF(false) +{ + // Allocate buffer + m_size = 254; + m_buffer = static_cast<UChar*>(fastMalloc(sizeof(UChar) * m_size)); + m_dest = m_buffer; +} + +bool TextTokenizer::write(const SegmentedString& s, bool appendData) +{ + ExceptionCode ec; + + m_dest = m_buffer; + + SegmentedString str = s; + while (!str.isEmpty()) { + UChar c = *str; + + if (c == '\r') { + *m_dest++ = '\n'; + + // possibly skip an LF in the case of an CRLF sequence + m_skipLF = true; + } else if (c == '\n') { + if (!m_skipLF) + *m_dest++ = c; + else + m_skipLF = false; + } else { + *m_dest++ = c; + m_skipLF = false; + } + + str.advance(); + + // Maybe enlarge the buffer + checkBuffer(); + } + + if (!m_preElement && !inViewSourceMode()) { + RefPtr<Element> rootElement = m_doc->createElementNS(xhtmlNamespaceURI, "html", ec); + m_doc->appendChild(rootElement, ec); + + RefPtr<Element> body = m_doc->createElementNS(xhtmlNamespaceURI, "body", ec); + rootElement->appendChild(body, ec); + + RefPtr<Element> preElement = m_doc->createElementNS(xhtmlNamespaceURI, "pre", ec); + preElement->setAttribute("style", "word-wrap: break-word; white-space: pre-wrap;", ec); + + body->appendChild(preElement, ec); + + m_preElement = preElement.get(); + } + + String string = String(m_buffer, m_dest - m_buffer); + if (inViewSourceMode()) { + static_cast<HTMLViewSourceDocument*>(m_doc)->addViewSourceText(string); + return false; + } + + unsigned charsLeft = string.length(); + while (charsLeft) { + // split large text to nodes of manageable size + RefPtr<Text> text = Text::createWithLengthLimit(m_doc, string, charsLeft); + m_preElement->appendChild(text, ec); + } + + return false; +} + +void TextTokenizer::finish() +{ + m_preElement = 0; + fastFree(m_buffer); + + m_doc->finishedParsing(); +} + +bool TextTokenizer::isWaitingForScripts() const +{ + // A text document is never waiting for scripts + return false; +} + +TextDocument::TextDocument(Frame* frame) + : HTMLDocument(frame) +{ +} + +Tokenizer* TextDocument::createTokenizer() +{ + return new TextTokenizer(this); +} + +Tokenizer* createTextTokenizer(HTMLViewSourceDocument* document) +{ + return new TextTokenizer(document); +} + +} diff --git a/WebCore/loader/TextDocument.h b/WebCore/loader/TextDocument.h new file mode 100644 index 0000000..c67fea5 --- /dev/null +++ b/WebCore/loader/TextDocument.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * 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 TextDocument_h +#define TextDocument_h + +#include "HTMLDocument.h" + +namespace WebCore { + +class HTMLViewSourceDocument; + +class TextDocument : public HTMLDocument { +public: + static PassRefPtr<TextDocument> create(Frame* frame) + { + return new TextDocument(frame); + } + +private: + TextDocument(Frame*); + + virtual Tokenizer* createTokenizer(); +}; + +Tokenizer* createTextTokenizer(HTMLViewSourceDocument*); + +} + +#endif // TextDocument_h diff --git a/WebCore/loader/TextResourceDecoder.cpp b/WebCore/loader/TextResourceDecoder.cpp new file mode 100644 index 0000000..4a0caa0 --- /dev/null +++ b/WebCore/loader/TextResourceDecoder.cpp @@ -0,0 +1,799 @@ +/* + Copyright (C) 1999 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + Copyright (C) 2005, 2006, 2007 Alexey Proskuryakov (ap@nypop.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#include "config.h" +#include "TextResourceDecoder.h" + +#include "DOMImplementation.h" +#include "HTMLNames.h" +#include "TextCodec.h" +#include <wtf/ASCIICType.h> +#include <wtf/StringExtras.h> + +using namespace WTF; + +namespace WebCore { + +using namespace HTMLNames; + +// You might think we should put these find functions elsewhere, perhaps with the +// similar functions that operate on UChar, but arguably only the decoder has +// a reason to process strings of char rather than UChar. + +static int find(const char* subject, size_t subjectLength, const char* target) +{ + size_t targetLength = strlen(target); + if (targetLength > subjectLength) + return -1; + for (size_t i = 0; i <= subjectLength - targetLength; ++i) { + bool match = true; + for (size_t j = 0; j < targetLength; ++j) { + if (subject[i + j] != target[j]) { + match = false; + break; + } + } + if (match) + return i; + } + return -1; +} + +static int findIgnoringCase(const char* subject, size_t subjectLength, const char* target) +{ + size_t targetLength = strlen(target); + if (targetLength > subjectLength) + return -1; +#ifndef NDEBUG + for (size_t i = 0; i < targetLength; ++i) + ASSERT(isASCIILower(target[i])); +#endif + for (size_t i = 0; i <= subjectLength - targetLength; ++i) { + bool match = true; + for (size_t j = 0; j < targetLength; ++j) { + if (toASCIILower(subject[i + j]) != target[j]) { + match = false; + break; + } + } + if (match) + return i; + } + return -1; +} + +static TextEncoding findTextEncoding(const char* encodingName, int length) +{ + Vector<char, 64> buffer(length + 1); + memcpy(buffer.data(), encodingName, length); + buffer[length] = '\0'; + return buffer.data(); +} + +class KanjiCode { +public: + enum Type { ASCII, JIS, EUC, SJIS, UTF16, UTF8 }; + static enum Type judge(const char* str, int length); + static const int ESC = 0x1b; + static const unsigned char sjisMap[256]; + static int ISkanji(int code) + { + if (code >= 0x100) + return 0; + return sjisMap[code & 0xff] & 1; + } + static int ISkana(int code) + { + if (code >= 0x100) + return 0; + return sjisMap[code & 0xff] & 2; + } +}; + +const unsigned char KanjiCode::sjisMap[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 +}; + +/* + * EUC-JP is + * [0xa1 - 0xfe][0xa1 - 0xfe] + * 0x8e[0xa1 - 0xfe](SS2) + * 0x8f[0xa1 - 0xfe][0xa1 - 0xfe](SS3) + * + * Shift_Jis is + * [0x81 - 0x9f, 0xe0 - 0xef(0xfe?)][0x40 - 0x7e, 0x80 - 0xfc] + * + * Shift_Jis Hankaku Kana is + * [0xa1 - 0xdf] + */ + +/* + * KanjiCode::judge() is based on judge_jcode() from jvim + * http://hp.vector.co.jp/authors/VA003457/vim/ + * + * Special Thanks to Kenichi Tsuchida + */ + +enum KanjiCode::Type KanjiCode::judge(const char* str, int size) +{ + enum Type code; + int i; + int bfr = false; /* Kana Moji */ + int bfk = 0; /* EUC Kana */ + int sjis = 0; + int euc = 0; + + const unsigned char* ptr = reinterpret_cast<const unsigned char*>(str); + + code = ASCII; + + i = 0; + while (i < size) { + if (ptr[i] == ESC && (size - i >= 3)) { + if ((ptr[i + 1] == '$' && ptr[i + 2] == 'B') + || (ptr[i + 1] == '(' && ptr[i + 2] == 'B')) { + code = JIS; + goto breakBreak; + } else if ((ptr[i + 1] == '$' && ptr[i + 2] == '@') + || (ptr[i + 1] == '(' && ptr[i + 2] == 'J')) { + code = JIS; + goto breakBreak; + } else if (ptr[i + 1] == '(' && ptr[i + 2] == 'I') { + code = JIS; + i += 3; + } else if (ptr[i + 1] == ')' && ptr[i + 2] == 'I') { + code = JIS; + i += 3; + } else { + i++; + } + bfr = false; + bfk = 0; + } else { + if (ptr[i] < 0x20) { + bfr = false; + bfk = 0; + /* ?? check kudokuten ?? && ?? hiragana ?? */ + if ((i >= 2) && (ptr[i - 2] == 0x81) + && (0x41 <= ptr[i - 1] && ptr[i - 1] <= 0x49)) { + code = SJIS; + sjis += 100; /* kudokuten */ + } else if ((i >= 2) && (ptr[i - 2] == 0xa1) + && (0xa2 <= ptr[i - 1] && ptr[i - 1] <= 0xaa)) { + code = EUC; + euc += 100; /* kudokuten */ + } else if ((i >= 2) && (ptr[i - 2] == 0x82) && (0xa0 <= ptr[i - 1])) { + sjis += 40; /* hiragana */ + } else if ((i >= 2) && (ptr[i - 2] == 0xa4) && (0xa0 <= ptr[i - 1])) { + euc += 40; /* hiragana */ + } + } else { + /* ?? check hiragana or katana ?? */ + if ((size - i > 1) && (ptr[i] == 0x82) && (0xa0 <= ptr[i + 1])) { + sjis++; /* hiragana */ + } else if ((size - i > 1) && (ptr[i] == 0x83) + && (0x40 <= ptr[i + 1] && ptr[i + 1] <= 0x9f)) { + sjis++; /* katakana */ + } else if ((size - i > 1) && (ptr[i] == 0xa4) && (0xa0 <= ptr[i + 1])) { + euc++; /* hiragana */ + } else if ((size - i > 1) && (ptr[i] == 0xa5) && (0xa0 <= ptr[i + 1])) { + euc++; /* katakana */ + } + if (bfr) { + if ((i >= 1) && (0x40 <= ptr[i] && ptr[i] <= 0xa0) && ISkanji(ptr[i - 1])) { + code = SJIS; + goto breakBreak; + } else if ((i >= 1) && (0x81 <= ptr[i - 1] && ptr[i - 1] <= 0x9f) && ((0x40 <= ptr[i] && ptr[i] < 0x7e) || (0x7e < ptr[i] && ptr[i] <= 0xfc))) { + code = SJIS; + goto breakBreak; + } else if ((i >= 1) && (0xfd <= ptr[i] && ptr[i] <= 0xfe) && (0xa1 <= ptr[i - 1] && ptr[i - 1] <= 0xfe)) { + code = EUC; + goto breakBreak; + } else if ((i >= 1) && (0xfd <= ptr[i - 1] && ptr[i - 1] <= 0xfe) && (0xa1 <= ptr[i] && ptr[i] <= 0xfe)) { + code = EUC; + goto breakBreak; + } else if ((i >= 1) && (ptr[i] < 0xa0 || 0xdf < ptr[i]) && (0x8e == ptr[i - 1])) { + code = SJIS; + goto breakBreak; + } else if (ptr[i] <= 0x7f) { + code = SJIS; + goto breakBreak; + } else { + if (0xa1 <= ptr[i] && ptr[i] <= 0xa6) { + euc++; /* sjis hankaku kana kigo */ + } else if (0xa1 <= ptr[i] && ptr[i] <= 0xdf) { + ; /* sjis hankaku kana */ + } else if (0xa1 <= ptr[i] && ptr[i] <= 0xfe) { + euc++; + } else if (0x8e == ptr[i]) { + euc++; + } else if (0x20 <= ptr[i] && ptr[i] <= 0x7f) { + sjis++; + } + bfr = false; + bfk = 0; + } + } else if (0x8e == ptr[i]) { + if (size - i <= 1) { + ; + } else if (0xa1 <= ptr[i + 1] && ptr[i + 1] <= 0xdf) { + /* EUC KANA or SJIS KANJI */ + if (bfk == 1) { + euc += 100; + } + bfk++; + i++; + } else { + /* SJIS only */ + code = SJIS; + goto breakBreak; + } + } else if (0x81 <= ptr[i] && ptr[i] <= 0x9f) { + /* SJIS only */ + code = SJIS; + if ((size - i >= 1) + && ((0x40 <= ptr[i + 1] && ptr[i + 1] <= 0x7e) + || (0x80 <= ptr[i + 1] && ptr[i + 1] <= 0xfc))) { + goto breakBreak; + } + } else if (0xfd <= ptr[i] && ptr[i] <= 0xfe) { + /* EUC only */ + code = EUC; + if ((size - i >= 1) + && (0xa1 <= ptr[i + 1] && ptr[i + 1] <= 0xfe)) { + goto breakBreak; + } + } else if (ptr[i] <= 0x7f) { + ; + } else { + bfr = true; + bfk = 0; + } + } + i++; + } + } + if (code == ASCII) { + if (sjis > euc) { + code = SJIS; + } else if (sjis < euc) { + code = EUC; + } + } +breakBreak: + return (code); +} + +TextResourceDecoder::ContentType TextResourceDecoder::determineContentType(const String& mimeType) +{ + if (equalIgnoringCase(mimeType, "text/css")) + return CSS; + if (equalIgnoringCase(mimeType, "text/html")) + return HTML; + if (DOMImplementation::isXMLMIMEType(mimeType)) + return XML; + return PlainText; +} + +const TextEncoding& TextResourceDecoder::defaultEncoding(ContentType contentType, const TextEncoding& specifiedDefaultEncoding) +{ + // Despite 8.5 "Text/xml with Omitted Charset" of RFC 3023, we assume UTF-8 instead of US-ASCII + // for text/xml. This matches Firefox. + if (contentType == XML) + return UTF8Encoding(); + if (!specifiedDefaultEncoding.isValid()) + return Latin1Encoding(); + return specifiedDefaultEncoding; +} + +TextResourceDecoder::TextResourceDecoder(const String& mimeType, const TextEncoding& specifiedDefaultEncoding) + : m_contentType(determineContentType(mimeType)) + , m_decoder(defaultEncoding(m_contentType, specifiedDefaultEncoding)) + , m_source(DefaultEncoding) + , m_checkedForBOM(false) + , m_checkedForCSSCharset(false) + , m_checkedForHeadCharset(false) + , m_sawError(false) +{ +} + +TextResourceDecoder::~TextResourceDecoder() +{ +} + +void TextResourceDecoder::setEncoding(const TextEncoding& encoding, EncodingSource source) +{ + // In case the encoding didn't exist, we keep the old one (helps some sites specifying invalid encodings). + if (!encoding.isValid()) + return; + + // When encoding comes from meta tag (i.e. it cannot be XML files sent via XHR), + // treat x-user-defined as windows-1252 (bug 18270) + if (source == EncodingFromMetaTag && strcasecmp(encoding.name(), "x-user-defined") == 0) + m_decoder.reset("windows-1252"); + else if (source == EncodingFromMetaTag || source == EncodingFromXMLHeader || source == EncodingFromCSSCharset) + m_decoder.reset(encoding.closest8BitEquivalent()); + else + m_decoder.reset(encoding); + + m_source = source; +} + +// Returns the position of the encoding string. +static int findXMLEncoding(const char* str, int len, int& encodingLength) +{ + int pos = find(str, len, "encoding"); + if (pos == -1) + return -1; + pos += 8; + + // Skip spaces and stray control characters. + while (pos < len && str[pos] <= ' ') + ++pos; + + // Skip equals sign. + if (pos >= len || str[pos] != '=') + return -1; + ++pos; + + // Skip spaces and stray control characters. + while (pos < len && str[pos] <= ' ') + ++pos; + + // Skip quotation mark. + if (pos >= len) + return - 1; + char quoteMark = str[pos]; + if (quoteMark != '"' && quoteMark != '\'') + return -1; + ++pos; + + // Find the trailing quotation mark. + int end = pos; + while (end < len && str[end] != quoteMark) + ++end; + if (end >= len) + return -1; + + encodingLength = end - pos; + return pos; +} + +// true if there is more to parse +static inline bool skipWhitespace(const char*& pos, const char* dataEnd) +{ + while (pos < dataEnd && (*pos == '\t' || *pos == ' ')) + ++pos; + return pos != dataEnd; +} + +void TextResourceDecoder::checkForBOM(const char* data, size_t len) +{ + // Check for UTF-16/32 or UTF-8 BOM mark at the beginning, which is a sure sign of a Unicode encoding. + + if (m_source == UserChosenEncoding) { + // FIXME: Maybe a BOM should override even a user-chosen encoding. + m_checkedForBOM = true; + return; + } + + // Check if we have enough data. + size_t bufferLength = m_buffer.size(); + if (bufferLength + len < 4) + return; + + m_checkedForBOM = true; + + // Extract the first four bytes. + // Handle the case where some of bytes are already in the buffer. + // The last byte is always guaranteed to not be in the buffer. + const unsigned char* udata = reinterpret_cast<const unsigned char*>(data); + unsigned char c1 = bufferLength >= 1 ? m_buffer[0] : *udata++; + unsigned char c2 = bufferLength >= 2 ? m_buffer[1] : *udata++; + unsigned char c3 = bufferLength >= 3 ? m_buffer[2] : *udata++; + ASSERT(bufferLength < 4); + unsigned char c4 = *udata; + + // Check for the BOM. + if (c1 == 0xFF && c2 == 0xFE) { + if (c3 !=0 || c4 != 0) + setEncoding(UTF16LittleEndianEncoding(), AutoDetectedEncoding); + else + setEncoding(UTF32LittleEndianEncoding(), AutoDetectedEncoding); + } + else if (c1 == 0xEF && c2 == 0xBB && c3 == 0xBF) + setEncoding(UTF8Encoding(), AutoDetectedEncoding); + else if (c1 == 0xFE && c2 == 0xFF) + setEncoding(UTF16BigEndianEncoding(), AutoDetectedEncoding); + else if (c1 == 0 && c2 == 0 && c3 == 0xFE && c4 == 0xFF) + setEncoding(UTF32BigEndianEncoding(), AutoDetectedEncoding); +} + +bool TextResourceDecoder::checkForCSSCharset(const char* data, size_t len, bool& movedDataToBuffer) +{ + if (m_source != DefaultEncoding) { + m_checkedForCSSCharset = true; + return true; + } + + size_t oldSize = m_buffer.size(); + m_buffer.grow(oldSize + len); + memcpy(m_buffer.data() + oldSize, data, len); + + movedDataToBuffer = true; + + if (m_buffer.size() > 8) { // strlen("@charset") == 8 + const char* dataStart = m_buffer.data(); + const char* dataEnd = dataStart + m_buffer.size(); + + if (dataStart[0] == '@' && dataStart[1] == 'c' && dataStart[2] == 'h' && dataStart[3] == 'a' && dataStart[4] == 'r' && + dataStart[5] == 's' && dataStart[6] == 'e' && dataStart[7] == 't') { + + dataStart += 8; + const char* pos = dataStart; + if (!skipWhitespace(pos, dataEnd)) + return false; + + if (*pos == '"' || *pos == '\'') { + char quotationMark = *pos; + ++pos; + dataStart = pos; + + while (pos < dataEnd && *pos != quotationMark) + ++pos; + if (pos == dataEnd) + return false; + + int encodingNameLength = pos - dataStart + 1; + + ++pos; + if (!skipWhitespace(pos, dataEnd)) + return false; + + if (*pos == ';') + setEncoding(findTextEncoding(dataStart, encodingNameLength), EncodingFromCSSCharset); + } + } + m_checkedForCSSCharset = true; + return true; + } + return false; +} + +// Other browsers allow comments in the head section, so we need to also. +// It's important not to look for tags inside the comments. +static inline void skipComment(const char*& ptr, const char* pEnd) +{ + const char* p = ptr; + // Allow <!-->; other browsers do. + if (*p == '>') { + p++; + } else { + while (p != pEnd) { + if (*p == '-') { + // This is the real end of comment, "-->". + if (p[1] == '-' && p[2] == '>') { + p += 3; + break; + } + // This is the incorrect end of comment that other browsers allow, "--!>". + if (p[1] == '-' && p[2] == '!' && p[3] == '>') { + p += 4; + break; + } + } + p++; + } + } + ptr = p; +} + +const int bytesToCheckUnconditionally = 1024; // That many input bytes will be checked for meta charset even if <head> section is over. + +bool TextResourceDecoder::checkForHeadCharset(const char* data, size_t len, bool& movedDataToBuffer) +{ + if (m_source != DefaultEncoding) { + m_checkedForHeadCharset = true; + return true; + } + + // This is not completely efficient, since the function might go + // through the HTML head several times. + + size_t oldSize = m_buffer.size(); + m_buffer.grow(oldSize + len); + memcpy(m_buffer.data() + oldSize, data, len); + + movedDataToBuffer = true; + + const char* ptr = m_buffer.data(); + const char* pEnd = ptr + m_buffer.size(); + + // Is there enough data available to check for XML declaration? + if (m_buffer.size() < 8) + return false; + + // Handle XML declaration, which can have encoding in it. This encoding is honored even for HTML documents. + // It is an error for an XML declaration not to be at the start of an XML document, and it is ignored in HTML documents in such case. + if (ptr[0] == '<' && ptr[1] == '?' && ptr[2] == 'x' && ptr[3] == 'm' && ptr[4] == 'l') { + const char* xmlDeclarationEnd = ptr; + while (xmlDeclarationEnd != pEnd && *xmlDeclarationEnd != '>') + ++xmlDeclarationEnd; + if (xmlDeclarationEnd == pEnd) + return false; + // No need for +1, because we have an extra "?" to lose at the end of XML declaration. + int len; + int pos = findXMLEncoding(ptr, xmlDeclarationEnd - ptr, len); + if (pos != -1) + setEncoding(findTextEncoding(ptr + pos, len), EncodingFromXMLHeader); + // continue looking for a charset - it may be specified in an HTTP-Equiv meta + } else if (ptr[0] == '<' && ptr[1] == 0 && ptr[2] == '?' && ptr[3] == 0 && ptr[4] == 'x' && ptr[5] == 0) { + setEncoding(UTF16LittleEndianEncoding(), AutoDetectedEncoding); + return true; + } else if (ptr[0] == 0 && ptr[1] == '<' && ptr[2] == 0 && ptr[3] == '?' && ptr[4] == 0 && ptr[5] == 'x') { + setEncoding(UTF16BigEndianEncoding(), AutoDetectedEncoding); + return true; + } else if (ptr[0] == '<' && ptr[1] == 0 && ptr[2] == 0 && ptr[3] == 0 && ptr[4] == '?' && ptr[5] == 0 && ptr[6] == 0 && ptr[7] == 0) { + setEncoding(UTF32LittleEndianEncoding(), AutoDetectedEncoding); + return true; + } else if (ptr[0] == 0 && ptr[1] == 0 && ptr[2] == 0 && ptr[3] == '<' && ptr[4] == 0 && ptr[5] == 0 && ptr[6] == 0 && ptr[7] == '?') { + setEncoding(UTF32BigEndianEncoding(), AutoDetectedEncoding); + return true; + } + + // we still don't have an encoding, and are in the head + // the following tags are allowed in <head>: + // SCRIPT|STYLE|META|LINK|OBJECT|TITLE|BASE + + // We stop scanning when a tag that is not permitted in <head> + // is seen, rather when </head> is seen, because that more closely + // matches behavior in other browsers; more details in + // <http://bugs.webkit.org/show_bug.cgi?id=3590>. + + // Additionally, we ignore things that looks like tags in <title>, <script> and <noscript>; see + // <http://bugs.webkit.org/show_bug.cgi?id=4560>, <http://bugs.webkit.org/show_bug.cgi?id=12165> + // and <http://bugs.webkit.org/show_bug.cgi?id=12389>. + + // Since many sites have charset declarations after <body> or other tags that are disallowed in <head>, + // we don't bail out until we've checked at least bytesToCheckUnconditionally bytes of input. + + AtomicStringImpl* enclosingTagName = 0; + bool inHeadSection = true; // Becomes false when </head> or any tag not allowed in head is encountered. + + // the HTTP-EQUIV meta has no effect on XHTML + if (m_contentType == XML) + return true; + + while (ptr + 3 < pEnd) { // +3 guarantees that "<!--" fits in the buffer - and certainly we aren't going to lose any "charset" that way. + if (*ptr == '<') { + bool end = false; + ptr++; + + // Handle comments. + if (ptr[0] == '!' && ptr[1] == '-' && ptr[2] == '-') { + ptr += 3; + skipComment(ptr, pEnd); + if (ptr - m_buffer.data() >= bytesToCheckUnconditionally && !inHeadSection) { + // Some pages that test bandwidth from within the browser do it by having + // huge comments and measuring the time they take to load. Repeatedly scanning + // these comments can take a lot of CPU time. + m_checkedForHeadCharset = true; + return true; + } + continue; + } + + if (*ptr == '/') { + ++ptr; + end = true; + } + + // Grab the tag name, but mostly ignore namespaces. + bool sawNamespace = false; + char tagBuffer[20]; + int len = 0; + while (len < 19) { + if (ptr == pEnd) + return false; + char c = *ptr; + if (c == ':') { + len = 0; + sawNamespace = true; + ptr++; + continue; + } + if (c >= 'a' && c <= 'z' || c >= '0' && c <= '9') + ; + else if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + else + break; + tagBuffer[len++] = c; + ptr++; + } + tagBuffer[len] = 0; + AtomicString tag(tagBuffer); + + if (enclosingTagName) { + if (end && tag.impl() == enclosingTagName) + enclosingTagName = 0; + } else { + if (tag == titleTag) + enclosingTagName = titleTag.localName().impl(); + else if (tag == scriptTag) + enclosingTagName = scriptTag.localName().impl(); + else if (tag == noscriptTag) + enclosingTagName = noscriptTag.localName().impl(); + } + + // Find where the opening tag ends. + const char* tagContentStart = ptr; + if (!end) { + while (ptr != pEnd && *ptr != '>') { + if (*ptr == '\'' || *ptr == '"') { + char quoteMark = *ptr; + ++ptr; + while (ptr != pEnd && *ptr != quoteMark) + ++ptr; + if (ptr == pEnd) + return false; + } + ++ptr; + } + if (ptr == pEnd) + return false; + ++ptr; + } + + if (!end && tag == metaTag && !sawNamespace) { + const char* str = tagContentStart; + int length = ptr - tagContentStart; + int pos = 0; + while (pos < length) { + int charsetPos = findIgnoringCase(str + pos, length - pos, "charset"); + if (charsetPos == -1) + break; + pos += charsetPos + 7; + // skip whitespace + while (pos < length && str[pos] <= ' ') + pos++; + if (pos == length) + break; + if (str[pos++] != '=') + continue; + while (pos < length && + (str[pos] <= ' ') || str[pos] == '=' || str[pos] == '"' || str[pos] == '\'') + pos++; + + // end ? + if (pos == length) + break; + int end = pos; + while (end < length && + str[end] != ' ' && str[end] != '"' && str[end] != '\'' && + str[end] != ';' && str[end] != '>') + end++; + setEncoding(findTextEncoding(str + pos, end - pos), EncodingFromMetaTag); + if (m_source == EncodingFromMetaTag) + return true; + + if (end >= length || str[end] == '/' || str[end] == '>') + break; + + pos = end + 1; + } + } else { + if (!enclosingTagName && tag != scriptTag && tag != noscriptTag && tag != styleTag + && tag != linkTag && tag != metaTag && tag != objectTag && tag != titleTag && tag != baseTag + && (end || tag != htmlTag) && (end || tag != headTag) && isASCIIAlpha(tagBuffer[0])) { + inHeadSection = false; + } + + if (ptr - m_buffer.data() >= bytesToCheckUnconditionally && !inHeadSection) { + m_checkedForHeadCharset = true; + return true; + } + } + } else + ++ptr; + } + return false; +} + +void TextResourceDecoder::detectJapaneseEncoding(const char* data, size_t len) +{ + switch (KanjiCode::judge(data, len)) { + case KanjiCode::JIS: + setEncoding("ISO-2022-JP", AutoDetectedEncoding); + break; + case KanjiCode::EUC: + setEncoding("EUC-JP", AutoDetectedEncoding); + break; + case KanjiCode::SJIS: + setEncoding("Shift_JIS", AutoDetectedEncoding); + break; + case KanjiCode::ASCII: + case KanjiCode::UTF16: + case KanjiCode::UTF8: + break; + } +} + +String TextResourceDecoder::decode(const char* data, size_t len) +{ + if (!m_checkedForBOM) + checkForBOM(data, len); + + bool movedDataToBuffer = false; + + if (m_contentType == CSS && !m_checkedForCSSCharset) + if (!checkForCSSCharset(data, len, movedDataToBuffer)) + return ""; + + if ((m_contentType == HTML || m_contentType == XML) && !m_checkedForHeadCharset) // HTML and XML + if (!checkForHeadCharset(data, len, movedDataToBuffer)) + return ""; + + // Do the auto-detect if our default encoding is one of the Japanese ones. + // FIXME: It seems wrong to change our encoding downstream after we have already done some decoding. + if (m_source != UserChosenEncoding && m_source != AutoDetectedEncoding && encoding().isJapanese()) + detectJapaneseEncoding(data, len); + + ASSERT(encoding().isValid()); + + if (m_buffer.isEmpty()) + return m_decoder.decode(data, len, false, m_contentType == XML, m_sawError); + + if (!movedDataToBuffer) { + size_t oldSize = m_buffer.size(); + m_buffer.grow(oldSize + len); + memcpy(m_buffer.data() + oldSize, data, len); + } + + String result = m_decoder.decode(m_buffer.data(), m_buffer.size(), false, m_contentType == XML, m_sawError); + m_buffer.clear(); + return result; +} + +String TextResourceDecoder::flush() +{ + String result = m_decoder.decode(m_buffer.data(), m_buffer.size(), true, m_contentType == XML, m_sawError); + m_buffer.clear(); + return result; +} + +} diff --git a/WebCore/loader/TextResourceDecoder.h b/WebCore/loader/TextResourceDecoder.h new file mode 100644 index 0000000..8bbe85e --- /dev/null +++ b/WebCore/loader/TextResourceDecoder.h @@ -0,0 +1,80 @@ +/* + Copyright (C) 1999 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com) + Copyright (C) 2006, 2008 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#ifndef TextResourceDecoder_h +#define TextResourceDecoder_h + +#include "TextDecoder.h" + +namespace WebCore { + +class TextResourceDecoder : public RefCounted<TextResourceDecoder> { +public: + enum EncodingSource { + DefaultEncoding, + AutoDetectedEncoding, + EncodingFromXMLHeader, + EncodingFromMetaTag, + EncodingFromCSSCharset, + EncodingFromHTTPHeader, + UserChosenEncoding + }; + + static PassRefPtr<TextResourceDecoder> create(const String& mimeType, const TextEncoding& defaultEncoding = TextEncoding()) + { + return adoptRef(new TextResourceDecoder(mimeType, defaultEncoding)); + } + ~TextResourceDecoder(); + + void setEncoding(const TextEncoding&, EncodingSource); + const TextEncoding& encoding() const { return m_decoder.encoding(); } + + String decode(const char* data, size_t length); + String flush(); + + bool sawError() const { return m_sawError; } + +private: + TextResourceDecoder(const String& mimeType, const TextEncoding& defaultEncoding); + + enum ContentType { PlainText, HTML, XML, CSS }; // PlainText is equivalent to directly using TextDecoder. + static ContentType determineContentType(const String& mimeType); + static const TextEncoding& defaultEncoding(ContentType, const TextEncoding& defaultEncoding); + + void checkForBOM(const char*, size_t); + bool checkForCSSCharset(const char*, size_t, bool& movedDataToBuffer); + bool checkForHeadCharset(const char*, size_t, bool& movedDataToBuffer); + void detectJapaneseEncoding(const char*, size_t); + + ContentType m_contentType; + TextDecoder m_decoder; + EncodingSource m_source; + Vector<char> m_buffer; + bool m_checkedForBOM; + bool m_checkedForCSSCharset; + bool m_checkedForHeadCharset; + bool m_sawError; +}; + +} + +#endif diff --git a/WebCore/loader/UserStyleSheetLoader.cpp b/WebCore/loader/UserStyleSheetLoader.cpp new file mode 100644 index 0000000..3afbc15 --- /dev/null +++ b/WebCore/loader/UserStyleSheetLoader.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "UserStyleSheetLoader.h" + +#include "CachedCSSStyleSheet.h" +#include "DocLoader.h" +#include "Frame.h" + +using namespace WebCore; + +UserStyleSheetLoader::UserStyleSheetLoader(PassRefPtr<Document> document, const String& url) + : m_document(document) + , m_cachedSheet(m_document->docLoader()->requestUserCSSStyleSheet(url, "")) +{ + if (m_cachedSheet) { + m_document->addPendingSheet(); + m_cachedSheet->addClient(this); + } +} + +UserStyleSheetLoader::~UserStyleSheetLoader() +{ + if (m_cachedSheet) { + if (!m_cachedSheet->isLoaded()) + m_document->removePendingSheet(); + m_cachedSheet->removeClient(this); + } +} + +void UserStyleSheetLoader::setCSSStyleSheet(const String& /*URL*/, const String& /*charset*/, const CachedCSSStyleSheet* sheet) +{ + m_document->removePendingSheet(); + if (Frame* frame = m_document->frame()) + frame->setUserStyleSheet(sheet->sheetText()); +} diff --git a/WebCore/loader/UserStyleSheetLoader.h b/WebCore/loader/UserStyleSheetLoader.h new file mode 100644 index 0000000..6e7a1ba --- /dev/null +++ b/WebCore/loader/UserStyleSheetLoader.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 UserStyleSheetLoader_h +#define UserStyleSheetLoader_h + +#include "CachedResourceClient.h" +#include "CachedResourceHandle.h" + +#include "Document.h" + +namespace WebCore { + class CachedCSSStyleSheet; + class String; + + // This class is deprecated and should not be used in any new code. User + // stylesheet loading should instead happen through Page. + class UserStyleSheetLoader : CachedResourceClient { + public: + UserStyleSheetLoader(PassRefPtr<Document>, const String& url); + ~UserStyleSheetLoader(); + + private: + virtual void setCSSStyleSheet(const String& URL, const String& charset, const CachedCSSStyleSheet* sheet); + + RefPtr<Document> m_document; + CachedResourceHandle<CachedCSSStyleSheet> m_cachedSheet; + }; + +} // namespace WebCore + +#endif // UserStyleSheetLoader_h diff --git a/WebCore/loader/appcache/ApplicationCache.cpp b/WebCore/loader/appcache/ApplicationCache.cpp new file mode 100644 index 0000000..d29eda8 --- /dev/null +++ b/WebCore/loader/appcache/ApplicationCache.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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. + */ + +#include "config.h" +#include "ApplicationCache.h" + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "ApplicationCacheGroup.h" +#include "ApplicationCacheResource.h" +#include "ApplicationCacheStorage.h" +#include "ResourceRequest.h" +#include <stdio.h> + +namespace WebCore { + +ApplicationCache::ApplicationCache() + : m_group(0) + , m_manifest(0) + , m_storageID(0) +{ +} + +ApplicationCache::~ApplicationCache() +{ + if (m_group && !m_group->isCopy()) + m_group->cacheDestroyed(this); +} + +void ApplicationCache::setGroup(ApplicationCacheGroup* group) +{ + ASSERT(!m_group); + m_group = group; +} + +void ApplicationCache::setManifestResource(PassRefPtr<ApplicationCacheResource> manifest) +{ + ASSERT(manifest); + ASSERT(!m_manifest); + ASSERT(manifest->type() & ApplicationCacheResource::Manifest); + + m_manifest = manifest.get(); + + addResource(manifest); +} + +void ApplicationCache::addResource(PassRefPtr<ApplicationCacheResource> resource) +{ + ASSERT(resource); + + const String& url = resource->url(); + + ASSERT(!m_resources.contains(url)); + + if (m_storageID) { + ASSERT(!resource->storageID()); + + // Add the resource to the storage. + cacheStorage().store(resource.get(), this); + } + + m_resources.set(url, resource); +} + +unsigned ApplicationCache::removeResource(const String& url) +{ + HashMap<String, RefPtr<ApplicationCacheResource> >::iterator it = m_resources.find(url); + if (it == m_resources.end()) + return 0; + + // The resource exists, get its type so we can return it. + unsigned type = it->second->type(); + + m_resources.remove(it); + + return type; +} + +ApplicationCacheResource* ApplicationCache::resourceForURL(const String& url) +{ + return m_resources.get(url).get(); +} + +bool ApplicationCache::requestIsHTTPOrHTTPSGet(const ResourceRequest& request) +{ + if (!request.url().protocolIs("http") && !request.url().protocolIs("https")) + return false; + + if (!equalIgnoringCase(request.httpMethod(), "GET")) + return false; + + return true; +} + +ApplicationCacheResource* ApplicationCache::resourceForRequest(const ResourceRequest& request) +{ + // We only care about HTTP/HTTPS GET requests. + if (!requestIsHTTPOrHTTPSGet(request)) + return false; + + return resourceForURL(request.url()); +} + +unsigned ApplicationCache::numDynamicEntries() const +{ + // FIXME: Implement + return 0; +} + +String ApplicationCache::dynamicEntry(unsigned index) const +{ + // FIXME: Implement + return String(); +} + +bool ApplicationCache::addDynamicEntry(const String& url) +{ + if (!equalIgnoringCase(m_group->manifestURL().protocol(), + KURL(url).protocol())) + return false; + + // FIXME: Implement + return true; +} + +void ApplicationCache::removeDynamicEntry(const String& url) +{ + // FIXME: Implement +} + +void ApplicationCache::setOnlineWhitelist(const HashSet<String>& onlineWhitelist) +{ + ASSERT(m_onlineWhitelist.isEmpty()); + m_onlineWhitelist = onlineWhitelist; +} + +bool ApplicationCache::isURLInOnlineWhitelist(const KURL& url) +{ + if (!url.hasRef()) + return m_onlineWhitelist.contains(url); + + KURL copy(url); + copy.setRef(String()); + return m_onlineWhitelist.contains(copy); +} + +void ApplicationCache::clearStorageID() +{ + m_storageID = 0; + + ResourceMap::const_iterator end = m_resources.end(); + for (ResourceMap::const_iterator it = m_resources.begin(); it != end; ++it) + it->second->clearStorageID(); +} + +#ifndef NDEBUG +void ApplicationCache::dump() +{ + HashMap<String, RefPtr<ApplicationCacheResource> >::const_iterator end = m_resources.end(); + + for (HashMap<String, RefPtr<ApplicationCacheResource> >::const_iterator it = m_resources.begin(); it != end; ++it) { + printf("%s ", it->first.ascii().data()); + ApplicationCacheResource::dumpType(it->second->type()); + } +} +#endif + +} + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/WebCore/loader/appcache/ApplicationCache.h b/WebCore/loader/appcache/ApplicationCache.h new file mode 100644 index 0000000..e536cbe --- /dev/null +++ b/WebCore/loader/appcache/ApplicationCache.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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 ApplicationCache_h +#define ApplicationCache_h + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> + +#include "StringHash.h" +#include "PlatformString.h" + +namespace WebCore { + +class ApplicationCacheGroup; +class ApplicationCacheResource; +class DocumentLoader; +class KURL; +class ResourceRequest; + +class ApplicationCache : public RefCounted<ApplicationCache> { +public: + static PassRefPtr<ApplicationCache> create() { return adoptRef(new ApplicationCache); } + ~ApplicationCache(); + + void addResource(PassRefPtr<ApplicationCacheResource> resource); + unsigned removeResource(const String& url); + + void setManifestResource(PassRefPtr<ApplicationCacheResource> manifest); + ApplicationCacheResource* manifestResource() const { return m_manifest; } + + void setGroup(ApplicationCacheGroup*); + ApplicationCacheGroup* group() const { return m_group; } + + ApplicationCacheResource* resourceForRequest(const ResourceRequest&); + ApplicationCacheResource* resourceForURL(const String& url); + + unsigned numDynamicEntries() const; + String dynamicEntry(unsigned index) const; + + bool addDynamicEntry(const String& url); + void removeDynamicEntry(const String& url); + + void setOnlineWhitelist(const HashSet<String>& onlineWhitelist); + const HashSet<String>& onlineWhitelist() const { return m_onlineWhitelist; } + bool isURLInOnlineWhitelist(const KURL&); + +#ifndef NDEBUG + void dump(); +#endif + + typedef HashMap<String, RefPtr<ApplicationCacheResource> > ResourceMap; + ResourceMap::const_iterator begin() const { return m_resources.begin(); } + ResourceMap::const_iterator end() const { return m_resources.end(); } + + void setStorageID(unsigned storageID) { m_storageID = storageID; } + unsigned storageID() const { return m_storageID; } + void clearStorageID(); + + static bool requestIsHTTPOrHTTPSGet(const ResourceRequest&); +private: + ApplicationCache(); + + ApplicationCacheGroup* m_group; + ResourceMap m_resources; + ApplicationCacheResource* m_manifest; + + HashSet<String> m_onlineWhitelist; + + unsigned m_storageID; +}; + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // ApplicationCache_h diff --git a/WebCore/loader/appcache/ApplicationCacheGroup.cpp b/WebCore/loader/appcache/ApplicationCacheGroup.cpp new file mode 100644 index 0000000..ff26767 --- /dev/null +++ b/WebCore/loader/appcache/ApplicationCacheGroup.cpp @@ -0,0 +1,715 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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. + */ + +#include "config.h" +#include "ApplicationCacheGroup.h" + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "ApplicationCache.h" +#include "ApplicationCacheResource.h" +#include "ApplicationCacheStorage.h" +#include "DocumentLoader.h" +#include "DOMApplicationCache.h" +#include "DOMWindow.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "MainResourceLoader.h" +#include "ManifestParser.h" +#include "Page.h" +#include "Settings.h" +#include <wtf/HashMap.h> + +namespace WebCore { + +ApplicationCacheGroup::ApplicationCacheGroup(const KURL& manifestURL, bool isCopy) + : m_manifestURL(manifestURL) + , m_status(Idle) + , m_savedNewestCachePointer(0) + , m_frame(0) + , m_storageID(0) + , m_isCopy(isCopy) +{ +} + +ApplicationCacheGroup::~ApplicationCacheGroup() +{ + if (m_isCopy) { + ASSERT(m_newestCache); + ASSERT(m_caches.size() == 1); + ASSERT(m_caches.contains(m_newestCache.get())); + ASSERT(!m_cacheBeingUpdated); + ASSERT(m_associatedDocumentLoaders.isEmpty()); + ASSERT(m_cacheCandidates.isEmpty()); + ASSERT(m_newestCache->group() == this); + + return; + } + + ASSERT(!m_newestCache); + ASSERT(m_caches.isEmpty()); + + stopLoading(); + + cacheStorage().cacheGroupDestroyed(this); +} + +ApplicationCache* ApplicationCacheGroup::cacheForMainRequest(const ResourceRequest& request, DocumentLoader* loader) +{ + if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request)) + return 0; + + ASSERT(loader->frame()); + ASSERT(loader->frame()->page()); + if (loader->frame() != loader->frame()->page()->mainFrame()) + return 0; + + if (ApplicationCacheGroup* group = cacheStorage().cacheGroupForURL(request.url())) { + ASSERT(group->newestCache()); + + return group->newestCache(); + } + + return 0; +} + +void ApplicationCacheGroup::selectCache(Frame* frame, const KURL& manifestURL) +{ + ASSERT(frame && frame->page()); + + if (!frame->settings()->offlineWebApplicationCacheEnabled()) + return; + + DocumentLoader* documentLoader = frame->loader()->documentLoader(); + ASSERT(!documentLoader->applicationCache()); + + if (manifestURL.isNull()) { + selectCacheWithoutManifestURL(frame); + return; + } + + ApplicationCache* mainResourceCache = documentLoader->mainResourceApplicationCache(); + + // Check if the main resource is being loaded as part of navigation of the main frame + bool isMainFrame = frame->page()->mainFrame() == frame; + + if (!isMainFrame) { + if (mainResourceCache && manifestURL != mainResourceCache->group()->manifestURL()) { + ApplicationCacheResource* resource = mainResourceCache->resourceForURL(documentLoader->originalURL()); + ASSERT(resource); + + resource->addType(ApplicationCacheResource::Foreign); + } + + return; + } + + if (mainResourceCache) { + if (manifestURL == mainResourceCache->group()->m_manifestURL) { + mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache); + mainResourceCache->group()->update(frame); + } else { + // FIXME: If the resource being loaded was loaded from an application cache and the URI of + // that application cache's manifest is not the same as the manifest URI with which the algorithm was invoked + // then we should "undo" the navigation. + } + + return; + } + + // The resource was loaded from the network, check if it is a HTTP/HTTPS GET. + const ResourceRequest& request = frame->loader()->activeDocumentLoader()->request(); + + if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request)) { + selectCacheWithoutManifestURL(frame); + return; + } + + // Check that the resource URL has the same scheme/host/port as the manifest URL. + if (!protocolHostAndPortAreEqual(manifestURL, request.url())) { + selectCacheWithoutManifestURL(frame); + return; + } + + ApplicationCacheGroup* group = cacheStorage().findOrCreateCacheGroup(manifestURL); + + if (ApplicationCache* cache = group->newestCache()) { + ASSERT(cache->manifestResource()); + + group->associateDocumentLoaderWithCache(frame->loader()->documentLoader(), cache); + + if (!frame->loader()->documentLoader()->isLoadingMainResource()) + group->finishedLoadingMainResource(frame->loader()->documentLoader()); + + group->update(frame); + } else { + bool isUpdating = group->m_cacheBeingUpdated; + + if (!isUpdating) + group->m_cacheBeingUpdated = ApplicationCache::create(); + documentLoader->setCandidateApplicationCacheGroup(group); + group->m_cacheCandidates.add(documentLoader); + + const KURL& url = frame->loader()->documentLoader()->originalURL(); + + unsigned type = 0; + + // If the resource has already been downloaded, remove it so that it will be replaced with the implicit resource + if (isUpdating) + type = group->m_cacheBeingUpdated->removeResource(url); + + // Add the main resource URL as an implicit entry. + group->addEntry(url, type | ApplicationCacheResource::Implicit); + + if (!frame->loader()->documentLoader()->isLoadingMainResource()) + group->finishedLoadingMainResource(frame->loader()->documentLoader()); + + if (!isUpdating) + group->update(frame); + } +} + +void ApplicationCacheGroup::selectCacheWithoutManifestURL(Frame* frame) +{ + if (!frame->settings()->offlineWebApplicationCacheEnabled()) + return; + + DocumentLoader* documentLoader = frame->loader()->documentLoader(); + ASSERT(!documentLoader->applicationCache()); + + ApplicationCache* mainResourceCache = documentLoader->mainResourceApplicationCache(); + bool isMainFrame = frame->page()->mainFrame() == frame; + + if (isMainFrame && mainResourceCache) { + mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache); + mainResourceCache->group()->update(frame); + } +} + +void ApplicationCacheGroup::finishedLoadingMainResource(DocumentLoader* loader) +{ + const KURL& url = loader->originalURL(); + + if (ApplicationCache* cache = loader->applicationCache()) { + RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Implicit, loader->mainResourceData()); + cache->addResource(resource.release()); + + if (!m_cacheBeingUpdated) + return; + } + + ASSERT(m_pendingEntries.contains(url)); + + EntryMap::iterator it = m_pendingEntries.find(url); + ASSERT(it->second & ApplicationCacheResource::Implicit); + + RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, loader->response(), it->second, loader->mainResourceData()); + + ASSERT(m_cacheBeingUpdated); + m_cacheBeingUpdated->addResource(resource.release()); + + m_pendingEntries.remove(it); + + checkIfLoadIsComplete(); +} + +void ApplicationCacheGroup::failedLoadingMainResource(DocumentLoader* loader) +{ + ASSERT(m_cacheCandidates.contains(loader) || m_associatedDocumentLoaders.contains(loader)); + + // Note that cacheUpdateFailed() can cause the cache group to be deleted. + cacheUpdateFailed(); +} + +void ApplicationCacheGroup::stopLoading() +{ + if (m_manifestHandle) { + ASSERT(!m_currentHandle); + ASSERT(!m_cacheBeingUpdated); + + m_manifestHandle->setClient(0); + m_manifestHandle->cancel(); + m_manifestHandle = 0; + } + + if (m_currentHandle) { + ASSERT(!m_manifestHandle); + ASSERT(m_cacheBeingUpdated); + + m_currentHandle->setClient(0); + m_currentHandle->cancel(); + m_currentHandle = 0; + } + + m_cacheBeingUpdated = 0; +} + +void ApplicationCacheGroup::documentLoaderDestroyed(DocumentLoader* loader) +{ + HashSet<DocumentLoader*>::iterator it = m_associatedDocumentLoaders.find(loader); + + if (it != m_associatedDocumentLoaders.end()) { + ASSERT(!m_cacheCandidates.contains(loader)); + + m_associatedDocumentLoaders.remove(it); + } else { + ASSERT(m_cacheCandidates.contains(loader)); + m_cacheCandidates.remove(loader); + } + + if (!m_associatedDocumentLoaders.isEmpty() || !m_cacheCandidates.isEmpty()) + return; + + // We should only have the newest cache remaining, or there is an initial cache attempt in progress. + ASSERT(m_caches.size() == 1 || m_cacheBeingUpdated); + + // If a cache update is in progress, stop it. + if (m_caches.size() == 1) { + ASSERT(m_caches.contains(m_newestCache.get())); + + // Release our reference to the newest cache. + m_savedNewestCachePointer = m_newestCache.get(); + + // This could cause us to be deleted. + m_newestCache = 0; + + return; + } + + // There is an initial cache attempt in progress + ASSERT(m_cacheBeingUpdated); + ASSERT(m_caches.size() == 0); + + // Delete ourselves, causing the cache attempt to be stopped. + delete this; +} + +void ApplicationCacheGroup::cacheDestroyed(ApplicationCache* cache) +{ + ASSERT(m_caches.contains(cache)); + + m_caches.remove(cache); + + if (cache != m_savedNewestCachePointer) + cacheStorage().remove(cache); + + if (m_caches.isEmpty()) + delete this; +} + +void ApplicationCacheGroup::setNewestCache(PassRefPtr<ApplicationCache> newestCache) +{ + ASSERT(!m_newestCache); + ASSERT(!m_caches.contains(newestCache.get())); + ASSERT(!newestCache->group()); + + m_newestCache = newestCache; + m_caches.add(m_newestCache.get()); + m_newestCache->setGroup(this); +} + +void ApplicationCacheGroup::update(Frame* frame) +{ + if (m_status == Checking || m_status == Downloading) + return; + + ASSERT(!m_frame); + m_frame = frame; + + m_status = Checking; + + callListenersOnAssociatedDocuments(&DOMApplicationCache::callCheckingListener); + + ASSERT(!m_manifestHandle); + ASSERT(!m_manifestResource); + + // FIXME: Handle defer loading + + ResourceRequest request(m_manifestURL); + m_frame->loader()->applyUserAgent(request); + + m_manifestHandle = ResourceHandle::create(request, this, m_frame, false, true, false); +} + +void ApplicationCacheGroup::didReceiveResponse(ResourceHandle* handle, const ResourceResponse& response) +{ + if (handle == m_manifestHandle) { + didReceiveManifestResponse(response); + return; + } + + ASSERT(handle == m_currentHandle); + + int statusCode = response.httpStatusCode() / 100; + if (statusCode == 4 || statusCode == 5) { + // Note that cacheUpdateFailed() can cause the cache group to be deleted. + cacheUpdateFailed(); + return; + } + + const KURL& url = handle->request().url(); + + ASSERT(!m_currentResource); + ASSERT(m_pendingEntries.contains(url)); + + unsigned type = m_pendingEntries.get(url); + + // If this is an initial cache attempt, we should not get implicit resources delivered here. + if (!m_newestCache) + ASSERT(!(type & ApplicationCacheResource::Implicit)); + + m_currentResource = ApplicationCacheResource::create(url, response, type); +} + +void ApplicationCacheGroup::didReceiveData(ResourceHandle* handle, const char* data, int length, int lengthReceived) +{ + if (handle == m_manifestHandle) { + didReceiveManifestData(data, length); + return; + } + + ASSERT(handle == m_currentHandle); + + ASSERT(m_currentResource); + m_currentResource->data()->append(data, length); +} + +void ApplicationCacheGroup::didFinishLoading(ResourceHandle* handle) +{ + if (handle == m_manifestHandle) { + didFinishLoadingManifest(); + return; + } + + ASSERT(m_currentHandle == handle); + ASSERT(m_pendingEntries.contains(handle->request().url())); + + m_pendingEntries.remove(handle->request().url()); + + ASSERT(m_cacheBeingUpdated); + + m_cacheBeingUpdated->addResource(m_currentResource.release()); + m_currentHandle = 0; + + // Load the next file. + if (!m_pendingEntries.isEmpty()) { + startLoadingEntry(); + return; + } + + checkIfLoadIsComplete(); +} + +void ApplicationCacheGroup::didFail(ResourceHandle* handle, const ResourceError&) +{ + if (handle == m_manifestHandle) { + didFailToLoadManifest(); + return; + } + + // Note that cacheUpdateFailed() can cause the cache group to be deleted. + cacheUpdateFailed(); +} + +void ApplicationCacheGroup::didReceiveManifestResponse(const ResourceResponse& response) +{ + int statusCode = response.httpStatusCode() / 100; + + if (statusCode == 4 || statusCode == 5 || + !equalIgnoringCase(response.mimeType(), "text/cache-manifest")) { + didFailToLoadManifest(); + return; + } + + ASSERT(!m_manifestResource); + ASSERT(m_manifestHandle); + m_manifestResource = ApplicationCacheResource::create(m_manifestHandle->request().url(), response, + ApplicationCacheResource::Manifest); +} + +void ApplicationCacheGroup::didReceiveManifestData(const char* data, int length) +{ + ASSERT(m_manifestResource); + m_manifestResource->data()->append(data, length); +} + +void ApplicationCacheGroup::didFinishLoadingManifest() +{ + if (!m_manifestResource) { + didFailToLoadManifest(); + return; + } + + bool isUpgradeAttempt = m_newestCache; + + m_manifestHandle = 0; + + // Check if the manifest is byte-for-byte identical. + if (isUpgradeAttempt) { + ApplicationCacheResource* newestManifest = m_newestCache->manifestResource(); + ASSERT(newestManifest); + + if (newestManifest->data()->size() == m_manifestResource->data()->size() && + !memcmp(newestManifest->data()->data(), m_manifestResource->data()->data(), newestManifest->data()->size())) { + + callListenersOnAssociatedDocuments(&DOMApplicationCache::callNoUpdateListener); + + m_status = Idle; + m_frame = 0; + m_manifestResource = 0; + return; + } + } + + Manifest manifest; + if (!parseManifest(m_manifestURL, m_manifestResource->data()->data(), m_manifestResource->data()->size(), manifest)) { + didFailToLoadManifest(); + return; + } + + // FIXME: Add the opportunistic caching namespaces and their fallbacks. + + // We have the manifest, now download the resources. + m_status = Downloading; + + callListenersOnAssociatedDocuments(&DOMApplicationCache::callDownloadingListener); + +#ifndef NDEBUG + // We should only have implicit entries. + { + EntryMap::const_iterator end = m_pendingEntries.end(); + for (EntryMap::const_iterator it = m_pendingEntries.begin(); it != end; ++it) + ASSERT(it->second & ApplicationCacheResource::Implicit); + } +#endif + + if (isUpgradeAttempt) { + ASSERT(!m_cacheBeingUpdated); + + m_cacheBeingUpdated = ApplicationCache::create(); + + ApplicationCache::ResourceMap::const_iterator end = m_newestCache->end(); + for (ApplicationCache::ResourceMap::const_iterator it = m_newestCache->begin(); it != end; ++it) { + unsigned type = it->second->type(); + if (type & (ApplicationCacheResource::Opportunistic | ApplicationCacheResource::Implicit | ApplicationCacheResource::Dynamic)) + addEntry(it->first, type); + } + } + + HashSet<String>::const_iterator end = manifest.explicitURLs.end(); + for (HashSet<String>::const_iterator it = manifest.explicitURLs.begin(); it != end; ++it) + addEntry(*it, ApplicationCacheResource::Explicit); + + m_cacheBeingUpdated->setOnlineWhitelist(manifest.onlineWhitelistedURLs); + + startLoadingEntry(); +} + +void ApplicationCacheGroup::cacheUpdateFailed() +{ + stopLoading(); + + callListenersOnAssociatedDocuments(&DOMApplicationCache::callErrorListener); + + m_pendingEntries.clear(); + m_manifestResource = 0; + + while (!m_cacheCandidates.isEmpty()) { + HashSet<DocumentLoader*>::iterator it = m_cacheCandidates.begin(); + + ASSERT((*it)->candidateApplicationCacheGroup() == this); + (*it)->setCandidateApplicationCacheGroup(0); + m_cacheCandidates.remove(it); + } + + m_status = Idle; + m_frame = 0; + + // If there are no associated caches, delete ourselves + if (m_associatedDocumentLoaders.isEmpty()) + delete this; +} + + +void ApplicationCacheGroup::didFailToLoadManifest() +{ + // Note that cacheUpdateFailed() can cause the cache group to be deleted. + cacheUpdateFailed(); +} + +void ApplicationCacheGroup::checkIfLoadIsComplete() +{ + ASSERT(m_cacheBeingUpdated); + + if (m_manifestHandle) + return; + + if (!m_pendingEntries.isEmpty()) + return; + + // We're done + bool isUpgradeAttempt = m_newestCache; + + m_cacheBeingUpdated->setManifestResource(m_manifestResource.release()); + + m_status = Idle; + m_frame = 0; + + Vector<RefPtr<DocumentLoader> > documentLoaders; + + if (isUpgradeAttempt) { + ASSERT(m_cacheCandidates.isEmpty()); + + copyToVector(m_associatedDocumentLoaders, documentLoaders); + } else { + while (!m_cacheCandidates.isEmpty()) { + HashSet<DocumentLoader*>::iterator it = m_cacheCandidates.begin(); + + DocumentLoader* loader = *it; + ASSERT(!loader->applicationCache()); + ASSERT(loader->candidateApplicationCacheGroup() == this); + + associateDocumentLoaderWithCache(loader, m_cacheBeingUpdated.get()); + + documentLoaders.append(loader); + + m_cacheCandidates.remove(it); + } + } + + setNewestCache(m_cacheBeingUpdated.release()); + + // Store the cache + cacheStorage().storeNewestCache(this); + + callListeners(isUpgradeAttempt ? &DOMApplicationCache::callUpdateReadyListener : &DOMApplicationCache::callCachedListener, + documentLoaders); +} + +void ApplicationCacheGroup::startLoadingEntry() +{ + ASSERT(m_cacheBeingUpdated); + + if (m_pendingEntries.isEmpty()) { + checkIfLoadIsComplete(); + return; + } + + EntryMap::const_iterator it = m_pendingEntries.begin(); + + // If this is an initial cache attempt, we do not want to fetch any implicit entries, + // since those are fed to us by the normal loader machinery. + if (!m_newestCache) { + // Get the first URL in the entry table that is not implicit + EntryMap::const_iterator end = m_pendingEntries.end(); + + while (it->second & ApplicationCacheResource::Implicit) { + ++it; + + if (it == end) + return; + } + } + + callListenersOnAssociatedDocuments(&DOMApplicationCache::callProgressListener); + + // FIXME: If this is an upgrade attempt, the newest cache should be used as an HTTP cache. + + ASSERT(!m_currentHandle); + + ResourceRequest request(it->first); + m_frame->loader()->applyUserAgent(request); + + m_currentHandle = ResourceHandle::create(request, this, m_frame, false, true, false); +} + +void ApplicationCacheGroup::addEntry(const String& url, unsigned type) +{ + ASSERT(m_cacheBeingUpdated); + + // Don't add the URL if we already have an implicit resource in the cache + if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) { + ASSERT(resource->type() & ApplicationCacheResource::Implicit); + + resource->addType(type); + return; + } + + // Don't add the URL if it's the same as the manifest URL. + if (m_manifestResource && m_manifestResource->url() == url) { + m_manifestResource->addType(type); + return; + } + + pair<EntryMap::iterator, bool> result = m_pendingEntries.add(url, type); + + if (!result.second) + result.first->second |= type; +} + +void ApplicationCacheGroup::associateDocumentLoaderWithCache(DocumentLoader* loader, ApplicationCache* cache) +{ + loader->setApplicationCache(cache); + + ASSERT(!m_associatedDocumentLoaders.contains(loader)); + m_associatedDocumentLoaders.add(loader); +} + +void ApplicationCacheGroup::callListenersOnAssociatedDocuments(ListenerFunction listenerFunction) +{ + Vector<RefPtr<DocumentLoader> > loaders; + copyToVector(m_associatedDocumentLoaders, loaders); + + callListeners(listenerFunction, loaders); +} + +void ApplicationCacheGroup::callListeners(ListenerFunction listenerFunction, const Vector<RefPtr<DocumentLoader> >& loaders) +{ + for (unsigned i = 0; i < loaders.size(); i++) { + Frame* frame = loaders[i]->frame(); + if (!frame) + continue; + + ASSERT(frame->loader()->documentLoader() == loaders[i]); + DOMWindow* window = frame->domWindow(); + + if (DOMApplicationCache* domCache = window->optionalApplicationCache()) + (domCache->*listenerFunction)(); + } +} + +void ApplicationCacheGroup::clearStorageID() +{ + m_storageID = 0; + + HashSet<ApplicationCache*>::const_iterator end = m_caches.end(); + for (HashSet<ApplicationCache*>::const_iterator it = m_caches.begin(); it != end; ++it) + (*it)->clearStorageID(); +} + + +} + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/WebCore/loader/appcache/ApplicationCacheGroup.h b/WebCore/loader/appcache/ApplicationCacheGroup.h new file mode 100644 index 0000000..d5b7563 --- /dev/null +++ b/WebCore/loader/appcache/ApplicationCacheGroup.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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 ApplicationCacheGroup_h +#define ApplicationCacheGroup_h + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include <wtf/Noncopyable.h> +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> + +#include "KURL.h" +#include "PlatformString.h" +#include "ResourceHandle.h" +#include "ResourceHandleClient.h" +#include "SharedBuffer.h" + +namespace WebCore { + +class ApplicationCache; +class ApplicationCacheResource; +class DOMApplicationCache; +class Document; +class DocumentLoader; +class Frame; + +class ApplicationCacheGroup : Noncopyable, ResourceHandleClient { +public: + ApplicationCacheGroup(const KURL& manifestURL, bool isCopy = false); + ~ApplicationCacheGroup(); + + enum Status { Idle, Checking, Downloading }; + + static ApplicationCache* cacheForMainRequest(const ResourceRequest&, DocumentLoader*); + + static void selectCache(Frame*, const KURL& manifestURL); + static void selectCacheWithoutManifestURL(Frame*); + + const KURL& manifestURL() const { return m_manifestURL; } + Status status() const { return m_status; } + + void setStorageID(unsigned storageID) { m_storageID = storageID; } + unsigned storageID() const { return m_storageID; } + void clearStorageID(); + + void update(Frame*); + void cacheDestroyed(ApplicationCache*); + + ApplicationCache* newestCache() const { return m_newestCache.get(); } + ApplicationCache* savedNewestCachePointer() const { return m_savedNewestCachePointer; } + + void finishedLoadingMainResource(DocumentLoader*); + void failedLoadingMainResource(DocumentLoader*); + void documentLoaderDestroyed(DocumentLoader*); + + void setNewestCache(PassRefPtr<ApplicationCache> newestCache); + + bool isCopy() const { return m_isCopy; } +private: + typedef void (DOMApplicationCache::*ListenerFunction)(); + void callListenersOnAssociatedDocuments(ListenerFunction); + void callListeners(ListenerFunction, const Vector<RefPtr<DocumentLoader> >& loaders); + + virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&); + virtual void didReceiveData(ResourceHandle*, const char*, int, int lengthReceived); + virtual void didFinishLoading(ResourceHandle*); + virtual void didFail(ResourceHandle*, const ResourceError&); + + void didReceiveManifestResponse(const ResourceResponse&); + void didReceiveManifestData(const char*, int); + void didFinishLoadingManifest(); + void didFailToLoadManifest(); + + void startLoadingEntry(); + void checkIfLoadIsComplete(); + void cacheUpdateFailed(); + + void addEntry(const String&, unsigned type); + + void associateDocumentLoaderWithCache(DocumentLoader*, ApplicationCache*); + + void stopLoading(); + + KURL m_manifestURL; + Status m_status; + + // This is the newest cache in the group. + RefPtr<ApplicationCache> m_newestCache; + + // During tear-down we save the pointer to the newest cache to prevent reference cycles. + ApplicationCache* m_savedNewestCachePointer; + + // The caches in this cache group. + HashSet<ApplicationCache*> m_caches; + + // The cache being updated (if any). + RefPtr<ApplicationCache> m_cacheBeingUpdated; + + // When a cache group does not yet have a complete cache, this contains the document loaders + // that should be associated with the cache once it has been downloaded. + HashSet<DocumentLoader*> m_cacheCandidates; + + // These are all the document loaders that are associated with a cache in this group. + HashSet<DocumentLoader*> m_associatedDocumentLoaders; + + // The URLs and types of pending cache entries. + typedef HashMap<String, unsigned> EntryMap; + EntryMap m_pendingEntries; + + // Frame used for fetching resources when updating + Frame* m_frame; + + unsigned m_storageID; + + // Whether this cache group is a copy that's only used for transferring the cache to another file. + bool m_isCopy; + + RefPtr<ResourceHandle> m_currentHandle; + RefPtr<ApplicationCacheResource> m_currentResource; + + RefPtr<ApplicationCacheResource> m_manifestResource; + RefPtr<ResourceHandle> m_manifestHandle; +}; + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // ApplicationCacheGroup_h diff --git a/WebCore/loader/appcache/ApplicationCacheResource.cpp b/WebCore/loader/appcache/ApplicationCacheResource.cpp new file mode 100644 index 0000000..ee82ff5 --- /dev/null +++ b/WebCore/loader/appcache/ApplicationCacheResource.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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. + */ + +#include "config.h" +#include "ApplicationCacheResource.h" +#include <stdio.h> + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +namespace WebCore { + +ApplicationCacheResource::ApplicationCacheResource(const KURL& url, const ResourceResponse& response, unsigned type, PassRefPtr<SharedBuffer> data) + : SubstituteResource(url, response, data) + , m_type(type) + , m_storageID(0) +{ +} + +void ApplicationCacheResource::addType(unsigned type) +{ + ASSERT(!m_storageID); + m_type |= type; +} + +#ifndef NDEBUG +void ApplicationCacheResource::dumpType(unsigned type) +{ + if (type & Implicit) + printf("implicit "); + if (type & Manifest) + printf("manifest "); + if (type & Explicit) + printf("explicit "); + if (type & Foreign) + printf("foreign "); + if (type & Fallback) + printf("fallback "); + if (type & Opportunistic) + printf("opportunistic "); + if (type & Dynamic) + printf("dynamic "); + + printf("\n"); +} +#endif + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/WebCore/loader/appcache/ApplicationCacheResource.h b/WebCore/loader/appcache/ApplicationCacheResource.h new file mode 100644 index 0000000..1d7b853 --- /dev/null +++ b/WebCore/loader/appcache/ApplicationCacheResource.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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 ApplicationCacheResource_h +#define ApplicationCacheResource_h + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "SubstituteResource.h" + +namespace WebCore { + +class ApplicationCacheResource : public SubstituteResource { +public: + enum Type { + Implicit = 1 << 0, + Manifest = 1 << 1, + Explicit = 1 << 2, + Foreign = 1 << 3, + Fallback = 1 << 4, + Opportunistic = 1 << 5, + Dynamic = 1 << 6 + }; + + static PassRefPtr<ApplicationCacheResource> create(const KURL& url, const ResourceResponse& response, unsigned type, PassRefPtr<SharedBuffer> buffer = SharedBuffer::create()) + { + return adoptRef(new ApplicationCacheResource(url, response, type, buffer)); + } + + unsigned type() const { return m_type; } + void addType(unsigned type); + + void setStorageID(unsigned storageID) { m_storageID = storageID; } + unsigned storageID() const { return m_storageID; } + void clearStorageID() { m_storageID = 0; } + +#ifndef NDEBUG + static void dumpType(unsigned type); +#endif + +private: + ApplicationCacheResource(const KURL& url, const ResourceResponse& response, unsigned type, PassRefPtr<SharedBuffer> buffer); + + unsigned m_type; + unsigned m_storageID; +}; + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // ApplicationCacheResource_h diff --git a/WebCore/loader/appcache/ApplicationCacheStorage.cpp b/WebCore/loader/appcache/ApplicationCacheStorage.cpp new file mode 100644 index 0000000..910d00c --- /dev/null +++ b/WebCore/loader/appcache/ApplicationCacheStorage.cpp @@ -0,0 +1,663 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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. + */ + +#include "config.h" +#include "ApplicationCacheStorage.h" + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "ApplicationCache.h" +#include "ApplicationCacheGroup.h" +#include "ApplicationCacheResource.h" +#include "FileSystem.h" +#include "CString.h" +#include "KURL.h" +#include "SQLiteStatement.h" +#include "SQLiteTransaction.h" + +namespace WebCore { + +static unsigned urlHostHash(const KURL& url) +{ + unsigned hostStart = url.hostStart(); + unsigned hostEnd = url.hostEnd(); + + return AlreadyHashed::avoidDeletedValue(StringImpl::computeHash(url.string().characters() + hostStart, hostEnd - hostStart)); +} + +ApplicationCacheGroup* ApplicationCacheStorage::loadCacheGroup(const KURL& manifestURL) +{ + openDatabase(false); + if (!m_database.isOpen()) + return 0; + + SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL AND manifestURL=?"); + if (statement.prepare() != SQLResultOk) + return 0; + + statement.bindText(1, manifestURL); + + int result = statement.step(); + if (result == SQLResultDone) + return 0; + + if (result != SQLResultRow) { + LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg()); + return 0; + } + + unsigned newestCacheStorageID = (unsigned)statement.getColumnInt64(2); + + RefPtr<ApplicationCache> cache = loadCache(newestCacheStorageID); + if (!cache) + return 0; + + ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL); + + group->setStorageID((unsigned)statement.getColumnInt64(0)); + group->setNewestCache(cache.release()); + + return group; +} + +ApplicationCacheGroup* ApplicationCacheStorage::findOrCreateCacheGroup(const KURL& manifestURL) +{ + std::pair<CacheGroupMap::iterator, bool> result = m_cachesInMemory.add(manifestURL, 0); + + if (!result.second) { + ASSERT(result.first->second); + + return result.first->second; + } + + // Look up the group in the database + ApplicationCacheGroup* group = loadCacheGroup(manifestURL); + + // If the group was not found we need to create it + if (!group) { + group = new ApplicationCacheGroup(manifestURL); + m_cacheHostSet.add(urlHostHash(manifestURL)); + } + + result.first->second = group; + + return group; +} + +void ApplicationCacheStorage::loadManifestHostHashes() +{ + static bool hasLoadedHashes = false; + + if (hasLoadedHashes) + return; + + // We set this flag to true before the database has been opened + // to avoid trying to open the database over and over if it doesn't exist. + hasLoadedHashes = true; + + openDatabase(false); + if (!m_database.isOpen()) + return; + + // Fetch the host hashes. + SQLiteStatement statement(m_database, "SELECT manifestHostHash FROM CacheGroups"); + if (statement.prepare() != SQLResultOk) + return; + + int result; + while ((result = statement.step()) == SQLResultRow) + m_cacheHostSet.add((unsigned)statement.getColumnInt64(0)); +} + +ApplicationCacheGroup* ApplicationCacheStorage::cacheGroupForURL(const KURL& url) +{ + loadManifestHostHashes(); + + // Hash the host name and see if there's a manifest with the same host. + if (!m_cacheHostSet.contains(urlHostHash(url))) + return 0; + + // Check if a cache already exists in memory. + CacheGroupMap::const_iterator end = m_cachesInMemory.end(); + for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) { + ApplicationCacheGroup* group = it->second; + + if (!protocolHostAndPortAreEqual(url, group->manifestURL())) + continue; + + if (ApplicationCache* cache = group->newestCache()) { + if (cache->resourceForURL(url)) + return group; + } + } + + if (!m_database.isOpen()) + return 0; + + // Check the database. Look for all cache groups with a newest cache. + SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL"); + if (statement.prepare() != SQLResultOk) + return 0; + + int result; + while ((result = statement.step()) == SQLResultRow) { + KURL manifestURL = KURL(statement.getColumnText(1)); + + if (!protocolHostAndPortAreEqual(url, manifestURL)) + continue; + + // We found a cache group that matches. Now check if the newest cache has a resource with + // a matching URL. + unsigned newestCacheID = (unsigned)statement.getColumnInt64(2); + RefPtr<ApplicationCache> cache = loadCache(newestCacheID); + + if (!cache->resourceForURL(url)) + continue; + + ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL); + + group->setStorageID((unsigned)statement.getColumnInt64(0)); + group->setNewestCache(cache.release()); + + ASSERT(!m_cachesInMemory.contains(manifestURL)); + m_cachesInMemory.set(group->manifestURL(), group); + + return group; + } + + if (result != SQLResultDone) + LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg()); + + return 0; +} + +void ApplicationCacheStorage::cacheGroupDestroyed(ApplicationCacheGroup* group) +{ + ASSERT(m_cachesInMemory.get(group->manifestURL()) == group); + + m_cachesInMemory.remove(group->manifestURL()); + + // If the cache is half-created, we don't want it in the saved set. + if (!group->savedNewestCachePointer()) + m_cacheHostSet.remove(urlHostHash(group->manifestURL())); +} + +void ApplicationCacheStorage::setCacheDirectory(const String& cacheDirectory) +{ + ASSERT(m_cacheDirectory.isNull()); + ASSERT(!cacheDirectory.isNull()); + + m_cacheDirectory = cacheDirectory; +} + +bool ApplicationCacheStorage::executeSQLCommand(const String& sql) +{ + ASSERT(m_database.isOpen()); + + bool result = m_database.executeCommand(sql); + if (!result) + LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"", + sql.utf8().data(), m_database.lastErrorMsg()); + + return result; +} + +static const int SchemaVersion = 2; + +void ApplicationCacheStorage::verifySchemaVersion() +{ + if (m_database.tableExists("SchemaVersion")) { + int version = SQLiteStatement(m_database, "SELECT version from SchemaVersion").getColumnInt(0); + + if (version == SchemaVersion) + return; + } + + m_database.clearAllTables(); + + SQLiteTransaction createSchemaVersionTable(m_database); + createSchemaVersionTable.begin(); + + executeSQLCommand("CREATE TABLE SchemaVersion (version INTEGER NOT NULL)"); + SQLiteStatement statement(m_database, "INSERT INTO SchemaVersion (version) VALUES (?)"); + if (statement.prepare() != SQLResultOk) + return; + + statement.bindInt64(1, SchemaVersion); + executeStatement(statement); + createSchemaVersionTable.commit(); +} + +void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist) +{ + if (m_database.isOpen()) + return; + + // The cache directory should never be null, but if it for some weird reason is we bail out. + if (m_cacheDirectory.isNull()) + return; + + String applicationCachePath = pathByAppendingComponent(m_cacheDirectory, "ApplicationCache.db"); + if (!createIfDoesNotExist && !fileExists(applicationCachePath)) + return; + + makeAllDirectories(m_cacheDirectory); + m_database.open(applicationCachePath); + + if (!m_database.isOpen()) + return; + + verifySchemaVersion(); + + // Create tables + executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheGroups (id INTEGER PRIMARY KEY AUTOINCREMENT, " + "manifestHostHash INTEGER NOT NULL ON CONFLICT FAIL, manifestURL TEXT UNIQUE ON CONFLICT FAIL, newestCache INTEGER)"); + executeSQLCommand("CREATE TABLE IF NOT EXISTS Caches (id INTEGER PRIMARY KEY AUTOINCREMENT, cacheGroup INTEGER)"); + executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheWhitelistURLs (url TEXT NOT NULL ON CONFLICT FAIL, cache INTEGER NOT NULL ON CONFLICT FAIL)"); + executeSQLCommand("CREATE TABLE IF NOT EXISTS FallbackURLs (namespace TEXT NOT NULL ON CONFLICT FAIL, fallbackURL TEXT NOT NULL ON CONFLICT FAIL, " + "cache INTEGER NOT NULL ON CONFLICT FAIL)"); + executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheEntries (cache INTEGER NOT NULL ON CONFLICT FAIL, type INTEGER, resource INTEGER NOT NULL)"); + executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResources (id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL ON CONFLICT FAIL, " + "statusCode INTEGER NOT NULL, responseURL TEXT NOT NULL, mimeType TEXT, textEncodingName TEXT, headers TEXT, data INTEGER NOT NULL ON CONFLICT FAIL)"); + executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResourceData (id INTEGER PRIMARY KEY AUTOINCREMENT, data BLOB)"); + + // When a cache is deleted, all its entries and its whitelist should be deleted. + executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheDeleted AFTER DELETE ON Caches" + " FOR EACH ROW BEGIN" + " DELETE FROM CacheEntries WHERE cache = OLD.id;" + " DELETE FROM CacheWhitelistURLs WHERE cache = OLD.id;" + " DELETE FROM FallbackURLs WHERE cache = OLD.id;" + " END"); + + // When a cache resource is deleted, its data blob should also be deleted. + executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheResourceDeleted AFTER DELETE ON CacheResources" + " FOR EACH ROW BEGIN" + " DELETE FROM CacheResourceData WHERE id = OLD.data;" + " END"); +} + +bool ApplicationCacheStorage::executeStatement(SQLiteStatement& statement) +{ + bool result = statement.executeCommand(); + if (!result) + LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"", + statement.query().utf8().data(), m_database.lastErrorMsg()); + + return result; +} + +bool ApplicationCacheStorage::store(ApplicationCacheGroup* group) +{ + ASSERT(group->storageID() == 0); + + SQLiteStatement statement(m_database, "INSERT INTO CacheGroups (manifestHostHash, manifestURL) VALUES (?, ?)"); + if (statement.prepare() != SQLResultOk) + return false; + + statement.bindInt64(1, urlHostHash(group->manifestURL())); + statement.bindText(2, group->manifestURL()); + + if (!executeStatement(statement)) + return false; + + group->setStorageID((unsigned)m_database.lastInsertRowID()); + return true; +} + +bool ApplicationCacheStorage::store(ApplicationCache* cache) +{ + ASSERT(cache->storageID() == 0); + ASSERT(cache->group()->storageID() != 0); + + SQLiteStatement statement(m_database, "INSERT INTO Caches (cacheGroup) VALUES (?)"); + if (statement.prepare() != SQLResultOk) + return false; + + statement.bindInt64(1, cache->group()->storageID()); + + if (!executeStatement(statement)) + return false; + + unsigned cacheStorageID = (unsigned)m_database.lastInsertRowID(); + + // Store all resources + { + ApplicationCache::ResourceMap::const_iterator end = cache->end(); + for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) { + if (!store(it->second.get(), cacheStorageID)) + return false; + } + } + + // Store the online whitelist + const HashSet<String>& onlineWhitelist = cache->onlineWhitelist(); + { + HashSet<String>::const_iterator end = onlineWhitelist.end(); + for (HashSet<String>::const_iterator it = onlineWhitelist.begin(); it != end; ++it) { + SQLiteStatement statement(m_database, "INSERT INTO CacheWhitelistURLs (url, cache) VALUES (?, ?)"); + statement.prepare(); + + statement.bindText(1, *it); + statement.bindInt64(2, cacheStorageID); + + if (!executeStatement(statement)) + return false; + } + } + + cache->setStorageID(cacheStorageID); + return true; +} + +bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, unsigned cacheStorageID) +{ + ASSERT(cacheStorageID); + ASSERT(!resource->storageID()); + + openDatabase(true); + + // First, insert the data + SQLiteStatement dataStatement(m_database, "INSERT INTO CacheResourceData (data) VALUES (?)"); + if (dataStatement.prepare() != SQLResultOk) + return false; + + if (resource->data()->size()) + dataStatement.bindBlob(1, resource->data()->data(), resource->data()->size()); + + if (!dataStatement.executeCommand()) + return false; + + unsigned dataId = (unsigned)m_database.lastInsertRowID(); + + // Then, insert the resource + + // Serialize the headers + Vector<UChar> stringBuilder; + + HTTPHeaderMap::const_iterator end = resource->response().httpHeaderFields().end(); + for (HTTPHeaderMap::const_iterator it = resource->response().httpHeaderFields().begin(); it!= end; ++it) { + stringBuilder.append(it->first.characters(), it->first.length()); + stringBuilder.append((UChar)':'); + stringBuilder.append(it->second.characters(), it->second.length()); + stringBuilder.append((UChar)'\n'); + } + + String headers = String::adopt(stringBuilder); + + SQLiteStatement resourceStatement(m_database, "INSERT INTO CacheResources (url, statusCode, responseURL, headers, data, mimeType, textEncodingName) VALUES (?, ?, ?, ?, ?, ?, ?)"); + if (resourceStatement.prepare() != SQLResultOk) + return false; + + resourceStatement.bindText(1, resource->url()); + resourceStatement.bindInt64(2, resource->response().httpStatusCode()); + resourceStatement.bindText(3, resource->response().url()); + resourceStatement.bindText(4, headers); + resourceStatement.bindInt64(5, dataId); + resourceStatement.bindText(6, resource->response().mimeType()); + resourceStatement.bindText(7, resource->response().textEncodingName()); + + if (!executeStatement(resourceStatement)) + return false; + + unsigned resourceId = (unsigned)m_database.lastInsertRowID(); + + // Finally, insert the cache entry + SQLiteStatement entryStatement(m_database, "INSERT INTO CacheEntries (cache, type, resource) VALUES (?, ?, ?)"); + if (entryStatement.prepare() != SQLResultOk) + return false; + + entryStatement.bindInt64(1, cacheStorageID); + entryStatement.bindInt64(2, resource->type()); + entryStatement.bindInt64(3, resourceId); + + if (!executeStatement(entryStatement)) + return false; + + resource->setStorageID(resourceId); + return true; +} + +void ApplicationCacheStorage::store(ApplicationCacheResource* resource, ApplicationCache* cache) +{ + ASSERT(cache->storageID()); + + openDatabase(true); + + SQLiteTransaction storeResourceTransaction(m_database); + storeResourceTransaction.begin(); + + if (!store(resource, cache->storageID())) + return; + + storeResourceTransaction.commit(); +} + +bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group) +{ + openDatabase(true); + + SQLiteTransaction storeCacheTransaction(m_database); + + storeCacheTransaction.begin(); + + if (!group->storageID()) { + // Store the group + if (!store(group)) + return false; + } + + ASSERT(group->newestCache()); + ASSERT(!group->newestCache()->storageID()); + + // Store the newest cache + if (!store(group->newestCache())) + return false; + + // Update the newest cache in the group. + + SQLiteStatement statement(m_database, "UPDATE CacheGroups SET newestCache=? WHERE id=?"); + if (statement.prepare() != SQLResultOk) + return false; + + statement.bindInt64(1, group->newestCache()->storageID()); + statement.bindInt64(2, group->storageID()); + + if (!executeStatement(statement)) + return false; + + storeCacheTransaction.commit(); + return true; +} + +static inline void parseHeader(const UChar* header, size_t headerLength, ResourceResponse& response) +{ + int pos = find(header, headerLength, ':'); + ASSERT(pos != -1); + + String headerName = String(header, pos); + String headerValue = String(header + pos + 1, headerLength - pos - 1); + + response.setHTTPHeaderField(headerName, headerValue); +} + +static inline void parseHeaders(const String& headers, ResourceResponse& response) +{ + int startPos = 0; + int endPos; + while ((endPos = headers.find('\n', startPos)) != -1) { + ASSERT(startPos != endPos); + + parseHeader(headers.characters() + startPos, endPos - startPos, response); + + startPos = endPos + 1; + } + + if (startPos != static_cast<int>(headers.length())) + parseHeader(headers.characters(), headers.length(), response); +} + +PassRefPtr<ApplicationCache> ApplicationCacheStorage::loadCache(unsigned storageID) +{ + SQLiteStatement cacheStatement(m_database, + "SELECT url, type, mimeType, textEncodingName, headers, CacheResourceData.data FROM CacheEntries INNER JOIN CacheResources ON CacheEntries.resource=CacheResources.id " + "INNER JOIN CacheResourceData ON CacheResourceData.id=CacheResources.data WHERE CacheEntries.cache=?"); + if (cacheStatement.prepare() != SQLResultOk) { + LOG_ERROR("Could not prepare cache statement, error \"%s\"", m_database.lastErrorMsg()); + return 0; + } + + cacheStatement.bindInt64(1, storageID); + + RefPtr<ApplicationCache> cache = ApplicationCache::create(); + + int result; + while ((result = cacheStatement.step()) == SQLResultRow) { + KURL url(cacheStatement.getColumnText(0)); + + unsigned type = (unsigned)cacheStatement.getColumnInt64(1); + + Vector<char> blob; + cacheStatement.getColumnBlobAsVector(5, blob); + + RefPtr<SharedBuffer> data = SharedBuffer::adoptVector(blob); + + String mimeType = cacheStatement.getColumnText(2); + String textEncodingName = cacheStatement.getColumnText(3); + + ResourceResponse response(url, mimeType, data->size(), textEncodingName, ""); + + String headers = cacheStatement.getColumnText(4); + parseHeaders(headers, response); + + RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, response, type, data.release()); + + if (type & ApplicationCacheResource::Manifest) + cache->setManifestResource(resource.release()); + else + cache->addResource(resource.release()); + } + + if (result != SQLResultDone) + LOG_ERROR("Could not load cache resources, error \"%s\"", m_database.lastErrorMsg()); + + // Load the online whitelist + SQLiteStatement whitelistStatement(m_database, "SELECT url FROM CacheWhitelistURLs WHERE cache=?"); + if (whitelistStatement.prepare() != SQLResultOk) + return 0; + whitelistStatement.bindInt64(1, storageID); + + HashSet<String> whitelist; + while ((result = whitelistStatement.step()) == SQLResultRow) + whitelist.add(whitelistStatement.getColumnText(0)); + + if (result != SQLResultDone) + LOG_ERROR("Could not load cache online whitelist, error \"%s\"", m_database.lastErrorMsg()); + + cache->setOnlineWhitelist(whitelist); + + cache->setStorageID(storageID); + + return cache.release(); +} + +void ApplicationCacheStorage::remove(ApplicationCache* cache) +{ + if (!cache->storageID()) + return; + + openDatabase(false); + if (!m_database.isOpen()) + return; + + SQLiteStatement statement(m_database, "DELETE FROM Caches WHERE id=?"); + if (statement.prepare() != SQLResultOk) + return; + + statement.bindInt64(1, cache->storageID()); + executeStatement(statement); +} + +void ApplicationCacheStorage::empty() +{ + openDatabase(false); + + if (!m_database.isOpen()) + return; + + // Clear cache groups, caches and cache resources. + executeSQLCommand("DELETE FROM CacheGroups"); + executeSQLCommand("DELETE FROM Caches"); + executeSQLCommand("DELETE FROM CacheResources"); + + // Clear the storage IDs for the caches in memory. + // The caches will still work, but cached resources will not be saved to disk + // until a cache update process has been initiated. + CacheGroupMap::const_iterator end = m_cachesInMemory.end(); + for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) + it->second->clearStorageID(); +} + +bool ApplicationCacheStorage::storeCopyOfCache(const String& cacheDirectory, ApplicationCache* cache) +{ + // Create a new cache. + RefPtr<ApplicationCache> cacheCopy = ApplicationCache::create(); + + // Set the online whitelist + cacheCopy->setOnlineWhitelist(cache->onlineWhitelist()); + + // Traverse the cache and add copies of all resources. + ApplicationCache::ResourceMap::const_iterator end = cache->end(); + for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) { + ApplicationCacheResource* resource = it->second.get(); + + RefPtr<ApplicationCacheResource> resourceCopy = ApplicationCacheResource::create(resource->url(), resource->response(), resource->type(), resource->data()); + + cacheCopy->addResource(resourceCopy.release()); + } + + // Now create a new cache group. + OwnPtr<ApplicationCacheGroup> groupCopy(new ApplicationCacheGroup(cache->group()->manifestURL(), true)); + + groupCopy->setNewestCache(cacheCopy); + + ApplicationCacheStorage copyStorage; + copyStorage.setCacheDirectory(cacheDirectory); + + // Empty the cache in case something was there before. + copyStorage.empty(); + + return copyStorage.storeNewestCache(groupCopy.get()); +} + +ApplicationCacheStorage& cacheStorage() +{ + static ApplicationCacheStorage storage; + + return storage; +} + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/WebCore/loader/appcache/ApplicationCacheStorage.h b/WebCore/loader/appcache/ApplicationCacheStorage.h new file mode 100644 index 0000000..6bd9ba1 --- /dev/null +++ b/WebCore/loader/appcache/ApplicationCacheStorage.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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 ApplicationCacheStorage_h +#define ApplicationCacheStorage_h + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "PlatformString.h" +#include "SQLiteDatabase.h" +#include "StringHash.h" + +#include <wtf/HashCountedSet.h> + +namespace WebCore { + +class ApplicationCache; +class ApplicationCacheGroup; +class ApplicationCacheResource; +class KURL; + +class ApplicationCacheStorage { +public: + void setCacheDirectory(const String&); + + ApplicationCacheGroup* cacheGroupForURL(const KURL&); + + ApplicationCacheGroup* findOrCreateCacheGroup(const KURL& manifestURL); + void cacheGroupDestroyed(ApplicationCacheGroup*); + + bool storeNewestCache(ApplicationCacheGroup*); + void store(ApplicationCacheResource*, ApplicationCache*); + + void remove(ApplicationCache*); + + void empty(); + + static bool storeCopyOfCache(const String& cacheDirectory, ApplicationCache*); +private: + PassRefPtr<ApplicationCache> loadCache(unsigned storageID); + ApplicationCacheGroup* loadCacheGroup(const KURL& manifestURL); + + bool store(ApplicationCacheGroup*); + bool store(ApplicationCache*); + bool store(ApplicationCacheResource*, unsigned cacheStorageID); + + void loadManifestHostHashes(); + + void verifySchemaVersion(); + + void openDatabase(bool createIfDoesNotExist); + + bool executeStatement(SQLiteStatement&); + bool executeSQLCommand(const String&); + + String m_cacheDirectory; + + SQLiteDatabase m_database; + + // In order to quickly determinate if a given resource exists in an application cache, + // we keep a hash set of the hosts of the manifest URLs of all cache groups. + HashCountedSet<unsigned, AlreadyHashed> m_cacheHostSet; + + typedef HashMap<String, ApplicationCacheGroup*> CacheGroupMap; + CacheGroupMap m_cachesInMemory; +}; + +ApplicationCacheStorage& cacheStorage(); + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // ApplicationCacheStorage_h diff --git a/WebCore/loader/appcache/DOMApplicationCache.cpp b/WebCore/loader/appcache/DOMApplicationCache.cpp new file mode 100644 index 0000000..f872a50 --- /dev/null +++ b/WebCore/loader/appcache/DOMApplicationCache.cpp @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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. + */ + +#include "config.h" +#include "DOMApplicationCache.h" + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "ApplicationCache.h" +#include "ApplicationCacheGroup.h" +#include "DocumentLoader.h" +#include "Event.h" +#include "EventException.h" +#include "EventListener.h" +#include "EventNames.h" +#include "Frame.h" +#include "FrameLoader.h" + +namespace WebCore { + +DOMApplicationCache::DOMApplicationCache(Frame* frame) + : m_frame(frame) +{ +} + +void DOMApplicationCache::disconnectFrame() +{ + m_frame = 0; +} + +ApplicationCache* DOMApplicationCache::associatedCache() const +{ + if (!m_frame) + return 0; + + return m_frame->loader()->documentLoader()->topLevelApplicationCache(); +} + +unsigned short DOMApplicationCache::status() const +{ + ApplicationCache* cache = associatedCache(); + if (!cache) + return UNCACHED; + + switch (cache->group()->status()) { + case ApplicationCacheGroup::Checking: + return CHECKING; + case ApplicationCacheGroup::Downloading: + return DOWNLOADING; + case ApplicationCacheGroup::Idle: { + if (cache != cache->group()->newestCache()) + return UPDATEREADY; + + return IDLE; + } + default: + ASSERT_NOT_REACHED(); + } + + return 0; +} + +void DOMApplicationCache::update(ExceptionCode& ec) +{ + ApplicationCache* cache = associatedCache(); + if (!cache) { + ec = INVALID_STATE_ERR; + return; + } + + cache->group()->update(m_frame); +} + +bool DOMApplicationCache::swapCache() +{ + if (!m_frame) + return false; + + ApplicationCache* cache = m_frame->loader()->documentLoader()->applicationCache(); + if (!cache) + return false; + + // Check if we already have the newest cache + ApplicationCache* newestCache = cache->group()->newestCache(); + if (cache == newestCache) + return false; + + ASSERT(cache->group() == newestCache->group()); + m_frame->loader()->documentLoader()->setApplicationCache(newestCache); + + return true; +} + +void DOMApplicationCache::swapCache(ExceptionCode& ec) +{ + if (!swapCache()) + ec = INVALID_STATE_ERR; +} + +unsigned DOMApplicationCache::length() const +{ + ApplicationCache* cache = associatedCache(); + if (!cache) + return 0; + + return cache->numDynamicEntries(); +} + +String DOMApplicationCache::item(unsigned item, ExceptionCode& ec) +{ + ApplicationCache* cache = associatedCache(); + if (!cache) { + ec = INVALID_STATE_ERR; + return String(); + } + + if (item >= length()) { + ec = INDEX_SIZE_ERR; + return String(); + } + + return cache->dynamicEntry(item); +} + +void DOMApplicationCache::add(const KURL& url, ExceptionCode& ec) +{ + ApplicationCache* cache = associatedCache(); + if (!cache) { + ec = INVALID_STATE_ERR; + return; + } + + if (!url.isValid()) { + ec = SYNTAX_ERR; + return; + } + + if (!cache->addDynamicEntry(url)) { + // This should use the (currently not specified) security exceptions in HTML5 4.3.4 + ec = SECURITY_ERR; + } +} + +void DOMApplicationCache::remove(const KURL& url, ExceptionCode& ec) +{ + ApplicationCache* cache = associatedCache(); + if (!cache) { + ec = INVALID_STATE_ERR; + return; + } + + cache->removeDynamicEntry(url); +} + +ScriptExecutionContext* DOMApplicationCache::scriptExecutionContext() const +{ + return m_frame->document(); +} + +void DOMApplicationCache::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> eventListener, bool) +{ + EventListenersMap::iterator iter = m_eventListeners.find(eventType); + if (iter == m_eventListeners.end()) { + ListenerVector listeners; + listeners.append(eventListener); + m_eventListeners.add(eventType, listeners); + } else { + ListenerVector& listeners = iter->second; + for (ListenerVector::iterator listenerIter = listeners.begin(); listenerIter != listeners.end(); ++listenerIter) { + if (*listenerIter == eventListener) + return; + } + + listeners.append(eventListener); + m_eventListeners.add(eventType, listeners); + } +} + +void DOMApplicationCache::removeEventListener(const AtomicString& eventType, EventListener* eventListener, bool useCapture) +{ + EventListenersMap::iterator iter = m_eventListeners.find(eventType); + if (iter == m_eventListeners.end()) + return; + + ListenerVector& listeners = iter->second; + for (ListenerVector::const_iterator listenerIter = listeners.begin(); listenerIter != listeners.end(); ++listenerIter) { + if (*listenerIter == eventListener) { + listeners.remove(listenerIter - listeners.begin()); + return; + } + } +} + +bool DOMApplicationCache::dispatchEvent(PassRefPtr<Event> event, ExceptionCode& ec) +{ + if (event->type().isEmpty()) { + ec = EventException::UNSPECIFIED_EVENT_TYPE_ERR; + return true; + } + + ListenerVector listenersCopy = m_eventListeners.get(event->type()); + for (ListenerVector::const_iterator listenerIter = listenersCopy.begin(); listenerIter != listenersCopy.end(); ++listenerIter) { + event->setTarget(this); + event->setCurrentTarget(this); + listenerIter->get()->handleEvent(event.get(), false); + } + + return !event->defaultPrevented(); +} + +void DOMApplicationCache::callListener(const AtomicString& eventType, EventListener* listener) +{ + ASSERT(m_frame); + + RefPtr<Event> event = Event::create(eventType, false, false); + if (listener) { + event->setTarget(this); + event->setCurrentTarget(this); + listener->handleEvent(event.get(), false); + } + + ExceptionCode ec = 0; + dispatchEvent(event.release(), ec); + ASSERT(!ec); +} + +void DOMApplicationCache::callCheckingListener() +{ + callListener(eventNames().checkingEvent, m_onCheckingListener.get()); +} + +void DOMApplicationCache::callErrorListener() +{ + callListener(eventNames().errorEvent, m_onErrorListener.get()); +} + +void DOMApplicationCache::callNoUpdateListener() +{ + callListener(eventNames().noupdateEvent, m_onNoUpdateListener.get()); +} + +void DOMApplicationCache::callDownloadingListener() +{ + callListener(eventNames().downloadingEvent, m_onDownloadingListener.get()); +} + +void DOMApplicationCache::callProgressListener() +{ + callListener(eventNames().progressEvent, m_onProgressListener.get()); +} + +void DOMApplicationCache::callUpdateReadyListener() +{ + callListener(eventNames().updatereadyEvent, m_onUpdateReadyListener.get()); +} + +void DOMApplicationCache::callCachedListener() +{ + callListener(eventNames().cachedEvent, m_onCachedListener.get()); +} + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/WebCore/loader/appcache/DOMApplicationCache.h b/WebCore/loader/appcache/DOMApplicationCache.h new file mode 100644 index 0000000..30c0b7e --- /dev/null +++ b/WebCore/loader/appcache/DOMApplicationCache.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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 DOMApplicationCache_h +#define DOMApplicationCache_h + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "AtomicStringHash.h" +#include "EventTarget.h" +#include "EventListener.h" +#include <wtf/HashMap.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class ApplicationCache; +class AtomicStringImpl; +class Frame; +class KURL; +class String; + +class DOMApplicationCache : public RefCounted<DOMApplicationCache>, public EventTarget { +public: + static PassRefPtr<DOMApplicationCache> create(Frame* frame) { return adoptRef(new DOMApplicationCache(frame)); } + void disconnectFrame(); + + enum Status { + UNCACHED = 0, + IDLE = 1, + CHECKING = 2, + DOWNLOADING = 3, + UPDATEREADY = 4, + }; + + unsigned short status() const; + + void update(ExceptionCode&); + void swapCache(ExceptionCode&); + + unsigned length() const; + String item(unsigned item, ExceptionCode&); + void add(const KURL&, ExceptionCode&); + void remove(const KURL&, ExceptionCode&); + + virtual void addEventListener(const AtomicString& eventType, PassRefPtr<EventListener>, bool useCapture); + virtual void removeEventListener(const AtomicString& eventType, EventListener*, bool useCapture); + virtual bool dispatchEvent(PassRefPtr<Event>, ExceptionCode&); + + typedef Vector<RefPtr<EventListener> > ListenerVector; + typedef HashMap<AtomicString, ListenerVector> EventListenersMap; + EventListenersMap& eventListeners() { return m_eventListeners; } + + using RefCounted<DOMApplicationCache>::ref; + using RefCounted<DOMApplicationCache>::deref; + + void setOnchecking(PassRefPtr<EventListener> eventListener) { m_onCheckingListener = eventListener; } + EventListener* onchecking() const { return m_onCheckingListener.get(); } + + void setOnerror(PassRefPtr<EventListener> eventListener) { m_onErrorListener = eventListener; } + EventListener* onerror() const { return m_onErrorListener.get(); } + + void setOnnoupdate(PassRefPtr<EventListener> eventListener) { m_onNoUpdateListener = eventListener; } + EventListener* onnoupdate() const { return m_onNoUpdateListener.get(); } + + void setOndownloading(PassRefPtr<EventListener> eventListener) { m_onDownloadingListener = eventListener; } + EventListener* ondownloading() const { return m_onDownloadingListener.get(); } + + void setOnprogress(PassRefPtr<EventListener> eventListener) { m_onProgressListener = eventListener; } + EventListener* onprogress() const { return m_onProgressListener.get(); } + + void setOnupdateready(PassRefPtr<EventListener> eventListener) { m_onUpdateReadyListener = eventListener; } + EventListener* onupdateready() const { return m_onUpdateReadyListener.get(); } + + void setOncached(PassRefPtr<EventListener> eventListener) { m_onCachedListener = eventListener; } + EventListener* oncached() const { return m_onCachedListener.get(); } + + virtual ScriptExecutionContext* scriptExecutionContext() const; + DOMApplicationCache* toDOMApplicationCache() { return this; } + + void callCheckingListener(); + void callErrorListener(); + void callNoUpdateListener(); + void callDownloadingListener(); + void callProgressListener(); + void callUpdateReadyListener(); + void callCachedListener(); + +private: + DOMApplicationCache(Frame*); + void callListener(const AtomicString& eventType, EventListener*); + + virtual void refEventTarget() { ref(); } + virtual void derefEventTarget() { deref(); } + + ApplicationCache* associatedCache() const; + bool swapCache(); + + RefPtr<EventListener> m_onCheckingListener; + RefPtr<EventListener> m_onErrorListener; + RefPtr<EventListener> m_onNoUpdateListener; + RefPtr<EventListener> m_onDownloadingListener; + RefPtr<EventListener> m_onProgressListener; + RefPtr<EventListener> m_onUpdateReadyListener; + RefPtr<EventListener> m_onCachedListener; + + EventListenersMap m_eventListeners; + + Frame* m_frame; +}; + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // DOMApplicationCache_h diff --git a/WebCore/loader/appcache/DOMApplicationCache.idl b/WebCore/loader/appcache/DOMApplicationCache.idl new file mode 100644 index 0000000..94326f3 --- /dev/null +++ b/WebCore/loader/appcache/DOMApplicationCache.idl @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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. + */ + +module offline { + + interface [ + Conditional=OFFLINE_WEB_APPLICATIONS, + CustomMarkFunction + ] DOMApplicationCache { + // update status + const unsigned short UNCACHED = 0; + const unsigned short IDLE = 1; + const unsigned short CHECKING = 2; + const unsigned short DOWNLOADING = 3; + const unsigned short UPDATEREADY = 4; + readonly attribute unsigned short status; + + void update() + raises(DOMException); + void swapCache() + raises(DOMException); + + // dynamic entries + readonly attribute unsigned long length; + DOMString item(in [IsIndex] unsigned long index) + raises(DOMException); + [Custom] void add(in DOMString uri) + raises(DOMException); + [Custom] void remove(in DOMString uri) + raises(DOMException); + + // events + attribute EventListener onchecking; + attribute EventListener onerror; + attribute EventListener onnoupdate; + attribute EventListener ondownloading; + attribute EventListener onprogress; + attribute EventListener onupdateready; + attribute EventListener oncached; + + // EventTarget interface + [Custom] void addEventListener(in DOMString type, + in EventListener listener, + in boolean useCapture); + [Custom] void removeEventListener(in DOMString type, + in EventListener listener, + in boolean useCapture); + boolean dispatchEvent(in Event evt) + raises(EventException); + }; + +} diff --git a/WebCore/loader/appcache/ManifestParser.cpp b/WebCore/loader/appcache/ManifestParser.cpp new file mode 100644 index 0000000..778d22d --- /dev/null +++ b/WebCore/loader/appcache/ManifestParser.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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. + */ + +#include "config.h" +#include "ManifestParser.h" + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "CharacterNames.h" +#include "KURL.h" +#include "TextEncoding.h" + +namespace WebCore { + +enum Mode { Explicit, Fallback, OnlineWhitelist }; + +bool parseManifest(const KURL& manifestURL, const char* data, int length, Manifest& manifest) +{ + ASSERT(manifest.explicitURLs.isEmpty()); + ASSERT(manifest.onlineWhitelistedURLs.isEmpty()); + ASSERT(manifest.fallbackURLs.isEmpty()); + + Mode mode = Explicit; + String s = UTF8Encoding().decode(data, length); + + if (s.isEmpty()) + return false; + + // Replace nulls with U+FFFD REPLACEMENT CHARACTER + s.replace(0, replacementCharacter); + + // Look for the magic signature + if (!s.startsWith("CACHE MANIFEST")) { + // The magic signature was not found. + return false; + } + + const UChar* end = s.characters() + s.length(); + const UChar* p = s.characters() + 14; // "CACHE MANIFEST" is 14 characters. + + while (p < end) { + // Skip whitespace + if (*p == ' ' || *p == '\t') { + p++; + } else + break; + } + + if (p < end && *p != '\n' && *p != '\r') { + // The magic signature was invalid + return false; + } + + while (1) { + // Skip whitespace + while (p < end && (*p == '\n' || *p == '\r' || *p == ' ' || *p == '\t')) + p++; + + if (p == end) + break; + + const UChar* lineStart = p; + + // Find the end of the line + while (p < end && *p != '\r' && *p != '\n') + p++; + + // Check if we have a comment + if (*lineStart == '#') + continue; + + // Get rid of trailing whitespace + const UChar* tmp = p - 1; + while (tmp > lineStart && (*tmp == ' ' || *tmp == '\t')) + tmp--; + + String line(lineStart, tmp - lineStart + 1); + + if (line == "CACHE:") + mode = Explicit; + else if (line == "FALLBACK:") + mode = Fallback; + else if (line == "NETWORK:") + mode = OnlineWhitelist; + else if (mode == Explicit || mode == OnlineWhitelist) { + KURL url(manifestURL, line); + + if (!url.isValid()) + continue; + + if (url.hasRef()) + url.setRef(String()); + + if (!equalIgnoringCase(url.protocol(), manifestURL.protocol())) + continue; + + if (mode == Explicit) + manifest.explicitURLs.add(url.string()); + else + manifest.onlineWhitelistedURLs.add(url.string()); + + } else if (mode == Fallback) { + const UChar *p = line.characters(); + const UChar *lineEnd = p + line.length(); + + // Look for whitespace separating the two URLs + while (p < lineEnd && *p != '\t' && *p != ' ') + p++; + + if (p == lineEnd) { + // There was no whitespace separating the URLs. + continue; + } + + KURL namespaceURL(manifestURL, String(line.characters(), p - line.characters())); + if (!namespaceURL.isValid()) + continue; + + // Check that the namespace URL has the same scheme/host/port as the manifest URL. + if (!protocolHostAndPortAreEqual(manifestURL, namespaceURL)) + continue; + + while (p < lineEnd && (*p == '\t' || *p == ' ')) + p++; + + KURL fallbackURL(String(p, line.length() - (p - line.characters()))); + + if (!fallbackURL.isValid()) + continue; + + if (!equalIgnoringCase(fallbackURL.protocol(), manifestURL.protocol())) + continue; + + manifest.fallbackURLs.add(namespaceURL, fallbackURL); + } else + ASSERT_NOT_REACHED(); + } + + return true; +} + +} + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/WebCore/loader/appcache/ManifestParser.h b/WebCore/loader/appcache/ManifestParser.h new file mode 100644 index 0000000..f4fe31a --- /dev/null +++ b/WebCore/loader/appcache/ManifestParser.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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 ManifestParser_h +#define ManifestParser_h + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include "StringHash.h" +#include "PlatformString.h" + +namespace WebCore { + +class KURL; + +struct Manifest { + HashSet<String> onlineWhitelistedURLs; + HashSet<String> explicitURLs; + HashMap<String, String> fallbackURLs; +}; + +bool parseManifest(const KURL& manifestURL, const char* data, int length, Manifest&); + +} + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // ManifestParser_h diff --git a/WebCore/loader/archive/Archive.h b/WebCore/loader/archive/Archive.h new file mode 100644 index 0000000..af3d8b1 --- /dev/null +++ b/WebCore/loader/archive/Archive.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 Archive_h +#define Archive_h + +#include "ArchiveResource.h" + +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class Archive : public RefCounted<Archive> { +public: + ArchiveResource* mainResource() { return m_mainResource.get(); } + const Vector<RefPtr<ArchiveResource> >& subresources() const { return m_subresources; } + const Vector<RefPtr<Archive> >& subframeArchives() const { return m_subframeArchives; } + +protected: + // These methods are meant for subclasses for different archive types to add resources in to the archive, + // and should not be exposed as archives should be immutable to clients + void setMainResource(PassRefPtr<ArchiveResource> mainResource) { m_mainResource = mainResource; } + void addSubresource(PassRefPtr<ArchiveResource> subResource) { m_subresources.append(subResource); } + void addSubframeArchive(PassRefPtr<Archive> subframeArchive) { m_subframeArchives.append(subframeArchive); } + +private: + RefPtr<ArchiveResource> m_mainResource; + Vector<RefPtr<ArchiveResource> > m_subresources; + Vector<RefPtr<Archive> > m_subframeArchives; +}; + +} + +#endif // Archive diff --git a/WebCore/loader/archive/ArchiveFactory.cpp b/WebCore/loader/archive/ArchiveFactory.cpp new file mode 100644 index 0000000..c42b5df --- /dev/null +++ b/WebCore/loader/archive/ArchiveFactory.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "ArchiveFactory.h" + +#if PLATFORM(CF) +#include "LegacyWebArchive.h" +#endif +#include "MIMETypeRegistry.h" +#include "PlatformString.h" + +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> + +namespace WebCore { + +typedef PassRefPtr<Archive> RawDataCreationFunction(SharedBuffer*); + +// The create functions in the archive classes return PassRefPtr to concrete subclasses +// of Archive. This adaptor makes the functions have a uniform return type. +template <typename ArchiveClass> static PassRefPtr<Archive> archiveFactoryCreate(SharedBuffer* buffer) +{ + return ArchiveClass::create(buffer); +} + +static HashMap<String, RawDataCreationFunction*, CaseFoldingHash>& archiveMIMETypes() +{ + static HashMap<String, RawDataCreationFunction*, CaseFoldingHash> mimeTypes; + static bool initialized = false; + + if (initialized) + return mimeTypes; + +#if PLATFORM(CF) + mimeTypes.set("application/x-webarchive", archiveFactoryCreate<LegacyWebArchive>); +#endif + + initialized = true; + return mimeTypes; +} + +bool ArchiveFactory::isArchiveMimeType(const String& mimeType) +{ + return archiveMIMETypes().contains(mimeType); +} + +PassRefPtr<Archive> ArchiveFactory::create(SharedBuffer* data, const String& mimeType) +{ + RawDataCreationFunction* function = archiveMIMETypes().get(mimeType); + return function ? function(data) : 0; +} + +void ArchiveFactory::registerKnownArchiveMIMETypes() +{ + HashSet<String>& mimeTypes = MIMETypeRegistry::getSupportedNonImageMIMETypes(); + HashMap<String, RawDataCreationFunction*, CaseFoldingHash>::iterator i = archiveMIMETypes().begin(); + HashMap<String, RawDataCreationFunction*, CaseFoldingHash>::iterator end = archiveMIMETypes().end(); + + for (; i != end; ++i) + mimeTypes.add(i->first); +} + +} diff --git a/WebCore/loader/archive/ArchiveFactory.h b/WebCore/loader/archive/ArchiveFactory.h new file mode 100644 index 0000000..bf1d5c6 --- /dev/null +++ b/WebCore/loader/archive/ArchiveFactory.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 ArchiveFactory_h +#define ArchiveFactory_h + +#include "Archive.h" + +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class SharedBuffer; +class String; + +class ArchiveFactory { +public: + static bool isArchiveMimeType(const String&); + static PassRefPtr<Archive> create(SharedBuffer* data, const String& mimeType); + static void registerKnownArchiveMIMETypes(); +}; + +} + +#endif // ArchiveFactory_h diff --git a/WebCore/loader/archive/ArchiveResource.cpp b/WebCore/loader/archive/ArchiveResource.cpp new file mode 100644 index 0000000..691f66a --- /dev/null +++ b/WebCore/loader/archive/ArchiveResource.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "ArchiveResource.h" + +#include "SharedBuffer.h" + +namespace WebCore { + +PassRefPtr<ArchiveResource> ArchiveResource::create(PassRefPtr<SharedBuffer> data, const KURL& url, const ResourceResponse& response) +{ + return data ? adoptRef(new ArchiveResource(data, url, response)) : 0; +} + +PassRefPtr<ArchiveResource> ArchiveResource::create(PassRefPtr<SharedBuffer> data, const KURL& url, const String& mimeType, const String& textEncoding, const String& frameName) +{ + return data ? adoptRef(new ArchiveResource(data, url, mimeType, textEncoding, frameName)) : 0; +} + +PassRefPtr<ArchiveResource> ArchiveResource::create(PassRefPtr<SharedBuffer> data, const KURL& url, const String& mimeType, const String& textEncoding, const String& frameName, const ResourceResponse& resourceResponse) +{ + return data ? adoptRef(new ArchiveResource(data, url, mimeType, textEncoding, frameName, resourceResponse)) : 0; +} + +ArchiveResource::ArchiveResource(PassRefPtr<SharedBuffer> data, const KURL& url, const ResourceResponse& response) + : SubstituteResource(url, response, data) + , m_mimeType(response.mimeType()) + , m_textEncoding(response.textEncodingName()) + , m_shouldIgnoreWhenUnarchiving(false) +{ +} + +ArchiveResource::ArchiveResource(PassRefPtr<SharedBuffer> data, const KURL& url, const String& mimeType, const String& textEncoding, const String& frameName) + : SubstituteResource(url, ResourceResponse(url, mimeType, data ? data->size() : 0, textEncoding, String()), data) + , m_mimeType(mimeType) + , m_textEncoding(textEncoding) + , m_frameName(frameName) + , m_shouldIgnoreWhenUnarchiving(false) +{ +} + +ArchiveResource::ArchiveResource(PassRefPtr<SharedBuffer> data, const KURL& url, const String& mimeType, const String& textEncoding, const String& frameName, const ResourceResponse& response) + : SubstituteResource(url, response.isNull() ? ResourceResponse(url, mimeType, data ? data->size() : 0, textEncoding, String()) : response, data) + , m_mimeType(mimeType) + , m_textEncoding(textEncoding) + , m_frameName(frameName) + , m_shouldIgnoreWhenUnarchiving(false) +{ +} + +} diff --git a/WebCore/loader/archive/ArchiveResource.h b/WebCore/loader/archive/ArchiveResource.h new file mode 100644 index 0000000..d975e04 --- /dev/null +++ b/WebCore/loader/archive/ArchiveResource.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 ArchiveResource_h +#define ArchiveResource_h + +#include "SubstituteResource.h" + +#include "PlatformString.h" + +namespace WebCore { + +class ArchiveResource : public SubstituteResource { +public: + static PassRefPtr<ArchiveResource> create(PassRefPtr<SharedBuffer>, const KURL&, const ResourceResponse&); + static PassRefPtr<ArchiveResource> create(PassRefPtr<SharedBuffer>, const KURL&, const String& mimeType, const String& textEncoding, const String& frameName); + static PassRefPtr<ArchiveResource> create(PassRefPtr<SharedBuffer>, const KURL&, const String& mimeType, const String& textEncoding, const String& frameName, const ResourceResponse&); + + const String& mimeType() const { return m_mimeType; } + const String& textEncoding() const { return m_textEncoding; } + const String& frameName() const { return m_frameName; } + + void ignoreWhenUnarchiving() { m_shouldIgnoreWhenUnarchiving = true; } + bool shouldIgnoreWhenUnarchiving() const { return m_shouldIgnoreWhenUnarchiving; } + +private: + ArchiveResource(PassRefPtr<SharedBuffer>, const KURL&, const ResourceResponse&); + ArchiveResource(PassRefPtr<SharedBuffer>, const KURL&, const String& mimeType, const String& textEncoding, const String& frameName); + ArchiveResource(PassRefPtr<SharedBuffer>, const KURL&, const String& mimeType, const String& textEncoding, const String& frameName, const ResourceResponse&); + + String m_mimeType; + String m_textEncoding; + String m_frameName; + + bool m_shouldIgnoreWhenUnarchiving; +}; + +} + +#endif // ArchiveResource_h diff --git a/WebCore/loader/archive/ArchiveResourceCollection.cpp b/WebCore/loader/archive/ArchiveResourceCollection.cpp new file mode 100644 index 0000000..6eb1237 --- /dev/null +++ b/WebCore/loader/archive/ArchiveResourceCollection.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "ArchiveResourceCollection.h" + +namespace WebCore { + +ArchiveResourceCollection::ArchiveResourceCollection() +{ +} + +void ArchiveResourceCollection::addAllResources(Archive* archive) +{ + ASSERT(archive); + if (!archive) + return; + + const Vector<RefPtr<ArchiveResource> >& subresources = archive->subresources(); + Vector<RefPtr<ArchiveResource> >::const_iterator iRes = subresources.begin(); + Vector<RefPtr<ArchiveResource> >::const_iterator endRes = subresources.end(); + + for (; iRes != endRes; ++iRes) + m_subresources.set((*iRes)->url(), iRes->get()); + + const Vector<RefPtr<Archive> >& subframes = archive->subframeArchives(); + Vector<RefPtr<Archive> >::const_iterator iFrame = subframes.begin(); + Vector<RefPtr<Archive> >::const_iterator endFrame = subframes.end(); + + for (; iFrame != endFrame; ++iFrame) { + ASSERT((*iFrame)->mainResource()); + const String& frameName = (*iFrame)->mainResource()->frameName(); + if (!frameName.isNull()) + m_subframes.set(frameName, iFrame->get()); + } +} + +// FIXME: Adding a resource directly to a DocumentLoader/ArchiveResourceCollection seems like bad design, but is API some apps rely on. +// Can we change the design in a manner that will let us deprecate that API without reducing functionality of those apps? +void ArchiveResourceCollection::addResource(PassRefPtr<ArchiveResource> resource) +{ + ASSERT(resource); + if (!resource) + return; + + const KURL& url = resource->url(); // get before passing PassRefPtr (which sets it to 0) + m_subresources.set(url, resource); +} + +ArchiveResource* ArchiveResourceCollection::archiveResourceForURL(const KURL& url) +{ + ArchiveResource* resource = m_subresources.get(url).get(); + if (!resource) + return 0; + + return resource; +} + +PassRefPtr<Archive> ArchiveResourceCollection::popSubframeArchive(const String& frameName) +{ + return m_subframes.take(frameName); +} + +} diff --git a/WebCore/loader/archive/ArchiveResourceCollection.h b/WebCore/loader/archive/ArchiveResourceCollection.h new file mode 100644 index 0000000..f898a8d --- /dev/null +++ b/WebCore/loader/archive/ArchiveResourceCollection.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 ArchiveResourceCollection_h +#define ArchiveResourceCollection_h + +#include "Archive.h" +#include "ArchiveResource.h" +#include "KURL.h" +#include "PlatformString.h" + +#include <wtf/HashMap.h> +#include <wtf/RefCounted.h> + +namespace WebCore { + +class ArchiveResourceCollection : Noncopyable { +public: + ArchiveResourceCollection(); + + void addResource(PassRefPtr<ArchiveResource>); + void addAllResources(Archive*); + + ArchiveResource* archiveResourceForURL(const KURL&); + PassRefPtr<Archive> popSubframeArchive(const String& frameName); + +private: + HashMap<String, RefPtr<ArchiveResource> > m_subresources; + HashMap<String, RefPtr<Archive> > m_subframes; +}; + +} + +#endif diff --git a/WebCore/loader/archive/cf/LegacyWebArchive.cpp b/WebCore/loader/archive/cf/LegacyWebArchive.cpp new file mode 100644 index 0000000..9d99588 --- /dev/null +++ b/WebCore/loader/archive/cf/LegacyWebArchive.cpp @@ -0,0 +1,562 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "LegacyWebArchive.h" + +#include "CString.h" +#include "Document.h" +#include "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameTree.h" +#include "HTMLFrameOwnerElement.h" +#include "HTMLNames.h" +#include "KURL.h" +#include "Logging.h" +#include "markup.h" +#include "Node.h" +#include "Range.h" +#include "SelectionController.h" +#include "SharedBuffer.h" + +#include <wtf/RetainPtr.h> + +namespace WebCore { + +static const CFStringRef LegacyWebArchiveMainResourceKey = CFSTR("WebMainResource"); +static const CFStringRef LegacyWebArchiveSubresourcesKey = CFSTR("WebSubresources"); +static const CFStringRef LegacyWebArchiveSubframeArchivesKey = CFSTR("WebSubframeArchives"); +static const CFStringRef LegacyWebArchiveResourceDataKey = CFSTR("WebResourceData"); +static const CFStringRef LegacyWebArchiveResourceFrameNameKey = CFSTR("WebResourceFrameName"); +static const CFStringRef LegacyWebArchiveResourceMIMETypeKey = CFSTR("WebResourceMIMEType"); +static const CFStringRef LegacyWebArchiveResourceURLKey = CFSTR("WebResourceURL"); +static const CFStringRef LegacyWebArchiveResourceTextEncodingNameKey = CFSTR("WebResourceTextEncodingName"); +static const CFStringRef LegacyWebArchiveResourceResponseKey = CFSTR("WebResourceResponse"); +static const CFStringRef LegacyWebArchiveResourceResponseVersionKey = CFSTR("WebResourceResponseVersion"); + +static RetainPtr<CFDictionaryRef> createPropertyListRepresentationFromResource(ArchiveResource* resource, bool mainResource) +{ + if (!resource) { + // The property list representation of a null/empty WebResource has the following 3 objects stored as nil + RetainPtr<CFMutableDictionaryRef> propertyList(AdoptCF, CFDictionaryCreateMutable(0, 3, 0, 0)); + CFDictionarySetValue(propertyList.get(), LegacyWebArchiveResourceDataKey, 0); + CFDictionarySetValue(propertyList.get(), LegacyWebArchiveResourceURLKey, 0); + CFDictionarySetValue(propertyList.get(), LegacyWebArchiveResourceMIMETypeKey, 0); + + return propertyList; + } + + RetainPtr<CFMutableDictionaryRef> propertyList(AdoptCF, CFDictionaryCreateMutable(0, 6, 0, &kCFTypeDictionaryValueCallBacks)); + + // Resource data can be empty, but must be represented by an empty CFDataRef + SharedBuffer* data = resource->data(); + RetainPtr<CFDataRef> cfData; + if (data) + cfData.adoptCF(data->createCFData()); + else + cfData.adoptCF(CFDataCreate(0, 0, 0)); + CFDictionarySetValue(propertyList.get(), LegacyWebArchiveResourceDataKey, cfData.get()); + + // Resource URL cannot be null + RetainPtr<CFStringRef> cfURL(AdoptCF, resource->url().string().createCFString()); + if (cfURL) + CFDictionarySetValue(propertyList.get(), LegacyWebArchiveResourceURLKey, cfURL.get()); + else { + LOG(Archives, "LegacyWebArchive - NULL resource URL is invalid - returning null property list"); + return 0; + } + + // FrameName should be left out if empty for subresources, but always included for main resources + const String& frameName(resource->frameName()); + if (!frameName.isEmpty() || mainResource) { + RetainPtr<CFStringRef> cfFrameName(AdoptCF, frameName.createCFString()); + CFDictionarySetValue(propertyList.get(), LegacyWebArchiveResourceFrameNameKey, cfFrameName.get()); + } + + // Set MIMEType, TextEncodingName, and ResourceResponse only if they actually exist + const String& mimeType(resource->mimeType()); + if (!mimeType.isEmpty()) { + RetainPtr<CFStringRef> cfMIMEType(AdoptCF, mimeType.createCFString()); + CFDictionarySetValue(propertyList.get(), LegacyWebArchiveResourceMIMETypeKey, cfMIMEType.get()); + } + + const String& textEncoding(resource->textEncoding()); + if (!textEncoding.isEmpty()) { + RetainPtr<CFStringRef> cfTextEncoding(AdoptCF, textEncoding.createCFString()); + CFDictionarySetValue(propertyList.get(), LegacyWebArchiveResourceTextEncodingNameKey, cfTextEncoding.get()); + } + + // Don't include the resource response for the main resource + if (!mainResource) { + RetainPtr<CFDataRef> resourceResponseData = propertyListDataFromResourceResponse(resource->response()); + if (resourceResponseData) + CFDictionarySetValue(propertyList.get(), LegacyWebArchiveResourceResponseKey, resourceResponseData.get()); + } + + return propertyList; +} + +static RetainPtr<CFDictionaryRef> createPropertyListRep(Archive* archive) +{ + RetainPtr<CFMutableDictionaryRef> propertyList(AdoptCF, CFDictionaryCreateMutable(0, 3, 0, &kCFTypeDictionaryValueCallBacks)); + + RetainPtr<CFDictionaryRef> mainResourceDict = createPropertyListRepresentationFromResource(archive->mainResource(), true); + if (!mainResourceDict) + return 0; + CFDictionarySetValue(propertyList.get(), LegacyWebArchiveMainResourceKey, mainResourceDict.get()); + + RetainPtr<CFMutableArrayRef> subresourcesArray(AdoptCF, CFArrayCreateMutable(0, archive->subresources().size(), &kCFTypeArrayCallBacks)); + const Vector<RefPtr<ArchiveResource> >& subresources(archive->subresources()); + for (unsigned i = 0; i < subresources.size(); ++i) { + RetainPtr<CFDictionaryRef> subresource = createPropertyListRepresentationFromResource(subresources[i].get(), false); + if (subresource) + CFArrayAppendValue(subresourcesArray.get(), subresource.get()); + else + LOG(Archives, "LegacyWebArchive - Failed to create property list for subresource"); + } + if (CFArrayGetCount(subresourcesArray.get())) + CFDictionarySetValue(propertyList.get(), LegacyWebArchiveSubresourcesKey, subresourcesArray.get()); + + RetainPtr<CFMutableArrayRef> subframesArray(AdoptCF, CFArrayCreateMutable(0, archive->subframeArchives().size(), &kCFTypeArrayCallBacks)); + const Vector<RefPtr<Archive> >& subframeArchives(archive->subframeArchives()); + for (unsigned i = 0; i < subframeArchives.size(); ++i) { + RetainPtr<CFDictionaryRef> subframeArchive = createPropertyListRep(subframeArchives[i].get()); + if (subframeArchive) + CFArrayAppendValue(subframesArray.get(), subframeArchive.get()); + else + LOG(Archives, "LegacyWebArchive - Failed to create property list for subframe archive"); + } + if (CFArrayGetCount(subframesArray.get())) + CFDictionarySetValue(propertyList.get(), LegacyWebArchiveSubframeArchivesKey, subframesArray.get()); + + return propertyList; +} + +static ResourceResponse createResourceResponseFromPropertyListData(CFDataRef data, CFStringRef responseDataType) +{ + ASSERT(data); + if (!data) + return ResourceResponse(); + + // If the ResourceResponseVersion (passed in as responseDataType) exists at all, this is a "new" webarchive that we can parse well in a cross platform manner + // If it doesn't exist, we will assume this is an "old" Cocoa-based WebArchive, and parse the ResourceResponse as such + if (!responseDataType) + return createResourceResponseFromMacArchivedData(data); + + // FIXME: Parse the "new" format that the above comment references here + return ResourceResponse(); +} + +static PassRefPtr<ArchiveResource> createResource(CFDictionaryRef dictionary) +{ + ASSERT(dictionary); + if (!dictionary) + return 0; + + CFDataRef resourceData = static_cast<CFDataRef>(CFDictionaryGetValue(dictionary, LegacyWebArchiveResourceDataKey)); + if (resourceData && CFGetTypeID(resourceData) != CFDataGetTypeID()) { + LOG(Archives, "LegacyWebArchive - Resource data is not of type CFData, cannot create invalid resource"); + return 0; + } + + CFStringRef frameName = static_cast<CFStringRef>(CFDictionaryGetValue(dictionary, LegacyWebArchiveResourceFrameNameKey)); + if (frameName && CFGetTypeID(frameName) != CFStringGetTypeID()) { + LOG(Archives, "LegacyWebArchive - Frame name is not of type CFString, cannot create invalid resource"); + return 0; + } + + CFStringRef mimeType = static_cast<CFStringRef>(CFDictionaryGetValue(dictionary, LegacyWebArchiveResourceMIMETypeKey)); + if (mimeType && CFGetTypeID(mimeType) != CFStringGetTypeID()) { + LOG(Archives, "LegacyWebArchive - MIME type is not of type CFString, cannot create invalid resource"); + return 0; + } + + CFStringRef url = static_cast<CFStringRef>(CFDictionaryGetValue(dictionary, LegacyWebArchiveResourceURLKey)); + if (url && CFGetTypeID(url) != CFStringGetTypeID()) { + LOG(Archives, "LegacyWebArchive - URL is not of type CFString, cannot create invalid resource"); + return 0; + } + + CFStringRef textEncoding = static_cast<CFStringRef>(CFDictionaryGetValue(dictionary, LegacyWebArchiveResourceTextEncodingNameKey)); + if (textEncoding && CFGetTypeID(textEncoding) != CFStringGetTypeID()) { + LOG(Archives, "LegacyWebArchive - Text encoding is not of type CFString, cannot create invalid resource"); + return 0; + } + + ResourceResponse response; + + CFDataRef resourceResponseData = static_cast<CFDataRef>(CFDictionaryGetValue(dictionary, LegacyWebArchiveResourceResponseKey)); + if (resourceResponseData) { + if (CFGetTypeID(resourceResponseData) != CFDataGetTypeID()) { + LOG(Archives, "LegacyWebArchive - Resource response data is not of type CFData, cannot create invalid resource"); + return 0; + } + + CFStringRef resourceResponseVersion = static_cast<CFStringRef>(CFDictionaryGetValue(dictionary, LegacyWebArchiveResourceResponseVersionKey)); + if (resourceResponseVersion && CFGetTypeID(resourceResponseVersion) != CFStringGetTypeID()) { + LOG(Archives, "LegacyWebArchive - Resource response version is not of type CFString, cannot create invalid resource"); + return 0; + } + + response = createResourceResponseFromPropertyListData(resourceResponseData, resourceResponseVersion); + } + + return ArchiveResource::create(SharedBuffer::create(CFDataGetBytePtr(resourceData), CFDataGetLength(resourceData)), KURL(url), mimeType, textEncoding, frameName, response); +} + +PassRefPtr<LegacyWebArchive> LegacyWebArchive::create() +{ + return adoptRef(new LegacyWebArchive); +} + +PassRefPtr<LegacyWebArchive> LegacyWebArchive::create(SharedBuffer* data) +{ + LOG(Archives, "LegacyWebArchive - Creating from raw data"); + + RefPtr<LegacyWebArchive> archive = create(); + if (!archive->init(data)) + return 0; + + return archive.release(); +} + +PassRefPtr<LegacyWebArchive> LegacyWebArchive::create(PassRefPtr<ArchiveResource> mainResource, Vector<PassRefPtr<ArchiveResource> >& subresources, Vector<PassRefPtr<LegacyWebArchive> >& subframeArchives) +{ + ASSERT(mainResource); + if (!mainResource) + return 0; + + RefPtr<LegacyWebArchive> archive = create(); + archive->setMainResource(mainResource); + + for (unsigned i = 0; i < subresources.size(); ++i) + archive->addSubresource(subresources[i]); + + for (unsigned i = 0; i < subframeArchives.size(); ++i) + archive->addSubframeArchive(subframeArchives[i]); + + return archive.release(); +} + +LegacyWebArchive::LegacyWebArchive() +{ +} + +bool LegacyWebArchive::init(SharedBuffer* data) +{ + ASSERT(data); + if (!data) + return false; + + RetainPtr<CFDataRef> cfData(AdoptCF, data->createCFData()); + if (!cfData) + return false; + + CFStringRef errorString = 0; + + RetainPtr<CFDictionaryRef> plist(AdoptCF, static_cast<CFDictionaryRef>(CFPropertyListCreateFromXMLData(0, cfData.get(), kCFPropertyListImmutable, &errorString))); + if (!plist) { +#ifndef NDEBUG + const char* cError = errorString ? CFStringGetCStringPtr(errorString, kCFStringEncodingUTF8) : "unknown error"; + LOG(Archives, "LegacyWebArchive - Error parsing PropertyList from archive data - %s", cError); +#endif + if (errorString) + CFRelease(errorString); + return false; + } + + if (CFGetTypeID(plist.get()) != CFDictionaryGetTypeID()) { + LOG(Archives, "LegacyWebArchive - Archive property list is not the expected CFDictionary, aborting invalid WebArchive"); + return false; + } + + return extract(plist.get()); +} + +bool LegacyWebArchive::extract(CFDictionaryRef dictionary) +{ + ASSERT(dictionary); + if (!dictionary) { + LOG(Archives, "LegacyWebArchive - Null root CFDictionary, aborting invalid WebArchive"); + return false; + } + + CFDictionaryRef mainResourceDict = static_cast<CFDictionaryRef>(CFDictionaryGetValue(dictionary, LegacyWebArchiveMainResourceKey)); + if (!mainResourceDict) { + LOG(Archives, "LegacyWebArchive - No main resource in archive, aborting invalid WebArchive"); + return false; + } + if (CFGetTypeID(mainResourceDict) != CFDictionaryGetTypeID()) { + LOG(Archives, "LegacyWebArchive - Main resource is not the expected CFDictionary, aborting invalid WebArchive"); + return false; + } + + setMainResource(createResource(mainResourceDict)); + if (!mainResource()) { + LOG(Archives, "LegacyWebArchive - Failed to parse main resource from CFDictionary or main resource does not exist, aborting invalid WebArchive"); + return false; + } + + CFArrayRef subresourceArray = static_cast<CFArrayRef>(CFDictionaryGetValue(dictionary, LegacyWebArchiveSubresourcesKey)); + if (subresourceArray && CFGetTypeID(subresourceArray) != CFArrayGetTypeID()) { + LOG(Archives, "LegacyWebArchive - Subresources is not the expected Array, aborting invalid WebArchive"); + return false; + } + + if (subresourceArray) { + CFIndex count = CFArrayGetCount(subresourceArray); + for (CFIndex i = 0; i < count; ++i) { + CFDictionaryRef subresourceDict = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(subresourceArray, i)); + if (CFGetTypeID(subresourceDict) != CFDictionaryGetTypeID()) { + LOG(Archives, "LegacyWebArchive - Subresource is not expected CFDictionary, aborting invalid WebArchive"); + return false; + } + addSubresource(createResource(subresourceDict)); + } + } + + CFArrayRef subframeArray = static_cast<CFArrayRef>(CFDictionaryGetValue(dictionary, LegacyWebArchiveSubframeArchivesKey)); + if (subframeArray && CFGetTypeID(subframeArray) != CFArrayGetTypeID()) { + LOG(Archives, "LegacyWebArchive - Subframe archives is not the expected Array, aborting invalid WebArchive"); + return false; + } + + if (subframeArray) { + CFIndex count = CFArrayGetCount(subframeArray); + for (CFIndex i = 0; i < count; ++i) { + CFDictionaryRef subframeDict = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(subframeArray, i)); + if (CFGetTypeID(subframeDict) != CFDictionaryGetTypeID()) { + LOG(Archives, "LegacyWebArchive - Subframe array is not expected CFDictionary, aborting invalid WebArchive"); + return false; + } + + RefPtr<LegacyWebArchive> subframeArchive = create(); + if (subframeArchive->extract(subframeDict)) + addSubframeArchive(subframeArchive.release()); + else + LOG(Archives, "LegacyWebArchive - Invalid subframe archive skipped"); + } + } + + return true; +} + +RetainPtr<CFDataRef> LegacyWebArchive::rawDataRepresentation() +{ + RetainPtr<CFDictionaryRef> propertyList = createPropertyListRep(this); + if (!propertyList) { + LOG(Archives, "LegacyWebArchive - Failed to create property list for archive, returning no data"); + return 0; + } + + // FIXME: On Mac, WebArchives have been written out as Binary Property Lists until this change. + // Unless we jump through CFWriteStream hoops, they'll now be textual XML data. Is this okay? + RetainPtr<CFDataRef> plistData(AdoptCF, CFPropertyListCreateXMLData(0, propertyList.get())); + if (!plistData) { + LOG(Archives, "LegacyWebArchive - Failed to convert property list into raw data, returning no data"); + return 0; + } + + return plistData; +} + +#if !PLATFORM(MAC) +// FIXME: Is it possible to parse in a Cocoa-style resource response manually, +// without NSKeyed(Un)Archiver, manipulating plists directly? +// If so, the code that does it will go here. +// In the meantime, Mac will continue to NSKeyed(Un)Archive the response as it always has +ResourceResponse createResourceResponseFromMacArchivedData(CFDataRef responseData) +{ + return ResourceResponse(); +} + +RetainPtr<CFDataRef> propertyListDataFromResourceResponse(const ResourceResponse& response) +{ + // FIXME: Write out the "new" format described in ::createResourceResponseFromPropertyListData() up above + return 0; +} +#endif + +PassRefPtr<LegacyWebArchive> LegacyWebArchive::create(Node* node) +{ + ASSERT(node); + if (!node) + return create(); + + Document* document = node->document(); + Frame* frame = document ? document->frame() : 0; + if (!frame) + return create(); + + Vector<Node*> nodeList; + String markupString = createMarkup(node, IncludeNode, &nodeList); + Node::NodeType nodeType = node->nodeType(); + if (nodeType != Node::DOCUMENT_NODE && nodeType != Node::DOCUMENT_TYPE_NODE) + markupString = frame->documentTypeString() + markupString; + + return create(markupString, frame, nodeList); +} + +PassRefPtr<LegacyWebArchive> LegacyWebArchive::create(Frame* frame) +{ + ASSERT(frame); + + DocumentLoader* documentLoader = frame->loader()->documentLoader(); + + if (!documentLoader) + return 0; + + Vector<PassRefPtr<LegacyWebArchive> > subframeArchives; + + unsigned children = frame->tree()->childCount(); + for (unsigned i = 0; i < children; ++i) { + RefPtr<LegacyWebArchive> childFrameArchive = create(frame->tree()->child(i)); + if (childFrameArchive) + subframeArchives.append(childFrameArchive.release()); + } + + Vector<PassRefPtr<ArchiveResource> > subresources; + documentLoader->getSubresources(subresources); + + return LegacyWebArchive::create(documentLoader->mainResource(), subresources, subframeArchives); +} + +PassRefPtr<LegacyWebArchive> LegacyWebArchive::create(Range* range) +{ + if (!range) + return 0; + + Node* startContainer = range->startContainer(); + if (!startContainer) + return 0; + + Document* document = startContainer->document(); + if (!document) + return 0; + + Frame* frame = document->frame(); + if (!frame) + return 0; + + Vector<Node*> nodeList; + + // FIXME: This is always "for interchange". Is that right? See the previous method. + String markupString = frame->documentTypeString() + createMarkup(range, &nodeList, AnnotateForInterchange); + + return create(markupString, frame, nodeList); +} + +PassRefPtr<LegacyWebArchive> LegacyWebArchive::create(const String& markupString, Frame* frame, Vector<Node*>& nodes) +{ + ASSERT(frame); + + const ResourceResponse& response = frame->loader()->documentLoader()->response(); + KURL responseURL = response.url(); + + // it's possible to have a response without a URL here + // <rdar://problem/5454935> + if (responseURL.isNull()) + responseURL = KURL(""); + + PassRefPtr<ArchiveResource> mainResource = ArchiveResource::create(utf8Buffer(markupString), responseURL, response.mimeType(), "UTF-8", frame->tree()->name()); + + Vector<PassRefPtr<LegacyWebArchive> > subframeArchives; + Vector<PassRefPtr<ArchiveResource> > subresources; + HashSet<String> uniqueSubresources; + + Vector<Node*>::iterator it = nodes.begin(); + Vector<Node*>::iterator end = nodes.end(); + + for (; it != end; ++it) { + Frame* childFrame; + if (((*it)->hasTagName(HTMLNames::frameTag) || (*it)->hasTagName(HTMLNames::iframeTag) || (*it)->hasTagName(HTMLNames::objectTag)) && + (childFrame = static_cast<HTMLFrameOwnerElement*>(*it)->contentFrame())) { + RefPtr<LegacyWebArchive> subframeArchive; + if (Document* document = childFrame->document()) + subframeArchive = LegacyWebArchive::create(document); + else + subframeArchive = create(childFrame); + + if (subframeArchive) + subframeArchives.append(subframeArchive); + else + LOG_ERROR("Unabled to archive subframe %s", childFrame->tree()->name().string().utf8().data()); + } else { + Vector<KURL> subresourceURLs; + (*it)->getSubresourceURLs(subresourceURLs); + + DocumentLoader* documentLoader = frame->loader()->documentLoader(); + for (unsigned i = 0; i < subresourceURLs.size(); ++i) { + if (uniqueSubresources.contains(subresourceURLs[i].string())) + continue; + uniqueSubresources.add(subresourceURLs[i].string()); + RefPtr<ArchiveResource> resource = documentLoader->subresource(subresourceURLs[i]); + if (resource) + subresources.append(resource.release()); + else + // FIXME: should do something better than spew to console here + LOG_ERROR("Failed to archive subresource for %s", subresourceURLs[i].string().utf8().data()); + } + } + } + + return create(mainResource, subresources, subframeArchives); +} + +PassRefPtr<LegacyWebArchive> LegacyWebArchive::createFromSelection(Frame* frame) +{ + if (!frame) + return 0; + + RefPtr<Range> selectionRange = frame->selection()->toRange(); + Vector<Node*> nodeList; + String markupString = frame->documentTypeString() + createMarkup(selectionRange.get(), &nodeList, AnnotateForInterchange); + + RefPtr<LegacyWebArchive> archive = create(markupString, frame, nodeList); + + if (!frame->isFrameSet()) + return archive.release(); + + // Wrap the frameset document in an iframe so it can be pasted into + // another document (which will have a body or frameset of its own). + String iframeMarkup = String::format("<iframe frameborder=\"no\" marginwidth=\"0\" marginheight=\"0\" width=\"98%%\" height=\"98%%\" src=\"%s\"></iframe>", + frame->loader()->documentLoader()->response().url().string().utf8().data()); + RefPtr<ArchiveResource> iframeResource = ArchiveResource::create(utf8Buffer(iframeMarkup), blankURL(), "text/html", "UTF-8", String()); + + Vector<PassRefPtr<ArchiveResource> > subresources; + + Vector<PassRefPtr<LegacyWebArchive> > subframeArchives; + subframeArchives.append(archive); + + archive = LegacyWebArchive::create(iframeResource.release(), subresources, subframeArchives); + + return archive.release(); +} + +} diff --git a/WebCore/loader/archive/cf/LegacyWebArchive.h b/WebCore/loader/archive/cf/LegacyWebArchive.h new file mode 100644 index 0000000..70faba5 --- /dev/null +++ b/WebCore/loader/archive/cf/LegacyWebArchive.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 LegacyWebArchive_h +#define LegacyWebArchive_h + +#include "Archive.h" + +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class Frame; +class Node; +class Range; + +class LegacyWebArchive : public Archive { +public: + static PassRefPtr<LegacyWebArchive> create(); + static PassRefPtr<LegacyWebArchive> create(SharedBuffer*); + static PassRefPtr<LegacyWebArchive> create(PassRefPtr<ArchiveResource> mainResource, Vector<PassRefPtr<ArchiveResource> >& subresources, Vector<PassRefPtr<LegacyWebArchive> >& subframeArchives); + static PassRefPtr<LegacyWebArchive> create(Node*); + static PassRefPtr<LegacyWebArchive> create(Frame*); + static PassRefPtr<LegacyWebArchive> createFromSelection(Frame* frame); + static PassRefPtr<LegacyWebArchive> create(Range*); + static PassRefPtr<LegacyWebArchive> create(const String& markupString, Frame*, Vector<Node*>& nodes); + + RetainPtr<CFDataRef> rawDataRepresentation(); + +private: + LegacyWebArchive(); + bool init(SharedBuffer*); + bool extract(CFDictionaryRef); + +}; + +ResourceResponse createResourceResponseFromMacArchivedData(CFDataRef); +RetainPtr<CFDataRef> propertyListDataFromResourceResponse(const ResourceResponse&); + +} + +#endif // Archive diff --git a/WebCore/loader/archive/cf/LegacyWebArchiveMac.mm b/WebCore/loader/archive/cf/LegacyWebArchiveMac.mm new file mode 100644 index 0000000..b853a15 --- /dev/null +++ b/WebCore/loader/archive/cf/LegacyWebArchiveMac.mm @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "LegacyWebArchive.h" + +namespace WebCore { + +static const NSString *LegacyWebArchiveResourceResponseKey = @"WebResourceResponse"; + +// FIXME: Is it possible to parse in a Cocoa-style resource response manually, +// without NSKeyed(Un)Archiver, manipulating plists directly? +ResourceResponse createResourceResponseFromMacArchivedData(CFDataRef responseData) +{ + ASSERT(responseData); + if (!responseData) + return ResourceResponse(); + + NSURLResponse *response = nil; + NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:(NSData *)responseData]; + @try { + id responseObject = [unarchiver decodeObjectForKey:LegacyWebArchiveResourceResponseKey]; + if ([responseObject isKindOfClass:[NSURLResponse class]]) + response = responseObject; + [unarchiver finishDecoding]; + } @catch(id) { + response = nil; + } + [unarchiver release]; + + return ResourceResponse(response); +} + +RetainPtr<CFDataRef> propertyListDataFromResourceResponse(const ResourceResponse& response) +{ + NSURLResponse *nsResponse = response.nsURLResponse(); + if (!nsResponse) + return 0; + + NSMutableData *responseData = (NSMutableData *)CFDataCreateMutable(0, 0); + NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:responseData]; + [archiver encodeObject:nsResponse forKey:LegacyWebArchiveResourceResponseKey]; + [archiver finishEncoding]; + [archiver release]; + + return RetainPtr<CFDataRef>(AdoptCF, (CFDataRef)responseData); +} + +} diff --git a/WebCore/loader/icon/IconDatabase.cpp b/WebCore/loader/icon/IconDatabase.cpp new file mode 100644 index 0000000..72e57fe --- /dev/null +++ b/WebCore/loader/icon/IconDatabase.cpp @@ -0,0 +1,2053 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2007 Justin Haygood (jhaygood@reaktix.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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. + */ + +#include "config.h" +#include "IconDatabase.h" + +#include "AutodrainedPool.h" +#include "CString.h" +#include "DocumentLoader.h" +#include "FileSystem.h" +#include "IconDatabaseClient.h" +#include "IconRecord.h" +#include "Image.h" +#include "IntSize.h" +#include "KURL.h" +#include "Logging.h" +#include "PageURLRecord.h" +#include "SQLiteStatement.h" +#include "SQLiteTransaction.h" +#include "SystemTime.h" +#include <runtime/InitializeThreading.h> +#include <wtf/MainThread.h> + +#if PLATFORM(WIN_OS) +#include <windows.h> +#include <winbase.h> +#include <shlobj.h> +#else +#include <sys/stat.h> +#endif + +#if PLATFORM(DARWIN) +#include <pthread.h> +#endif + +#include <errno.h> + +// For methods that are meant to support API from the main thread - should not be called internally +#define ASSERT_NOT_SYNC_THREAD() ASSERT(!m_syncThreadRunning || !IS_ICON_SYNC_THREAD()) + +// For methods that are meant to support the sync thread ONLY +#define IS_ICON_SYNC_THREAD() (m_syncThread == currentThread()) +#define ASSERT_ICON_SYNC_THREAD() ASSERT(IS_ICON_SYNC_THREAD()) + +#if PLATFORM(QT) +#define CAN_THEME_URL_ICON +#endif + +namespace WebCore { + +static IconDatabase* sharedIconDatabase = 0; +static int databaseCleanupCounter = 0; + +// This version number is in the DB and marks the current generation of the schema +// Currently, a mismatched schema causes the DB to be wiped and reset. This isn't +// so bad during development but in the future, we would need to write a conversion +// function to advance older released schemas to "current" +const int currentDatabaseVersion = 6; + +// Icons expire once every 4 days +const int iconExpirationTime = 60*60*24*4; + +const int updateTimerDelay = 5; + +static bool checkIntegrityOnOpen = false; + +#ifndef NDEBUG +static String urlForLogging(const String& url) +{ + static unsigned urlTruncationLength = 120; + + if (url.length() < urlTruncationLength) + return url; + return url.substring(0, urlTruncationLength) + "..."; +} +#endif + +static IconDatabaseClient* defaultClient() +{ + static IconDatabaseClient* defaultClient = new IconDatabaseClient(); + return defaultClient; +} + +IconDatabase* iconDatabase() +{ + if (!sharedIconDatabase) { + JSC::initializeThreading(); + sharedIconDatabase = new IconDatabase; + } + return sharedIconDatabase; +} + +// ************************ +// *** Main Thread Only *** +// ************************ + +void IconDatabase::setClient(IconDatabaseClient* client) +{ + // We don't allow a null client, because we never null check it anywhere in this code + // Also don't allow a client change after the thread has already began + // (setting the client should occur before the database is opened) + ASSERT(client); + ASSERT(!m_syncThreadRunning); + if (!client || m_syncThreadRunning) + return; + + m_client = client; +} + +bool IconDatabase::open(const String& databasePath) +{ + ASSERT_NOT_SYNC_THREAD(); + + if (!m_isEnabled) + return false; + + if (isOpen()) { + LOG_ERROR("Attempt to reopen the IconDatabase which is already open. Must close it first."); + return false; + } + + m_databaseDirectory = databasePath.copy(); + + // Formulate the full path for the database file + m_completeDatabasePath = pathByAppendingComponent(m_databaseDirectory, defaultDatabaseFilename()); + + // Lock here as well as first thing in the thread so the thread doesn't actually commence until the createThread() call + // completes and m_syncThreadRunning is properly set + m_syncLock.lock(); + m_syncThread = createThread(IconDatabase::iconDatabaseSyncThreadStart, this, "WebCore::IconDatabase"); + m_syncLock.unlock(); + if (!m_syncThread) + return false; + return true; +} + +void IconDatabase::close() +{ +#ifdef ANDROID + // Since we close and reopen the database within the same process, reset + // this flag + m_initialPruningComplete = false; +#endif + ASSERT_NOT_SYNC_THREAD(); + + if (m_syncThreadRunning) { + // Set the flag to tell the sync thread to wrap it up + m_threadTerminationRequested = true; + + // Wake up the sync thread if it's waiting + wakeSyncThread(); + + // Wait for the sync thread to terminate + waitForThreadCompletion(m_syncThread, 0); + } + + m_syncThreadRunning = false; + m_threadTerminationRequested = false; + m_removeIconsRequested = false; +} + +void IconDatabase::removeAllIcons() +{ + ASSERT_NOT_SYNC_THREAD(); + + if (!isOpen()) + return; + + LOG(IconDatabase, "Requesting background thread to remove all icons"); + + // Clear the in-memory record of every IconRecord, anything waiting to be read from disk, and anything waiting to be written to disk + { + MutexLocker locker(m_urlAndIconLock); + + // Clear the IconRecords for every page URL - RefCounting will cause the IconRecords themselves to be deleted + // We don't delete the actual PageRecords because we have the "retain icon for url" count to keep track of + HashMap<String, PageURLRecord*>::iterator iter = m_pageURLToRecordMap.begin(); + HashMap<String, PageURLRecord*>::iterator end = m_pageURLToRecordMap.end(); + for (; iter != end; ++iter) + (*iter).second->setIconRecord(0); + + // Clear the iconURL -> IconRecord map + m_iconURLToRecordMap.clear(); + + // Clear all in-memory records of things that need to be synced out to disk + { + MutexLocker locker(m_pendingSyncLock); + m_pageURLsPendingSync.clear(); + m_iconsPendingSync.clear(); + } + + // Clear all in-memory records of things that need to be read in from disk + { + MutexLocker locker(m_pendingReadingLock); + m_pageURLsPendingImport.clear(); + m_pageURLsInterestedInIcons.clear(); + m_iconsPendingReading.clear(); + m_loadersPendingDecision.clear(); + } + } + + m_removeIconsRequested = true; + wakeSyncThread(); +} + +Image* IconDatabase::iconForPageURL(const String& pageURLOriginal, const IntSize& size) +{ + ASSERT_NOT_SYNC_THREAD(); + + // pageURLOriginal cannot be stored without being deep copied first. + // We should go our of our way to only copy it if we have to store it + + if (!isOpen() || pageURLOriginal.isEmpty()) + return defaultIcon(size); + + MutexLocker locker(m_urlAndIconLock); + + String pageURLCopy; // Creates a null string for easy testing + + PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal); + if (!pageRecord) { + pageURLCopy = pageURLOriginal.copy(); + pageRecord = getOrCreatePageURLRecord(pageURLCopy); + } + + // If pageRecord is NULL, one of two things is true - + // 1 - The initial url import is incomplete and this pageURL was marked to be notified once it is complete if an iconURL exists + // 2 - The initial url import IS complete and this pageURL has no icon + if (!pageRecord) { + MutexLocker locker(m_pendingReadingLock); + + // Import is ongoing, there might be an icon. In this case, register to be notified when the icon comes in + // If we ever reach this condition, we know we've already made the pageURL copy + if (!m_iconURLImportComplete) + m_pageURLsInterestedInIcons.add(pageURLCopy); + + return 0; + } + + IconRecord* iconRecord = pageRecord->iconRecord(); + + // If the initial URL import isn't complete, it's possible to have a PageURL record without an associated icon + // In this case, the pageURL is already in the set to alert the client when the iconURL mapping is complete so + // we can just bail now + if (!m_iconURLImportComplete && !iconRecord) + return 0; + + // The only way we should *not* have an icon record is if this pageURL is retained but has no icon yet - make sure of that + ASSERT(iconRecord || m_retainedPageURLs.contains(pageURLOriginal)); + + if (!iconRecord) + return 0; + + // If it's a new IconRecord object that doesn't have its imageData set yet, + // mark it to be read by the background thread + if (iconRecord->imageDataStatus() == ImageDataStatusUnknown) { + if (pageURLCopy.isNull()) + pageURLCopy = pageURLOriginal.copy(); + + MutexLocker locker(m_pendingReadingLock); + m_pageURLsInterestedInIcons.add(pageURLCopy); + m_iconsPendingReading.add(iconRecord); + wakeSyncThread(); + return 0; + } + + // If the size parameter was (0, 0) that means the caller of this method just wanted the read from disk to be kicked off + // and isn't actually interested in the image return value + if (size == IntSize(0, 0)) + return 0; + + // PARANOID DISCUSSION: This method makes some assumptions. It returns a WebCore::image which the icon database might dispose of at anytime in the future, + // and Images aren't ref counted. So there is no way for the client to guarantee continued existence of the image. + // This has *always* been the case, but in practice clients would always create some other platform specific representation of the image + // and drop the raw Image*. On Mac an NSImage, and on windows drawing into an HBITMAP. + // The async aspect adds a huge question - what if the image is deleted before the platform specific API has a chance to create its own + // representation out of it? + // If an image is read in from the icondatabase, we do *not* overwrite any image data that exists in the in-memory cache. + // This is because we make the assumption that anything in memory is newer than whatever is in the database. + // So the only time the data will be set from the second thread is when it is INITIALLY being read in from the database, but we would never + // delete the image on the secondary thread if the image already exists. + return iconRecord->image(size); +} + +void IconDatabase::readIconForPageURLFromDisk(const String& pageURL) +{ + // The effect of asking for an Icon for a pageURL automatically queues it to be read from disk + // if it hasn't already been set in memory. The special IntSize (0, 0) is a special way of telling + // that method "I don't care about the actual Image, i just want you to make sure you're getting it from disk. + iconForPageURL(pageURL, IntSize(0,0)); +} + +String IconDatabase::iconURLForPageURL(const String& pageURLOriginal) +{ + ASSERT_NOT_SYNC_THREAD(); + + // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first + // Also, in the case we have a real answer for the caller, we must deep copy that as well + + if (!isOpen() || pageURLOriginal.isEmpty()) + return String(); + + MutexLocker locker(m_urlAndIconLock); + + PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal); + if (!pageRecord) + pageRecord = getOrCreatePageURLRecord(pageURLOriginal.copy()); + + // If pageRecord is NULL, one of two things is true - + // 1 - The initial url import is incomplete and this pageURL has already been marked to be notified once it is complete if an iconURL exists + // 2 - The initial url import IS complete and this pageURL has no icon + if (!pageRecord) + return String(); + + // Possible the pageRecord is around because it's a retained pageURL with no iconURL, so we have to check + return pageRecord->iconRecord() ? pageRecord->iconRecord()->iconURL().copy() : String(); +} + +#ifdef CAN_THEME_URL_ICON +static inline void loadDefaultIconRecord(IconRecord* defaultIconRecord) +{ + defaultIconRecord->loadImageFromResource("urlIcon"); +} +#else +static inline void loadDefaultIconRecord(IconRecord* defaultIconRecord) +{ + static const unsigned char defaultIconData[] = { 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x03, 0x32, 0x80, 0x00, 0x20, 0x50, 0x38, 0x24, 0x16, 0x0D, 0x07, 0x84, 0x42, 0x61, 0x50, 0xB8, + 0x64, 0x08, 0x18, 0x0D, 0x0A, 0x0B, 0x84, 0xA2, 0xA1, 0xE2, 0x08, 0x5E, 0x39, 0x28, 0xAF, 0x48, 0x24, 0xD3, 0x53, 0x9A, 0x37, 0x1D, 0x18, 0x0E, 0x8A, 0x4B, 0xD1, 0x38, + 0xB0, 0x7C, 0x82, 0x07, 0x03, 0x82, 0xA2, 0xE8, 0x6C, 0x2C, 0x03, 0x2F, 0x02, 0x82, 0x41, 0xA1, 0xE2, 0xF8, 0xC8, 0x84, 0x68, 0x6D, 0x1C, 0x11, 0x0A, 0xB7, 0xFA, 0x91, + 0x6E, 0xD1, 0x7F, 0xAF, 0x9A, 0x4E, 0x87, 0xFB, 0x19, 0xB0, 0xEA, 0x7F, 0xA4, 0x95, 0x8C, 0xB7, 0xF9, 0xA9, 0x0A, 0xA9, 0x7F, 0x8C, 0x88, 0x66, 0x96, 0xD4, 0xCA, 0x69, + 0x2F, 0x00, 0x81, 0x65, 0xB0, 0x29, 0x90, 0x7C, 0xBA, 0x2B, 0x21, 0x1E, 0x5C, 0xE6, 0xB4, 0xBD, 0x31, 0xB6, 0xE7, 0x7A, 0xBF, 0xDD, 0x6F, 0x37, 0xD3, 0xFD, 0xD8, 0xF2, + 0xB6, 0xDB, 0xED, 0xAC, 0xF7, 0x03, 0xC5, 0xFE, 0x77, 0x53, 0xB6, 0x1F, 0xE6, 0x24, 0x8B, 0x1D, 0xFE, 0x26, 0x20, 0x9E, 0x1C, 0xE0, 0x80, 0x65, 0x7A, 0x18, 0x02, 0x01, + 0x82, 0xC5, 0xA0, 0xC0, 0xF1, 0x89, 0xBA, 0x23, 0x30, 0xAD, 0x1F, 0xE7, 0xE5, 0x5B, 0x6D, 0xFE, 0xE7, 0x78, 0x3E, 0x1F, 0xEE, 0x97, 0x8B, 0xE7, 0x37, 0x9D, 0xCF, 0xE7, + 0x92, 0x8B, 0x87, 0x0B, 0xFC, 0xA0, 0x8E, 0x68, 0x3F, 0xC6, 0x27, 0xA6, 0x33, 0xFC, 0x36, 0x5B, 0x59, 0x3F, 0xC1, 0x02, 0x63, 0x3B, 0x74, 0x00, 0x03, 0x07, 0x0B, 0x61, + 0x00, 0x20, 0x60, 0xC9, 0x08, 0x00, 0x1C, 0x25, 0x9F, 0xE0, 0x12, 0x8A, 0xD5, 0xFE, 0x6B, 0x4F, 0x35, 0x9F, 0xED, 0xD7, 0x4B, 0xD9, 0xFE, 0x8A, 0x59, 0xB8, 0x1F, 0xEC, + 0x56, 0xD3, 0xC1, 0xFE, 0x63, 0x4D, 0xF2, 0x83, 0xC6, 0xB6, 0x1B, 0xFC, 0x34, 0x68, 0x61, 0x3F, 0xC1, 0xA6, 0x25, 0xEB, 0xFC, 0x06, 0x58, 0x5C, 0x3F, 0xC0, 0x03, 0xE4, + 0xC3, 0xFC, 0x04, 0x0F, 0x1A, 0x6F, 0xE0, 0xE0, 0x20, 0xF9, 0x61, 0x7A, 0x02, 0x28, 0x2B, 0xBC, 0x46, 0x25, 0xF3, 0xFC, 0x66, 0x3D, 0x99, 0x27, 0xF9, 0x7E, 0x6B, 0x1D, + 0xC7, 0xF9, 0x2C, 0x5E, 0x1C, 0x87, 0xF8, 0xC0, 0x4D, 0x9A, 0xE7, 0xF8, 0xDA, 0x51, 0xB2, 0xC1, 0x68, 0xF2, 0x64, 0x1F, 0xE1, 0x50, 0xED, 0x0A, 0x04, 0x23, 0x79, 0x8A, + 0x7F, 0x82, 0xA3, 0x39, 0x80, 0x7F, 0x80, 0xC2, 0xB1, 0x5E, 0xF7, 0x04, 0x2F, 0xB2, 0x10, 0x02, 0x86, 0x63, 0xC9, 0xCC, 0x07, 0xBF, 0x87, 0xF8, 0x4A, 0x38, 0xAF, 0xC1, + 0x88, 0xF8, 0x66, 0x1F, 0xE1, 0xD9, 0x08, 0xD4, 0x8F, 0x25, 0x5B, 0x4A, 0x49, 0x97, 0x87, 0x39, 0xFE, 0x25, 0x12, 0x10, 0x68, 0xAA, 0x4A, 0x2F, 0x42, 0x29, 0x12, 0x69, + 0x9F, 0xE1, 0xC1, 0x00, 0x67, 0x1F, 0xE1, 0x58, 0xED, 0x00, 0x83, 0x23, 0x49, 0x82, 0x7F, 0x81, 0x21, 0xE0, 0xFC, 0x73, 0x21, 0x00, 0x50, 0x7D, 0x2B, 0x84, 0x03, 0x83, + 0xC2, 0x1B, 0x90, 0x06, 0x69, 0xFE, 0x23, 0x91, 0xAE, 0x50, 0x9A, 0x49, 0x32, 0xC2, 0x89, 0x30, 0xE9, 0x0A, 0xC4, 0xD9, 0xC4, 0x7F, 0x94, 0xA6, 0x51, 0xDE, 0x7F, 0x9D, + 0x07, 0x89, 0xF6, 0x7F, 0x91, 0x85, 0xCA, 0x88, 0x25, 0x11, 0xEE, 0x50, 0x7C, 0x43, 0x35, 0x21, 0x60, 0xF1, 0x0D, 0x82, 0x62, 0x39, 0x07, 0x2C, 0x20, 0xE0, 0x80, 0x72, + 0x34, 0x17, 0xA1, 0x80, 0xEE, 0xF0, 0x89, 0x24, 0x74, 0x1A, 0x2C, 0x93, 0xB3, 0x78, 0xCC, 0x52, 0x9D, 0x6A, 0x69, 0x56, 0xBB, 0x0D, 0x85, 0x69, 0xE6, 0x7F, 0x9E, 0x27, + 0xB9, 0xFD, 0x50, 0x54, 0x47, 0xF9, 0xCC, 0x78, 0x9F, 0x87, 0xF9, 0x98, 0x70, 0xB9, 0xC2, 0x91, 0x2C, 0x6D, 0x1F, 0xE1, 0xE1, 0x00, 0xBF, 0x02, 0xC1, 0xF5, 0x18, 0x84, + 0x01, 0xE1, 0x48, 0x8C, 0x42, 0x07, 0x43, 0xC9, 0x76, 0x7F, 0x8B, 0x04, 0xE4, 0xDE, 0x35, 0x95, 0xAB, 0xB0, 0xF0, 0x5C, 0x55, 0x23, 0xF9, 0x7E, 0x7E, 0x9F, 0xE4, 0x0C, + 0xA7, 0x55, 0x47, 0xC7, 0xF9, 0xE6, 0xCF, 0x1F, 0xE7, 0x93, 0x35, 0x52, 0x54, 0x63, 0x19, 0x46, 0x73, 0x1F, 0xE2, 0x61, 0x08, 0xF0, 0x82, 0xE1, 0x80, 0x92, 0xF9, 0x20, + 0xC0, 0x28, 0x18, 0x0A, 0x05, 0xA1, 0xA2, 0xF8, 0x6E, 0xDB, 0x47, 0x49, 0xFE, 0x3E, 0x17, 0xB6, 0x61, 0x13, 0x1A, 0x29, 0x26, 0xA9, 0xFE, 0x7F, 0x92, 0x70, 0x69, 0xFE, + 0x4C, 0x2F, 0x55, 0x01, 0xF1, 0x54, 0xD4, 0x35, 0x49, 0x4A, 0x69, 0x59, 0x83, 0x81, 0x58, 0x76, 0x9F, 0xE2, 0x20, 0xD6, 0x4C, 0x9B, 0xA0, 0x48, 0x1E, 0x0B, 0xB7, 0x48, + 0x58, 0x26, 0x11, 0x06, 0x42, 0xE8, 0xA4, 0x40, 0x17, 0x27, 0x39, 0x00, 0x60, 0x2D, 0xA4, 0xC3, 0x2C, 0x7F, 0x94, 0x56, 0xE4, 0xE1, 0x77, 0x1F, 0xE5, 0xB9, 0xD7, 0x66, + 0x1E, 0x07, 0xB3, 0x3C, 0x63, 0x1D, 0x35, 0x49, 0x0E, 0x63, 0x2D, 0xA2, 0xF1, 0x12, 0x60, 0x1C, 0xE0, 0xE0, 0x52, 0x1B, 0x8B, 0xAC, 0x38, 0x0E, 0x07, 0x03, 0x60, 0x28, + 0x1C, 0x0E, 0x87, 0x00, 0xF0, 0x66, 0x27, 0x11, 0xA2, 0xC1, 0x02, 0x5A, 0x1C, 0xE4, 0x21, 0x83, 0x1F, 0x13, 0x86, 0xFA, 0xD2, 0x55, 0x1D, 0xD6, 0x61, 0xBC, 0x77, 0xD3, + 0xE6, 0x91, 0xCB, 0x4C, 0x90, 0xA6, 0x25, 0xB8, 0x2F, 0x90, 0xC5, 0xA9, 0xCE, 0x12, 0x07, 0x02, 0x91, 0x1B, 0x9F, 0x68, 0x00, 0x16, 0x76, 0x0D, 0xA1, 0x00, 0x08, 0x06, + 0x03, 0x81, 0xA0, 0x20, 0x1A, 0x0D, 0x06, 0x80, 0x30, 0x24, 0x12, 0x89, 0x20, 0x98, 0x4A, 0x1F, 0x0F, 0x21, 0xA0, 0x9E, 0x36, 0x16, 0xC2, 0x88, 0xE6, 0x48, 0x9B, 0x83, + 0x31, 0x1C, 0x55, 0x1E, 0x43, 0x59, 0x1A, 0x56, 0x1E, 0x42, 0xF0, 0xFA, 0x4D, 0x1B, 0x9B, 0x08, 0xDC, 0x5B, 0x02, 0xA1, 0x30, 0x7E, 0x3C, 0xEE, 0x5B, 0xA6, 0xDD, 0xB8, + 0x6D, 0x5B, 0x62, 0xB7, 0xCD, 0xF3, 0x9C, 0xEA, 0x04, 0x80, 0x80, 0x00, 0x00, 0x0E, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x01, 0x01, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 0xE0, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x01, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x11, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x08, 0x01, 0x15, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x01, 0x16, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x01, 0x17, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x29, 0x01, 0x1A, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xE8, 0x01, 0x1B, 0x00, 0x05, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x03, 0xF0, 0x01, 0x1C, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, + 0x00, 0x00, 0x01, 0x52, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0A, + 0xFC, 0x80, 0x00, 0x00, 0x27, 0x10, 0x00, 0x0A, 0xFC, 0x80, 0x00, 0x00, 0x27, 0x10 }; + + static RefPtr<SharedBuffer> defaultIconBuffer(SharedBuffer::create(defaultIconData, sizeof(defaultIconData))); + defaultIconRecord->setImageData(defaultIconBuffer); +} +#endif + +Image* IconDatabase::defaultIcon(const IntSize& size) +{ + ASSERT_NOT_SYNC_THREAD(); + + + if (!m_defaultIconRecord) { + m_defaultIconRecord = IconRecord::create("urlIcon"); + loadDefaultIconRecord(m_defaultIconRecord.get()); + } + + return m_defaultIconRecord->image(size); +} + + +void IconDatabase::retainIconForPageURL(const String& pageURLOriginal) +{ + ASSERT_NOT_SYNC_THREAD(); + + // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first + + if (!isEnabled() || pageURLOriginal.isEmpty()) + return; + + MutexLocker locker(m_urlAndIconLock); + + PageURLRecord* record = m_pageURLToRecordMap.get(pageURLOriginal); + + String pageURL; + + if (!record) { + pageURL = pageURLOriginal.copy(); + + record = new PageURLRecord(pageURL); + m_pageURLToRecordMap.set(pageURL, record); + } + + if (!record->retain()) { + if (pageURL.isNull()) + pageURL = pageURLOriginal.copy(); + + // This page just had its retain count bumped from 0 to 1 - Record that fact + m_retainedPageURLs.add(pageURL); + + // If we read the iconURLs yet, we want to avoid any pageURL->iconURL lookups and the pageURLsPendingDeletion is moot, + // so we bail here and skip those steps + if (!m_iconURLImportComplete) + return; + + MutexLocker locker(m_pendingSyncLock); + // If this pageURL waiting to be sync'ed, update the sync record + // This saves us in the case where a page was ready to be deleted from the database but was just retained - so theres no need to delete it! + if (!m_privateBrowsingEnabled && m_pageURLsPendingSync.contains(pageURL)) { + LOG(IconDatabase, "Bringing %s back from the brink", pageURL.ascii().data()); + m_pageURLsPendingSync.set(pageURL, record->snapshot()); + } + } +} + +void IconDatabase::releaseIconForPageURL(const String& pageURLOriginal) +{ + ASSERT_NOT_SYNC_THREAD(); + + // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first + + if (!isEnabled() || pageURLOriginal.isEmpty()) + return; + + MutexLocker locker(m_urlAndIconLock); + + // Check if this pageURL is actually retained + if (!m_retainedPageURLs.contains(pageURLOriginal)) { + LOG_ERROR("Attempting to release icon for URL %s which is not retained", urlForLogging(pageURLOriginal).ascii().data()); + return; + } + + // Get its retain count - if it's retained, we'd better have a PageURLRecord for it + PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal); + ASSERT(pageRecord); + LOG(IconDatabase, "Releasing pageURL %s to a retain count of %i", urlForLogging(pageURLOriginal).ascii().data(), pageRecord->retainCount() - 1); + ASSERT(pageRecord->retainCount() > 0); + + // If it still has a positive retain count, store the new count and bail + if (pageRecord->release()) + return; + + // This pageRecord has now been fully released. Do the appropriate cleanup + LOG(IconDatabase, "No more retainers for PageURL %s", urlForLogging(pageURLOriginal).ascii().data()); + m_pageURLToRecordMap.remove(pageURLOriginal); + m_retainedPageURLs.remove(pageURLOriginal); + + // Grab the iconRecord for later use (and do a sanity check on it for kicks) + IconRecord* iconRecord = pageRecord->iconRecord(); + + ASSERT(!iconRecord || (iconRecord && m_iconURLToRecordMap.get(iconRecord->iconURL()) == iconRecord)); + + { + MutexLocker locker(m_pendingReadingLock); + + // Since this pageURL is going away, there's no reason anyone would ever be interested in its read results + if (!m_iconURLImportComplete) + m_pageURLsPendingImport.remove(pageURLOriginal); + m_pageURLsInterestedInIcons.remove(pageURLOriginal); + + // If this icon is down to it's last retainer, we don't care about reading it in from disk anymore + if (iconRecord && iconRecord->hasOneRef()) { + m_iconURLToRecordMap.remove(iconRecord->iconURL()); + m_iconsPendingReading.remove(iconRecord); + } + } + + // Mark stuff for deletion from the database only if we're not in private browsing + if (!m_privateBrowsingEnabled) { + MutexLocker locker(m_pendingSyncLock); + m_pageURLsPendingSync.set(pageURLOriginal.copy(), pageRecord->snapshot(true)); + + // If this page is the last page to refer to a particular IconRecord, that IconRecord needs to + // be marked for deletion + if (iconRecord && iconRecord->hasOneRef()) + m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true)); + } + + delete pageRecord; + + if (isOpen()) + scheduleOrDeferSyncTimer(); +} + +void IconDatabase::setIconDataForIconURL(PassRefPtr<SharedBuffer> dataOriginal, const String& iconURLOriginal) +{ + ASSERT_NOT_SYNC_THREAD(); + + // Cannot do anything with dataOriginal or iconURLOriginal that would end up storing them without deep copying first + + if (!isOpen() || iconURLOriginal.isEmpty()) + return; + + RefPtr<SharedBuffer> data = dataOriginal ? dataOriginal->copy() : 0; + String iconURL = iconURLOriginal.copy(); + + Vector<String> pageURLs; + { + MutexLocker locker(m_urlAndIconLock); + + // If this icon was pending a read, remove it from that set because this new data should override what is on disk + RefPtr<IconRecord> icon = m_iconURLToRecordMap.get(iconURL); + if (icon) { + MutexLocker locker(m_pendingReadingLock); + m_iconsPendingReading.remove(icon.get()); + } else + icon = getOrCreateIconRecord(iconURL); + + // Update the data and set the time stamp + icon->setImageData(data); + icon->setTimestamp((int)currentTime()); + + // Copy the current retaining pageURLs - if any - to notify them of the change + pageURLs.appendRange(icon->retainingPageURLs().begin(), icon->retainingPageURLs().end()); + + // Mark the IconRecord as requiring an update to the database only if private browsing is disabled + if (!m_privateBrowsingEnabled) { + MutexLocker locker(m_pendingSyncLock); + m_iconsPendingSync.set(iconURL, icon->snapshot()); + } + + if (icon->hasOneRef()) { + ASSERT(icon->retainingPageURLs().isEmpty()); + LOG(IconDatabase, "Icon for icon url %s is about to be destroyed - removing mapping for it", urlForLogging(icon->iconURL()).ascii().data()); + m_iconURLToRecordMap.remove(icon->iconURL()); + } + } + + // Send notification out regarding all PageURLs that retain this icon + // But not if we're on the sync thread because that implies this mapping + // comes from the initial import which we don't want notifications for + if (!IS_ICON_SYNC_THREAD()) { + // Start the timer to commit this change - or further delay the timer if it was already started + scheduleOrDeferSyncTimer(); + + // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go + // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up + AutodrainedPool pool(25); + + for (unsigned i = 0; i < pageURLs.size(); ++i) { + LOG(IconDatabase, "Dispatching notification that retaining pageURL %s has a new icon", urlForLogging(pageURLs[i]).ascii().data()); + m_client->dispatchDidAddIconForPageURL(pageURLs[i]); + + pool.cycle(); + } + } +} + +void IconDatabase::setIconURLForPageURL(const String& iconURLOriginal, const String& pageURLOriginal) +{ + ASSERT_NOT_SYNC_THREAD(); + + // Cannot do anything with iconURLOriginal or pageURLOriginal that would end up storing them without deep copying first + + ASSERT(!iconURLOriginal.isEmpty()); + + if (!isOpen() || pageURLOriginal.isEmpty()) + return; + + String iconURL, pageURL; + + { + MutexLocker locker(m_urlAndIconLock); + + PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal); + + // If the urls already map to each other, bail. + // This happens surprisingly often, and seems to cream iBench performance + if (pageRecord && pageRecord->iconRecord() && pageRecord->iconRecord()->iconURL() == iconURLOriginal) + return; + + pageURL = pageURLOriginal.copy(); + iconURL = iconURLOriginal.copy(); + + if (!pageRecord) { + pageRecord = new PageURLRecord(pageURL); + m_pageURLToRecordMap.set(pageURL, pageRecord); + } + + RefPtr<IconRecord> iconRecord = pageRecord->iconRecord(); + + // Otherwise, set the new icon record for this page + pageRecord->setIconRecord(getOrCreateIconRecord(iconURL)); + + // If the current icon has only a single ref left, it is about to get wiped out. + // Remove it from the in-memory records and don't bother reading it in from disk anymore + if (iconRecord && iconRecord->hasOneRef()) { + ASSERT(iconRecord->retainingPageURLs().size() == 0); + LOG(IconDatabase, "Icon for icon url %s is about to be destroyed - removing mapping for it", urlForLogging(iconRecord->iconURL()).ascii().data()); + m_iconURLToRecordMap.remove(iconRecord->iconURL()); + MutexLocker locker(m_pendingReadingLock); + m_iconsPendingReading.remove(iconRecord.get()); + } + + // And mark this mapping to be added to the database + if (!m_privateBrowsingEnabled) { + MutexLocker locker(m_pendingSyncLock); + m_pageURLsPendingSync.set(pageURL, pageRecord->snapshot()); + + // If the icon is on its last ref, mark it for deletion + if (iconRecord && iconRecord->hasOneRef()) + m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true)); + } + } + + // Since this mapping is new, send the notification out - but not if we're on the sync thread because that implies this mapping + // comes from the initial import which we don't want notifications for + if (!IS_ICON_SYNC_THREAD()) { + // Start the timer to commit this change - or further delay the timer if it was already started + scheduleOrDeferSyncTimer(); + + LOG(IconDatabase, "Dispatching notification that we changed an icon mapping for url %s", urlForLogging(pageURL).ascii().data()); + AutodrainedPool pool; + m_client->dispatchDidAddIconForPageURL(pageURL); + } +} + +IconLoadDecision IconDatabase::loadDecisionForIconURL(const String& iconURL, DocumentLoader* notificationDocumentLoader) +{ + ASSERT_NOT_SYNC_THREAD(); + + if (!isOpen() || iconURL.isEmpty()) + return IconLoadNo; + + // If we have a IconRecord, it should also have its timeStamp marked because there is only two times when we create the IconRecord: + // 1 - When we read the icon urls from disk, getting the timeStamp at the same time + // 2 - When we get a new icon from the loader, in which case the timestamp is set at that time + { + MutexLocker locker(m_urlAndIconLock); + if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL)) { + LOG(IconDatabase, "Found expiration time on a present icon based on existing IconRecord"); + return (int)currentTime() - icon->getTimestamp() > iconExpirationTime ? IconLoadYes : IconLoadNo; + } + } + + // If we don't have a record for it, but we *have* imported all iconURLs from disk, then we should load it now + MutexLocker readingLocker(m_pendingReadingLock); + if (m_iconURLImportComplete) + return IconLoadYes; + + // Otherwise - since we refuse to perform I/O on the main thread to find out for sure - we return the answer that says + // "You might be asked to load this later, so flag that" + LOG(IconDatabase, "Don't know if we should load %s or not - adding %p to the set of document loaders waiting on a decision", iconURL.ascii().data(), notificationDocumentLoader); + m_loadersPendingDecision.add(notificationDocumentLoader); + + return IconLoadUnknown; +} + +bool IconDatabase::iconDataKnownForIconURL(const String& iconURL) +{ + ASSERT_NOT_SYNC_THREAD(); + + MutexLocker locker(m_urlAndIconLock); + if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL)) + return icon->imageDataStatus() != ImageDataStatusUnknown; + + return false; +} + +void IconDatabase::setEnabled(bool enabled) +{ + ASSERT_NOT_SYNC_THREAD(); + + if (!enabled && isOpen()) + close(); + m_isEnabled = enabled; +} + +bool IconDatabase::isEnabled() const +{ + ASSERT_NOT_SYNC_THREAD(); + + return m_isEnabled; +} + +void IconDatabase::setPrivateBrowsingEnabled(bool flag) +{ + m_privateBrowsingEnabled = flag; +} + +bool IconDatabase::isPrivateBrowsingEnabled() const +{ + return m_privateBrowsingEnabled; +} + +void IconDatabase::delayDatabaseCleanup() +{ + ++databaseCleanupCounter; + if (databaseCleanupCounter == 1) + LOG(IconDatabase, "Database cleanup is now DISABLED"); +} + +void IconDatabase::allowDatabaseCleanup() +{ + if (--databaseCleanupCounter < 0) + databaseCleanupCounter = 0; + if (databaseCleanupCounter == 0) + LOG(IconDatabase, "Database cleanup is now ENABLED"); +} + +void IconDatabase::checkIntegrityBeforeOpening() +{ + checkIntegrityOnOpen = true; +} + +size_t IconDatabase::pageURLMappingCount() +{ + MutexLocker locker(m_urlAndIconLock); + return m_pageURLToRecordMap.size(); +} + +size_t IconDatabase::retainedPageURLCount() +{ + MutexLocker locker(m_urlAndIconLock); + return m_retainedPageURLs.size(); +} + +size_t IconDatabase::iconRecordCount() +{ + MutexLocker locker(m_urlAndIconLock); + return m_iconURLToRecordMap.size(); +} + +size_t IconDatabase::iconRecordCountWithData() +{ + MutexLocker locker(m_urlAndIconLock); + size_t result = 0; + + HashMap<String, IconRecord*>::iterator i = m_iconURLToRecordMap.begin(); + HashMap<String, IconRecord*>::iterator end = m_iconURLToRecordMap.end(); + + for (; i != end; ++i) + result += ((*i).second->imageDataStatus() == ImageDataStatusPresent); + + return result; +} + +IconDatabase::IconDatabase() + : m_syncThreadRunning(false) + , m_defaultIconRecord(0) + , m_isEnabled(false) + , m_privateBrowsingEnabled(false) + , m_threadTerminationRequested(false) + , m_removeIconsRequested(false) + , m_iconURLImportComplete(false) + , m_initialPruningComplete(false) + , m_client(defaultClient()) + , m_imported(false) + , m_isImportedSet(false) +{ +#if PLATFORM(DARWIN) + ASSERT(pthread_main_np()); +#endif +} + +IconDatabase::~IconDatabase() +{ + ASSERT_NOT_REACHED(); +} + +void IconDatabase::notifyPendingLoadDecisionsOnMainThread(void* context) +{ + static_cast<IconDatabase*>(context)->notifyPendingLoadDecisions(); +} + +void IconDatabase::notifyPendingLoadDecisions() +{ + ASSERT_NOT_SYNC_THREAD(); + + // This method should only be called upon completion of the initial url import from the database + ASSERT(m_iconURLImportComplete); + LOG(IconDatabase, "Notifying all DocumentLoaders that were waiting on a load decision for thier icons"); + + HashSet<RefPtr<DocumentLoader> >::iterator i = m_loadersPendingDecision.begin(); + HashSet<RefPtr<DocumentLoader> >::iterator end = m_loadersPendingDecision.end(); + + for (; i != end; ++i) + if ((*i)->refCount() > 1) + (*i)->iconLoadDecisionAvailable(); + + m_loadersPendingDecision.clear(); +} + +void IconDatabase::wakeSyncThread() +{ + MutexLocker locker(m_syncLock); + m_syncCondition.signal(); +} + +void IconDatabase::scheduleOrDeferSyncTimer() +{ + ASSERT_NOT_SYNC_THREAD(); + if (!m_syncTimer) + m_syncTimer.set(new Timer<IconDatabase>(this, &IconDatabase::syncTimerFired)); + + m_syncTimer->startOneShot(updateTimerDelay); +} + +void IconDatabase::syncTimerFired(Timer<IconDatabase>*) +{ + ASSERT_NOT_SYNC_THREAD(); + wakeSyncThread(); +} + +// ****************** +// *** Any Thread *** +// ****************** + +bool IconDatabase::isOpen() const +{ + MutexLocker locker(m_syncLock); + return m_syncDB.isOpen(); +} + +String IconDatabase::databasePath() const +{ + MutexLocker locker(m_syncLock); + return m_completeDatabasePath.copy(); +} + +String IconDatabase::defaultDatabaseFilename() +{ + static String defaultDatabaseFilename = "WebpageIcons.db"; + return defaultDatabaseFilename.copy(); +} + +// Unlike getOrCreatePageURLRecord(), getOrCreateIconRecord() does not mark the icon as "interested in import" +PassRefPtr<IconRecord> IconDatabase::getOrCreateIconRecord(const String& iconURL) +{ + // Clients of getOrCreateIconRecord() are required to acquire the m_urlAndIconLock before calling this method + ASSERT(!m_urlAndIconLock.tryLock()); + + if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL)) + return icon; + + RefPtr<IconRecord> newIcon = IconRecord::create(iconURL); + m_iconURLToRecordMap.set(iconURL, newIcon.get()); + + return newIcon.release(); +} + +// This method retrieves the existing PageURLRecord, or creates a new one and marks it as "interested in the import" for later notification +PageURLRecord* IconDatabase::getOrCreatePageURLRecord(const String& pageURL) +{ + // Clients of getOrCreatePageURLRecord() are required to acquire the m_urlAndIconLock before calling this method + ASSERT(!m_urlAndIconLock.tryLock()); + + if (pageURL.isEmpty()) + return 0; + + PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL); + + MutexLocker locker(m_pendingReadingLock); + if (!m_iconURLImportComplete) { + // If the initial import of all URLs hasn't completed and we have no page record, we assume we *might* know about this later and create a record for it + if (!pageRecord) { + LOG(IconDatabase, "Creating new PageURLRecord for pageURL %s", urlForLogging(pageURL).ascii().data()); + pageRecord = new PageURLRecord(pageURL); + m_pageURLToRecordMap.set(pageURL, pageRecord); + } + + // If the pageRecord for this page does not have an iconRecord attached to it, then it is a new pageRecord still awaiting the initial import + // Mark the URL as "interested in the result of the import" then bail + if (!pageRecord->iconRecord()) { + m_pageURLsPendingImport.add(pageURL); + return 0; + } + } + + // We've done the initial import of all URLs known in the database. If this record doesn't exist now, it never will + return pageRecord; +} + + +// ************************ +// *** Sync Thread Only *** +// ************************ + +void IconDatabase::importIconURLForPageURL(const String& iconURL, const String& pageURL) +{ + ASSERT_ICON_SYNC_THREAD(); + + // This function is only for setting actual existing url mappings so assert that neither of these URLs are empty + ASSERT(!iconURL.isEmpty() && !pageURL.isEmpty()); + + setIconURLForPageURLInSQLDatabase(iconURL, pageURL); +} + +void IconDatabase::importIconDataForIconURL(PassRefPtr<SharedBuffer> data, const String& iconURL) +{ + ASSERT_ICON_SYNC_THREAD(); + + ASSERT(!iconURL.isEmpty()); + + writeIconSnapshotToSQLDatabase(IconSnapshot(iconURL, (int)currentTime(), data.get())); +} + +bool IconDatabase::shouldStopThreadActivity() const +{ + ASSERT_ICON_SYNC_THREAD(); + + return m_threadTerminationRequested || m_removeIconsRequested; +} + +void* IconDatabase::iconDatabaseSyncThreadStart(void* vIconDatabase) +{ + IconDatabase* iconDB = static_cast<IconDatabase*>(vIconDatabase); + + return iconDB->iconDatabaseSyncThread(); +} + +void* IconDatabase::iconDatabaseSyncThread() +{ + // The call to create this thread might not complete before the thread actually starts, so we might fail this ASSERT_ICON_SYNC_THREAD() because the pointer + // to our thread structure hasn't been filled in yet. + // To fix this, the main thread acquires this lock before creating us, then releases the lock after creation is complete. A quick lock/unlock cycle here will + // prevent us from running before that call completes + m_syncLock.lock(); + m_syncLock.unlock(); + + ASSERT_ICON_SYNC_THREAD(); + + LOG(IconDatabase, "(THREAD) IconDatabase sync thread started"); + +#ifndef NDEBUG + double startTime = currentTime(); +#endif + + // Need to create the database path if it doesn't already exist + makeAllDirectories(m_databaseDirectory); + + // Existence of a journal file is evidence of a previous crash/force quit and automatically qualifies + // us to do an integrity check + String journalFilename = m_completeDatabasePath + "-journal"; + if (!checkIntegrityOnOpen) { + AutodrainedPool pool; + checkIntegrityOnOpen = fileExists(journalFilename); + } + + { + MutexLocker locker(m_syncLock); + if (!m_syncDB.open(m_completeDatabasePath)) { + LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg()); + return 0; + } + } + + if (shouldStopThreadActivity()) + return syncThreadMainLoop(); + +#ifndef NDEBUG + double timeStamp = currentTime(); + LOG(IconDatabase, "(THREAD) Open took %.4f seconds", timeStamp - startTime); +#endif + + performOpenInitialization(); + if (shouldStopThreadActivity()) + return syncThreadMainLoop(); + +#ifndef NDEBUG + double newStamp = currentTime(); + LOG(IconDatabase, "(THREAD) performOpenInitialization() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime); + timeStamp = newStamp; +#endif + + if (!imported()) { + LOG(IconDatabase, "(THREAD) Performing Safari2 import procedure"); + SQLiteTransaction importTransaction(m_syncDB); + importTransaction.begin(); + + // Commit the transaction only if the import completes (the import should be atomic) + if (m_client->performImport()) { + setImported(true); + importTransaction.commit(); + } else { + LOG(IconDatabase, "(THREAD) Safari 2 import was cancelled"); + importTransaction.rollback(); + } + + if (shouldStopThreadActivity()) + return syncThreadMainLoop(); + +#ifndef NDEBUG + newStamp = currentTime(); + LOG(IconDatabase, "(THREAD) performImport() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime); + timeStamp = newStamp; +#endif + } + + // Uncomment the following line to simulate a long lasting URL import (*HUGE* icon databases, or network home directories) + // while (currentTime() - timeStamp < 10); + + // Read in URL mappings from the database + LOG(IconDatabase, "(THREAD) Starting iconURL import"); + performURLImport(); + + if (shouldStopThreadActivity()) + return syncThreadMainLoop(); + +#ifndef NDEBUG + newStamp = currentTime(); + LOG(IconDatabase, "(THREAD) performURLImport() took %.4f seconds. Entering main loop %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime); +#endif + + LOG(IconDatabase, "(THREAD) Beginning sync"); + return syncThreadMainLoop(); +} + +static int databaseVersionNumber(SQLiteDatabase& db) +{ + return SQLiteStatement(db, "SELECT value FROM IconDatabaseInfo WHERE key = 'Version';").getColumnInt(0); +} + +static bool isValidDatabase(SQLiteDatabase& db) +{ + + // These four tables should always exist in a valid db + if (!db.tableExists("IconInfo") || !db.tableExists("IconData") || !db.tableExists("PageURL") || !db.tableExists("IconDatabaseInfo")) + return false; + + if (databaseVersionNumber(db) < currentDatabaseVersion) { + LOG(IconDatabase, "DB version is not found or below expected valid version"); + return false; + } + + return true; +} + +static void createDatabaseTables(SQLiteDatabase& db) +{ + if (!db.executeCommand("CREATE TABLE PageURL (url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,iconID INTEGER NOT NULL ON CONFLICT FAIL);")) { + LOG_ERROR("Could not create PageURL table in database (%i) - %s", db.lastError(), db.lastErrorMsg()); + db.close(); + return; + } + if (!db.executeCommand("CREATE INDEX PageURLIndex ON PageURL (url);")) { + LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg()); + db.close(); + return; + } + if (!db.executeCommand("CREATE TABLE IconInfo (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT FAIL, stamp INTEGER);")) { + LOG_ERROR("Could not create IconInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg()); + db.close(); + return; + } + if (!db.executeCommand("CREATE INDEX IconInfoIndex ON IconInfo (url, iconID);")) { + LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg()); + db.close(); + return; + } + if (!db.executeCommand("CREATE TABLE IconData (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, data BLOB);")) { + LOG_ERROR("Could not create IconData table in database (%i) - %s", db.lastError(), db.lastErrorMsg()); + db.close(); + return; + } + if (!db.executeCommand("CREATE INDEX IconDataIndex ON IconData (iconID);")) { + LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg()); + db.close(); + return; + } + if (!db.executeCommand("CREATE TABLE IconDatabaseInfo (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) { + LOG_ERROR("Could not create IconDatabaseInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg()); + db.close(); + return; + } + if (!db.executeCommand(String("INSERT INTO IconDatabaseInfo VALUES ('Version', ") + String::number(currentDatabaseVersion) + ");")) { + LOG_ERROR("Could not insert icon database version into IconDatabaseInfo table (%i) - %s", db.lastError(), db.lastErrorMsg()); + db.close(); + return; + } +} + +void IconDatabase::performOpenInitialization() +{ + ASSERT_ICON_SYNC_THREAD(); + + if (!isOpen()) + return; + + if (checkIntegrityOnOpen) { + checkIntegrityOnOpen = false; + if (!checkIntegrity()) { + LOG(IconDatabase, "Integrity check was bad - dumping IconDatabase"); + + m_syncDB.close(); + + { + MutexLocker locker(m_syncLock); + // Should've been consumed by SQLite, delete just to make sure we don't see it again in the future; + deleteFile(m_completeDatabasePath + "-journal"); + deleteFile(m_completeDatabasePath); + } + + // Reopen the write database, creating it from scratch + if (!m_syncDB.open(m_completeDatabasePath)) { + LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg()); + return; + } + } + } + + int version = databaseVersionNumber(m_syncDB); + + if (version > currentDatabaseVersion) { + LOG(IconDatabase, "Database version number %i is greater than our current version number %i - closing the database to prevent overwriting newer versions", version, currentDatabaseVersion); + m_syncDB.close(); + m_threadTerminationRequested = true; + return; + } + + if (!isValidDatabase(m_syncDB)) { + LOG(IconDatabase, "%s is missing or in an invalid state - reconstructing", m_completeDatabasePath.ascii().data()); + m_syncDB.clearAllTables(); + createDatabaseTables(m_syncDB); + } + + // Reduce sqlite RAM cache size from default 2000 pages (~1.5kB per page). 3MB of cache for icon database is overkill + if (!SQLiteStatement(m_syncDB, "PRAGMA cache_size = 200;").executeCommand()) + LOG_ERROR("SQLite database could not set cache_size"); +} + +bool IconDatabase::checkIntegrity() +{ + ASSERT_ICON_SYNC_THREAD(); + + SQLiteStatement integrity(m_syncDB, "PRAGMA integrity_check;"); + if (integrity.prepare() != SQLResultOk) { + LOG_ERROR("checkIntegrity failed to execute"); + return false; + } + + int resultCode = integrity.step(); + if (resultCode == SQLResultOk) + return true; + + if (resultCode != SQLResultRow) + return false; + + int columns = integrity.columnCount(); + if (columns != 1) { + LOG_ERROR("Received %i columns performing integrity check, should be 1", columns); + return false; + } + + String resultText = integrity.getColumnText(0); + + // A successful, no-error integrity check will be "ok" - all other strings imply failure + if (resultText == "ok") + return true; + + LOG_ERROR("Icon database integrity check failed - \n%s", resultText.ascii().data()); + return false; +} + +void IconDatabase::performURLImport() +{ + ASSERT_ICON_SYNC_THREAD(); + + SQLiteStatement query(m_syncDB, "SELECT PageURL.url, IconInfo.url, IconInfo.stamp FROM PageURL INNER JOIN IconInfo ON PageURL.iconID=IconInfo.iconID;"); + + if (query.prepare() != SQLResultOk) { + LOG_ERROR("Unable to prepare icon url import query"); + return; + } + + // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go + // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up + AutodrainedPool pool(25); + + int result = query.step(); + while (result == SQLResultRow) { + String pageURL = query.getColumnText(0); + String iconURL = query.getColumnText(1); + + { + MutexLocker locker(m_urlAndIconLock); + + PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL); + + // If the pageRecord doesn't exist in this map, then no one has retained this pageURL + // If the s_databaseCleanupCounter count is non-zero, then we're not supposed to be pruning the database in any manner, + // so go ahead and actually create a pageURLRecord for this url even though it's not retained. + // If database cleanup *is* allowed, we don't want to bother pulling in a page url from disk that noone is actually interested + // in - we'll prune it later instead! + if (!pageRecord && databaseCleanupCounter && !pageURL.isEmpty()) { + pageRecord = new PageURLRecord(pageURL); + m_pageURLToRecordMap.set(pageURL, pageRecord); + } + + if (pageRecord) { + IconRecord* currentIcon = pageRecord->iconRecord(); + + if (!currentIcon || currentIcon->iconURL() != iconURL) { + pageRecord->setIconRecord(getOrCreateIconRecord(iconURL)); + currentIcon = pageRecord->iconRecord(); + } + + // Regardless, the time stamp from disk still takes precedence. Until we read this icon from disk, we didn't think we'd seen it before + // so we marked the timestamp as "now", but it's really much older + currentIcon->setTimestamp(query.getColumnInt(2)); + } + } + + // FIXME: Currently the WebKit API supports 1 type of notification that is sent whenever we get an Icon URL for a Page URL. We might want to re-purpose it to work for + // getting the actually icon itself also (so each pageurl would get this notification twice) or we might want to add a second type of notification - + // one for the URL and one for the Image itself + // Note that WebIconDatabase is not neccessarily API so we might be able to make this change + { + MutexLocker locker(m_pendingReadingLock); + if (m_pageURLsPendingImport.contains(pageURL)) { + m_client->dispatchDidAddIconForPageURL(pageURL); + m_pageURLsPendingImport.remove(pageURL); + + pool.cycle(); + } + } + + // Stop the import at any time of the thread has been asked to shutdown + if (shouldStopThreadActivity()) { + LOG(IconDatabase, "IconDatabase asked to terminate during performURLImport()"); + return; + } + + result = query.step(); + } + + if (result != SQLResultDone) + LOG(IconDatabase, "Error reading page->icon url mappings from database"); + + // Clear the m_pageURLsPendingImport set - either the page URLs ended up with an iconURL (that we'll notify about) or not, + // but after m_iconURLImportComplete is set to true, we don't care about this set anymore + Vector<String> urls; + { + MutexLocker locker(m_pendingReadingLock); + + urls.appendRange(m_pageURLsPendingImport.begin(), m_pageURLsPendingImport.end()); + m_pageURLsPendingImport.clear(); + m_iconURLImportComplete = true; + } + + Vector<String> urlsToNotify; + + // Loop through the urls pending import + // Remove unretained ones if database cleanup is allowed + // Keep a set of ones that are retained and pending notification + + { + MutexLocker locker(m_urlAndIconLock); + + for (unsigned i = 0; i < urls.size(); ++i) { + if (!m_retainedPageURLs.contains(urls[i])) { + PageURLRecord* record = m_pageURLToRecordMap.get(urls[i]); + if (record && !databaseCleanupCounter) { + m_pageURLToRecordMap.remove(urls[i]); + IconRecord* iconRecord = record->iconRecord(); + + // If this page is the only remaining retainer of its icon, mark that icon for deletion and don't bother + // reading anything related to it + if (iconRecord && iconRecord->hasOneRef()) { + m_iconURLToRecordMap.remove(iconRecord->iconURL()); + + { + MutexLocker locker(m_pendingReadingLock); + m_pageURLsInterestedInIcons.remove(urls[i]); + m_iconsPendingReading.remove(iconRecord); + } + { + MutexLocker locker(m_pendingSyncLock); + m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true)); + } + } + + delete record; + } + } else { + urlsToNotify.append(urls[i]); + } + } + } + + LOG(IconDatabase, "Notifying %zu interested page URLs that their icon URL is known due to the import", urlsToNotify.size()); + // Now that we don't hold any locks, perform the actual notifications + for (unsigned i = 0; i < urlsToNotify.size(); ++i) { + LOG(IconDatabase, "Notifying icon info known for pageURL %s", urlsToNotify[i].ascii().data()); + m_client->dispatchDidAddIconForPageURL(urlsToNotify[i]); + if (shouldStopThreadActivity()) + return; + + pool.cycle(); + } + + // Notify all DocumentLoaders that were waiting for an icon load decision on the main thread + callOnMainThread(notifyPendingLoadDecisionsOnMainThread, this); +} + +void* IconDatabase::syncThreadMainLoop() +{ + ASSERT_ICON_SYNC_THREAD(); + static bool prunedUnretainedIcons = false; + + m_syncLock.lock(); + + // It's possible thread termination is requested before the main loop even starts - in that case, just skip straight to cleanup + while (!m_threadTerminationRequested) { + m_syncLock.unlock(); + +#ifndef NDEBUG + double timeStamp = currentTime(); +#endif + LOG(IconDatabase, "(THREAD) Main work loop starting"); + + // If we should remove all icons, do it now. This is an uninteruptible procedure that we will always do before quitting if it is requested + if (m_removeIconsRequested) { + removeAllIconsOnThread(); + m_removeIconsRequested = false; + } + + // Then, if the thread should be quitting, quit now! + if (m_threadTerminationRequested) + break; + + bool didAnyWork = true; + while (didAnyWork) { + bool didWrite = writeToDatabase(); + if (shouldStopThreadActivity()) + break; + + didAnyWork = readFromDatabase(); + if (shouldStopThreadActivity()) + break; + + // Prune unretained icons after the first time we sync anything out to the database + // This way, pruning won't be the only operation we perform to the database by itself + // We also don't want to bother doing this if the thread should be terminating (the user is quitting) + // or if private browsing is enabled + // We also don't want to prune if the m_databaseCleanupCounter count is non-zero - that means someone + // has asked to delay pruning + if (didWrite && !m_privateBrowsingEnabled && !prunedUnretainedIcons && !databaseCleanupCounter) { +#ifndef NDEBUG + double time = currentTime(); +#endif + LOG(IconDatabase, "(THREAD) Starting pruneUnretainedIcons()"); + + pruneUnretainedIcons(); + + LOG(IconDatabase, "(THREAD) pruneUnretainedIcons() took %.4f seconds", currentTime() - time); + + // If pruneUnretainedIcons() returned early due to requested thread termination, its still okay + // to mark prunedUnretainedIcons true because we're about to terminate anyway + prunedUnretainedIcons = true; + } + + didAnyWork = didAnyWork || didWrite; + if (shouldStopThreadActivity()) + break; + } + +#ifndef NDEBUG + double newstamp = currentTime(); + LOG(IconDatabase, "(THREAD) Main work loop ran for %.4f seconds, %s requested to terminate", newstamp - timeStamp, shouldStopThreadActivity() ? "was" : "was not"); +#endif + + m_syncLock.lock(); + + // There is some condition that is asking us to stop what we're doing now and handle a special case + // This is either removing all icons, or shutting down the thread to quit the app + // We handle those at the top of this main loop so continue to jump back up there + if (shouldStopThreadActivity()) + continue; + + m_syncCondition.wait(m_syncLock); + } + m_syncLock.unlock(); + + // Thread is terminating at this point + return cleanupSyncThread(); +} + +bool IconDatabase::readFromDatabase() +{ + ASSERT_ICON_SYNC_THREAD(); + +#ifndef NDEBUG + double timeStamp = currentTime(); +#endif + + bool didAnyWork = false; + + // We'll make a copy of the sets of things that need to be read. Then we'll verify at the time of updating the record that it still wants to be updated + // This way we won't hold the lock for a long period of time + Vector<IconRecord*> icons; + { + MutexLocker locker(m_pendingReadingLock); + icons.appendRange(m_iconsPendingReading.begin(), m_iconsPendingReading.end()); + } + + // Keep track of icons we actually read to notify them of the new icon + HashSet<String> urlsToNotify; + + for (unsigned i = 0; i < icons.size(); ++i) { + didAnyWork = true; + RefPtr<SharedBuffer> imageData = getImageDataForIconURLFromSQLDatabase(icons[i]->iconURL()); + + // Verify this icon still wants to be read from disk + { + MutexLocker urlLocker(m_urlAndIconLock); + { + MutexLocker readLocker(m_pendingReadingLock); + + if (m_iconsPendingReading.contains(icons[i])) { + // Set the new data + icons[i]->setImageData(imageData.get()); + + // Remove this icon from the set that needs to be read + m_iconsPendingReading.remove(icons[i]); + + // We have a set of all Page URLs that retain this icon as well as all PageURLs waiting for an icon + // We want to find the intersection of these two sets to notify them + // Check the sizes of these two sets to minimize the number of iterations + const HashSet<String>* outerHash; + const HashSet<String>* innerHash; + + if (icons[i]->retainingPageURLs().size() > m_pageURLsInterestedInIcons.size()) { + outerHash = &m_pageURLsInterestedInIcons; + innerHash = &(icons[i]->retainingPageURLs()); + } else { + innerHash = &m_pageURLsInterestedInIcons; + outerHash = &(icons[i]->retainingPageURLs()); + } + + HashSet<String>::const_iterator iter = outerHash->begin(); + HashSet<String>::const_iterator end = outerHash->end(); + for (; iter != end; ++iter) { + if (innerHash->contains(*iter)) { + LOG(IconDatabase, "%s is interesting in the icon we just read. Adding it to the list and removing it from the interested set", urlForLogging(*iter).ascii().data()); + urlsToNotify.add(*iter); + } + + // If we ever get to the point were we've seen every url interested in this icon, break early + if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size()) + break; + } + + // We don't need to notify a PageURL twice, so all the ones we're about to notify can be removed from the interested set + if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size()) + m_pageURLsInterestedInIcons.clear(); + else { + iter = urlsToNotify.begin(); + end = urlsToNotify.end(); + for (; iter != end; ++iter) + m_pageURLsInterestedInIcons.remove(*iter); + } + } + } + } + + if (shouldStopThreadActivity()) + return didAnyWork; + + // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go + // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up + AutodrainedPool pool(25); + + // Now that we don't hold any locks, perform the actual notifications + HashSet<String>::iterator iter = urlsToNotify.begin(); + HashSet<String>::iterator end = urlsToNotify.end(); + for (unsigned iteration = 0; iter != end; ++iter, ++iteration) { + LOG(IconDatabase, "Notifying icon received for pageURL %s", urlForLogging(*iter).ascii().data()); + m_client->dispatchDidAddIconForPageURL(*iter); + if (shouldStopThreadActivity()) + return didAnyWork; + + pool.cycle(); + } + + LOG(IconDatabase, "Done notifying %i pageURLs who just received their icons", urlsToNotify.size()); + urlsToNotify.clear(); + + if (shouldStopThreadActivity()) + return didAnyWork; + } + + LOG(IconDatabase, "Reading from database took %.4f seconds", currentTime() - timeStamp); + + return didAnyWork; +} + +bool IconDatabase::writeToDatabase() +{ + ASSERT_ICON_SYNC_THREAD(); + +#ifndef NDEBUG + double timeStamp = currentTime(); +#endif + + bool didAnyWork = false; + + // We can copy the current work queue then clear it out - If any new work comes in while we're writing out, + // we'll pick it up on the next pass. This greatly simplifies the locking strategy for this method and remains cohesive with changes + // asked for by the database on the main thread + Vector<IconSnapshot> iconSnapshots; + Vector<PageURLSnapshot> pageSnapshots; + { + MutexLocker locker(m_pendingSyncLock); + + iconSnapshots.appendRange(m_iconsPendingSync.begin().values(), m_iconsPendingSync.end().values()); + m_iconsPendingSync.clear(); + + pageSnapshots.appendRange(m_pageURLsPendingSync.begin().values(), m_pageURLsPendingSync.end().values()); + m_pageURLsPendingSync.clear(); + } + + if (iconSnapshots.size() || pageSnapshots.size()) + didAnyWork = true; + + SQLiteTransaction syncTransaction(m_syncDB); + syncTransaction.begin(); + + for (unsigned i = 0; i < iconSnapshots.size(); ++i) { + writeIconSnapshotToSQLDatabase(iconSnapshots[i]); + LOG(IconDatabase, "Wrote IconRecord for IconURL %s with timeStamp of %i to the DB", urlForLogging(iconSnapshots[i].iconURL).ascii().data(), iconSnapshots[i].timestamp); + } + + for (unsigned i = 0; i < pageSnapshots.size(); ++i) { + // If the icon URL is empty, this page is meant to be deleted + // ASSERTs are sanity checks to make sure the mappings exist if they should and don't if they shouldn't + if (pageSnapshots[i].iconURL.isEmpty()) + removePageURLFromSQLDatabase(pageSnapshots[i].pageURL); + else + setIconURLForPageURLInSQLDatabase(pageSnapshots[i].iconURL, pageSnapshots[i].pageURL); + LOG(IconDatabase, "Committed IconURL for PageURL %s to database", urlForLogging(pageSnapshots[i].pageURL).ascii().data()); + } + + syncTransaction.commit(); + + // Check to make sure there are no dangling PageURLs - If there are, we want to output one log message but not spam the console potentially every few seconds + if (didAnyWork) + checkForDanglingPageURLs(false); + + LOG(IconDatabase, "Updating the database took %.4f seconds", currentTime() - timeStamp); + + return didAnyWork; +} + +void IconDatabase::pruneUnretainedIcons() +{ + ASSERT_ICON_SYNC_THREAD(); + + if (!isOpen()) + return; + + // This method should only be called once per run + ASSERT(!m_initialPruningComplete); + + // This method relies on having read in all page URLs from the database earlier. + ASSERT(m_iconURLImportComplete); + + // Get the known PageURLs from the db, and record the ID of any that are not in the retain count set. + Vector<int64_t> pageIDsToDelete; + + SQLiteStatement pageSQL(m_syncDB, "SELECT rowid, url FROM PageURL;"); + pageSQL.prepare(); + + int result; + while ((result = pageSQL.step()) == SQLResultRow) { + MutexLocker locker(m_urlAndIconLock); + if (!m_pageURLToRecordMap.contains(pageSQL.getColumnText(1))) + pageIDsToDelete.append(pageSQL.getColumnInt64(0)); + } + + if (result != SQLResultDone) + LOG_ERROR("Error reading PageURL table from on-disk DB"); + pageSQL.finalize(); + + // Delete page URLs that were in the table, but not in our retain count set. + size_t numToDelete = pageIDsToDelete.size(); + if (numToDelete) { + SQLiteTransaction pruningTransaction(m_syncDB); + pruningTransaction.begin(); + + SQLiteStatement pageDeleteSQL(m_syncDB, "DELETE FROM PageURL WHERE rowid = (?);"); + pageDeleteSQL.prepare(); + for (size_t i = 0; i < numToDelete; ++i) { + LOG(IconDatabase, "Pruning page with rowid %lli from disk", pageIDsToDelete[i]); + pageDeleteSQL.bindInt64(1, pageIDsToDelete[i]); + int result = pageDeleteSQL.step(); + if (result != SQLResultDone) + LOG_ERROR("Unabled to delete page with id %lli from disk", pageIDsToDelete[i]); + pageDeleteSQL.reset(); + + // If the thread was asked to terminate, we should commit what pruning we've done so far, figuring we can + // finish the rest later (hopefully) + if (shouldStopThreadActivity()) { + pruningTransaction.commit(); + return; + } + } + pruningTransaction.commit(); + pageDeleteSQL.finalize(); + } + + // Deleting unreferenced icons from the Icon tables has to be atomic - + // If the user quits while these are taking place, they might have to wait. Thankfully this will rarely be an issue + // A user on a network home directory with a wildly inconsistent database might see quite a pause... + + SQLiteTransaction pruningTransaction(m_syncDB); + pruningTransaction.begin(); + + // Wipe Icons that aren't retained + if (!m_syncDB.executeCommand("DELETE FROM IconData WHERE iconID NOT IN (SELECT iconID FROM PageURL);")) + LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconData table"); + if (!m_syncDB.executeCommand("DELETE FROM IconInfo WHERE iconID NOT IN (SELECT iconID FROM PageURL);")) + LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconInfo table"); + + pruningTransaction.commit(); + + checkForDanglingPageURLs(true); + + m_initialPruningComplete = true; +} + +void IconDatabase::checkForDanglingPageURLs(bool pruneIfFound) +{ + ASSERT_ICON_SYNC_THREAD(); + + // This check can be relatively expensive so we don't do it in a release build unless the caller has asked us to prune any dangling + // entries. We also don't want to keep performing this check and reporting this error if it has already found danglers before so we + // keep track of whether we've found any. We skip the check in the release build pretending to have already found danglers already. +#ifndef NDEBUG + static bool danglersFound = true; +#else + static bool danglersFound = false; +#endif + + if ((pruneIfFound || !danglersFound) && SQLiteStatement(m_syncDB, "SELECT url FROM PageURL WHERE PageURL.iconID NOT IN (SELECT iconID FROM IconInfo) LIMIT 1;").returnsAtLeastOneResult()) { + danglersFound = true; + LOG(IconDatabase, "Dangling PageURL entries found"); + if (pruneIfFound && !m_syncDB.executeCommand("DELETE FROM PageURL WHERE iconID NOT IN (SELECT iconID FROM IconInfo);")) + LOG(IconDatabase, "Unable to prune dangling PageURLs"); + } +} + +void IconDatabase::removeAllIconsOnThread() +{ + ASSERT_ICON_SYNC_THREAD(); + + LOG(IconDatabase, "Removing all icons on the sync thread"); + + // Delete all the prepared statements so they can start over + deleteAllPreparedStatements(); + + // To reset the on-disk database, we'll wipe all its tables then vacuum it + // This is easier and safer than closing it, deleting the file, and recreating from scratch + m_syncDB.clearAllTables(); + m_syncDB.runVacuumCommand(); + createDatabaseTables(m_syncDB); + + LOG(IconDatabase, "Dispatching notification that we removed all icons"); + m_client->dispatchDidRemoveAllIcons(); +} + +void IconDatabase::deleteAllPreparedStatements() +{ + ASSERT_ICON_SYNC_THREAD(); + + m_setIconIDForPageURLStatement.set(0); + m_removePageURLStatement.set(0); + m_getIconIDForIconURLStatement.set(0); + m_getImageDataForIconURLStatement.set(0); + m_addIconToIconInfoStatement.set(0); + m_addIconToIconDataStatement.set(0); + m_getImageDataStatement.set(0); + m_deletePageURLsForIconURLStatement.set(0); + m_deleteIconFromIconInfoStatement.set(0); + m_deleteIconFromIconDataStatement.set(0); + m_updateIconInfoStatement.set(0); + m_updateIconDataStatement.set(0); + m_setIconInfoStatement.set(0); + m_setIconDataStatement.set(0); +} + +void* IconDatabase::cleanupSyncThread() +{ + ASSERT_ICON_SYNC_THREAD(); + +#ifndef NDEBUG + double timeStamp = currentTime(); +#endif + + // If the removeIcons flag is set, remove all icons from the db. + if (m_removeIconsRequested) + removeAllIconsOnThread(); + + // Sync remaining icons out + LOG(IconDatabase, "(THREAD) Doing final writeout and closure of sync thread"); + writeToDatabase(); + + // Close the database + MutexLocker locker(m_syncLock); + + m_databaseDirectory = String(); + m_completeDatabasePath = String(); + deleteAllPreparedStatements(); + m_syncDB.close(); + +#ifndef NDEBUG + LOG(IconDatabase, "(THREAD) Final closure took %.4f seconds", currentTime() - timeStamp); +#endif + + m_syncThreadRunning = false; + return 0; +} + +bool IconDatabase::imported() +{ + ASSERT_ICON_SYNC_THREAD(); + + if (m_isImportedSet) + return m_imported; + + SQLiteStatement query(m_syncDB, "SELECT IconDatabaseInfo.value FROM IconDatabaseInfo WHERE IconDatabaseInfo.key = \"ImportedSafari2Icons\";"); + if (query.prepare() != SQLResultOk) { + LOG_ERROR("Unable to prepare imported statement"); + return false; + } + + int result = query.step(); + if (result == SQLResultRow) + result = query.getColumnInt(0); + else { + if (result != SQLResultDone) + LOG_ERROR("imported statement failed"); + result = 0; + } + + m_isImportedSet = true; + return m_imported = result; +} + +void IconDatabase::setImported(bool import) +{ + ASSERT_ICON_SYNC_THREAD(); + + m_imported = import; + m_isImportedSet = true; + + String queryString = import ? + "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 1);" : + "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 0);"; + + SQLiteStatement query(m_syncDB, queryString); + + if (query.prepare() != SQLResultOk) { + LOG_ERROR("Unable to prepare set imported statement"); + return; + } + + if (query.step() != SQLResultDone) + LOG_ERROR("set imported statement failed"); +} + +// readySQLiteStatement() handles two things +// 1 - If the SQLDatabase& argument is different, the statement must be destroyed and remade. This happens when the user +// switches to and from private browsing +// 2 - Lazy construction of the Statement in the first place, in case we've never made this query before +inline void readySQLiteStatement(OwnPtr<SQLiteStatement>& statement, SQLiteDatabase& db, const String& str) +{ + if (statement && (statement->database() != &db || statement->isExpired())) { + if (statement->isExpired()) + LOG(IconDatabase, "SQLiteStatement associated with %s is expired", str.ascii().data()); + statement.set(0); + } + if (!statement) { + statement.set(new SQLiteStatement(db, str)); + if (statement->prepare() != SQLResultOk) + LOG_ERROR("Preparing statement %s failed", str.ascii().data()); + } +} + +void IconDatabase::setIconURLForPageURLInSQLDatabase(const String& iconURL, const String& pageURL) +{ + ASSERT_ICON_SYNC_THREAD(); + + int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL); + + if (!iconID) + iconID = addIconURLToSQLDatabase(iconURL); + + if (!iconID) { + LOG_ERROR("Failed to establish an ID for iconURL %s", urlForLogging(iconURL).ascii().data()); + ASSERT(false); + return; + } + + setIconIDForPageURLInSQLDatabase(iconID, pageURL); +} + +void IconDatabase::setIconIDForPageURLInSQLDatabase(int64_t iconID, const String& pageURL) +{ + ASSERT_ICON_SYNC_THREAD(); + + readySQLiteStatement(m_setIconIDForPageURLStatement, m_syncDB, "INSERT INTO PageURL (url, iconID) VALUES ((?), ?);"); + m_setIconIDForPageURLStatement->bindText(1, pageURL); + m_setIconIDForPageURLStatement->bindInt64(2, iconID); + + int result = m_setIconIDForPageURLStatement->step(); + if (result != SQLResultDone) { + ASSERT(false); + LOG_ERROR("setIconIDForPageURLQuery failed for url %s", urlForLogging(pageURL).ascii().data()); + } + + m_setIconIDForPageURLStatement->reset(); +} + +void IconDatabase::removePageURLFromSQLDatabase(const String& pageURL) +{ + ASSERT_ICON_SYNC_THREAD(); + + readySQLiteStatement(m_removePageURLStatement, m_syncDB, "DELETE FROM PageURL WHERE url = (?);"); + m_removePageURLStatement->bindText(1, pageURL); + + if (m_removePageURLStatement->step() != SQLResultDone) + LOG_ERROR("removePageURLFromSQLDatabase failed for url %s", urlForLogging(pageURL).ascii().data()); + + m_removePageURLStatement->reset(); +} + + +int64_t IconDatabase::getIconIDForIconURLFromSQLDatabase(const String& iconURL) +{ + ASSERT_ICON_SYNC_THREAD(); + + readySQLiteStatement(m_getIconIDForIconURLStatement, m_syncDB, "SELECT IconInfo.iconID FROM IconInfo WHERE IconInfo.url = (?);"); + m_getIconIDForIconURLStatement->bindText(1, iconURL); + + int64_t result = m_getIconIDForIconURLStatement->step(); + if (result == SQLResultRow) + result = m_getIconIDForIconURLStatement->getColumnInt64(0); + else { + if (result != SQLResultDone) + LOG_ERROR("getIconIDForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data()); + result = 0; + } + + m_getIconIDForIconURLStatement->reset(); + return result; +} + +int64_t IconDatabase::addIconURLToSQLDatabase(const String& iconURL) +{ + ASSERT_ICON_SYNC_THREAD(); + + // There would be a transaction here to make sure these two inserts are atomic + // In practice the only caller of this method is always wrapped in a transaction itself so placing another + // here is unnecessary + + readySQLiteStatement(m_addIconToIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url, stamp) VALUES (?, 0);"); + m_addIconToIconInfoStatement->bindText(1, iconURL); + + int result = m_addIconToIconInfoStatement->step(); + m_addIconToIconInfoStatement->reset(); + if (result != SQLResultDone) { + LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconInfo", urlForLogging(iconURL).ascii().data()); + return 0; + } + int64_t iconID = m_syncDB.lastInsertRowID(); + + readySQLiteStatement(m_addIconToIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);"); + m_addIconToIconDataStatement->bindInt64(1, iconID); + + result = m_addIconToIconDataStatement->step(); + m_addIconToIconDataStatement->reset(); + if (result != SQLResultDone) { + LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconData", urlForLogging(iconURL).ascii().data()); + return 0; + } + + return iconID; +} + +PassRefPtr<SharedBuffer> IconDatabase::getImageDataForIconURLFromSQLDatabase(const String& iconURL) +{ + ASSERT_ICON_SYNC_THREAD(); + + RefPtr<SharedBuffer> imageData; + + readySQLiteStatement(m_getImageDataForIconURLStatement, m_syncDB, "SELECT IconData.data FROM IconData WHERE IconData.iconID IN (SELECT iconID FROM IconInfo WHERE IconInfo.url = (?));"); + m_getImageDataForIconURLStatement->bindText(1, iconURL); + + int result = m_getImageDataForIconURLStatement->step(); + if (result == SQLResultRow) { + Vector<char> data; + m_getImageDataForIconURLStatement->getColumnBlobAsVector(0, data); + imageData = SharedBuffer::create(data.data(), data.size()); + } else if (result != SQLResultDone) + LOG_ERROR("getImageDataForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data()); + + m_getImageDataForIconURLStatement->reset(); + + return imageData.release(); +} + +void IconDatabase::removeIconFromSQLDatabase(const String& iconURL) +{ + ASSERT_ICON_SYNC_THREAD(); + + if (iconURL.isEmpty()) + return; + + // There would be a transaction here to make sure these removals are atomic + // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary + + // It's possible this icon is not in the database because of certain rapid browsing patterns (such as a stress test) where the + // icon is marked to be added then marked for removal before it is ever written to disk. No big deal, early return + int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL); + if (!iconID) + return; + + readySQLiteStatement(m_deletePageURLsForIconURLStatement, m_syncDB, "DELETE FROM PageURL WHERE PageURL.iconID = (?);"); + m_deletePageURLsForIconURLStatement->bindInt64(1, iconID); + + if (m_deletePageURLsForIconURLStatement->step() != SQLResultDone) + LOG_ERROR("m_deletePageURLsForIconURLStatement failed for url %s", urlForLogging(iconURL).ascii().data()); + + readySQLiteStatement(m_deleteIconFromIconInfoStatement, m_syncDB, "DELETE FROM IconInfo WHERE IconInfo.iconID = (?);"); + m_deleteIconFromIconInfoStatement->bindInt64(1, iconID); + + if (m_deleteIconFromIconInfoStatement->step() != SQLResultDone) + LOG_ERROR("m_deleteIconFromIconInfoStatement failed for url %s", urlForLogging(iconURL).ascii().data()); + + readySQLiteStatement(m_deleteIconFromIconDataStatement, m_syncDB, "DELETE FROM IconData WHERE IconData.iconID = (?);"); + m_deleteIconFromIconDataStatement->bindInt64(1, iconID); + + if (m_deleteIconFromIconDataStatement->step() != SQLResultDone) + LOG_ERROR("m_deleteIconFromIconDataStatement failed for url %s", urlForLogging(iconURL).ascii().data()); + + m_deletePageURLsForIconURLStatement->reset(); + m_deleteIconFromIconInfoStatement->reset(); + m_deleteIconFromIconDataStatement->reset(); +} + +void IconDatabase::writeIconSnapshotToSQLDatabase(const IconSnapshot& snapshot) +{ + ASSERT_ICON_SYNC_THREAD(); + + if (snapshot.iconURL.isEmpty()) + return; + + // A nulled out timestamp and data means this icon is destined to be deleted - do that instead of writing it out + if (!snapshot.timestamp && !snapshot.data) { + LOG(IconDatabase, "Removing %s from on-disk database", urlForLogging(snapshot.iconURL).ascii().data()); + removeIconFromSQLDatabase(snapshot.iconURL); + return; + } + + // There would be a transaction here to make sure these removals are atomic + // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary + + // Get the iconID for this url + int64_t iconID = getIconIDForIconURLFromSQLDatabase(snapshot.iconURL); + + // If there is already an iconID in place, update the database. + // Otherwise, insert new records + if (iconID) { + readySQLiteStatement(m_updateIconInfoStatement, m_syncDB, "UPDATE IconInfo SET stamp = ?, url = ? WHERE iconID = ?;"); + m_updateIconInfoStatement->bindInt64(1, snapshot.timestamp); + m_updateIconInfoStatement->bindText(2, snapshot.iconURL); + m_updateIconInfoStatement->bindInt64(3, iconID); + + if (m_updateIconInfoStatement->step() != SQLResultDone) + LOG_ERROR("Failed to update icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data()); + + m_updateIconInfoStatement->reset(); + + readySQLiteStatement(m_updateIconDataStatement, m_syncDB, "UPDATE IconData SET data = ? WHERE iconID = ?;"); + m_updateIconDataStatement->bindInt64(2, iconID); + + // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data, + // signifying that this icon doesn't have any data + if (snapshot.data && snapshot.data->size()) + m_updateIconDataStatement->bindBlob(1, snapshot.data->data(), snapshot.data->size()); + else + m_updateIconDataStatement->bindNull(1); + + if (m_updateIconDataStatement->step() != SQLResultDone) + LOG_ERROR("Failed to update icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data()); + + m_updateIconDataStatement->reset(); + } else { + readySQLiteStatement(m_setIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url,stamp) VALUES (?, ?);"); + m_setIconInfoStatement->bindText(1, snapshot.iconURL); + m_setIconInfoStatement->bindInt64(2, snapshot.timestamp); + + if (m_setIconInfoStatement->step() != SQLResultDone) + LOG_ERROR("Failed to set icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data()); + + m_setIconInfoStatement->reset(); + + int64_t iconID = m_syncDB.lastInsertRowID(); + + readySQLiteStatement(m_setIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);"); + m_setIconDataStatement->bindInt64(1, iconID); + + // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data, + // signifying that this icon doesn't have any data + if (snapshot.data && snapshot.data->size()) + m_setIconDataStatement->bindBlob(2, snapshot.data->data(), snapshot.data->size()); + else + m_setIconDataStatement->bindNull(2); + + if (m_setIconDataStatement->step() != SQLResultDone) + LOG_ERROR("Failed to set icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data()); + + m_setIconDataStatement->reset(); + } +} + +} // namespace WebCore diff --git a/WebCore/loader/icon/IconDatabase.h b/WebCore/loader/icon/IconDatabase.h new file mode 100644 index 0000000..4303ae1 --- /dev/null +++ b/WebCore/loader/icon/IconDatabase.h @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2007 Justin Haygood (jhaygood@reaktix.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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 IconDatabase_h +#define IconDatabase_h + +#if ENABLE(ICONDATABASE) +#include "SQLiteDatabase.h" +#endif + +#include "StringHash.h" +#include "Timer.h" +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> +#if ENABLE(ICONDATABASE) +#include <wtf/Threading.h> +#endif + +namespace WebCore { + +class DocumentLoader; +class Image; +class IntSize; +class IconDatabaseClient; +class IconRecord; +class IconSnapshot; +class KURL; +class PageURLRecord; +class PageURLSnapshot; +class SharedBuffer; + +#if ENABLE(ICONDATABASE) +class SQLTransaction; +#endif + +enum IconLoadDecision { + IconLoadYes, + IconLoadNo, + IconLoadUnknown +}; + +class IconDatabase : Noncopyable { + +// *** Main Thread Only *** +public: + void setClient(IconDatabaseClient*); + + bool open(const String& path); + void close(); + + void removeAllIcons(); + + Image* iconForPageURL(const String&, const IntSize&); + void readIconForPageURLFromDisk(const String&); + String iconURLForPageURL(const String&); + Image* defaultIcon(const IntSize&); + + void retainIconForPageURL(const String&); + void releaseIconForPageURL(const String&); + + void setIconDataForIconURL(PassRefPtr<SharedBuffer> data, const String&); + void setIconURLForPageURL(const String& iconURL, const String& pageURL); + + IconLoadDecision loadDecisionForIconURL(const String&, DocumentLoader*); + bool iconDataKnownForIconURL(const String&); + + void setEnabled(bool enabled); + bool isEnabled() const; + + void setPrivateBrowsingEnabled(bool flag); + bool isPrivateBrowsingEnabled() const; + + static void delayDatabaseCleanup(); + static void allowDatabaseCleanup(); + static void checkIntegrityBeforeOpening(); + + // Support for WebCoreStatistics in WebKit + size_t pageURLMappingCount(); + size_t retainedPageURLCount(); + size_t iconRecordCount(); + size_t iconRecordCountWithData(); + +private: + IconDatabase(); + ~IconDatabase(); + friend IconDatabase* iconDatabase(); + +#if ENABLE(ICONDATABASE) + static void notifyPendingLoadDecisionsOnMainThread(void*); + void notifyPendingLoadDecisions(); + + void wakeSyncThread(); + void scheduleOrDeferSyncTimer(); + OwnPtr<Timer<IconDatabase> > m_syncTimer; + void syncTimerFired(Timer<IconDatabase>*); + + ThreadIdentifier m_syncThread; + bool m_syncThreadRunning; + + HashSet<RefPtr<DocumentLoader> > m_loadersPendingDecision; + + RefPtr<IconRecord> m_defaultIconRecord; +#endif // ENABLE(ICONDATABASE) + +// *** Any Thread *** +public: + bool isOpen() const; + String databasePath() const; + static String defaultDatabaseFilename(); + +#if ENABLE(ICONDATABASE) +private: + PassRefPtr<IconRecord> getOrCreateIconRecord(const String& iconURL); + PageURLRecord* getOrCreatePageURLRecord(const String& pageURL); + + bool m_isEnabled; + bool m_privateBrowsingEnabled; + + mutable Mutex m_syncLock; + ThreadCondition m_syncCondition; + String m_databaseDirectory; + // Holding m_syncLock is required when accessing m_completeDatabasePath + String m_completeDatabasePath; + + bool m_threadTerminationRequested; + bool m_removeIconsRequested; + bool m_iconURLImportComplete; + + Mutex m_urlAndIconLock; + // Holding m_urlAndIconLock is required when accessing any of the following data structures or the objects they contain + HashMap<String, IconRecord*> m_iconURLToRecordMap; + HashMap<String, PageURLRecord*> m_pageURLToRecordMap; + HashSet<String> m_retainedPageURLs; + + Mutex m_pendingSyncLock; + // Holding m_pendingSyncLock is required when accessing any of the following data structures + HashMap<String, PageURLSnapshot> m_pageURLsPendingSync; + HashMap<String, IconSnapshot> m_iconsPendingSync; + + Mutex m_pendingReadingLock; + // Holding m_pendingSyncLock is required when accessing any of the following data structures - when dealing with IconRecord*s, holding m_urlAndIconLock is also required + HashSet<String> m_pageURLsPendingImport; + HashSet<String> m_pageURLsInterestedInIcons; + HashSet<IconRecord*> m_iconsPendingReading; + +// *** Sync Thread Only *** +public: + // Should be used only on the sync thread and only by the Safari 2 Icons import procedure + void importIconURLForPageURL(const String& iconURL, const String& pageURL); + void importIconDataForIconURL(PassRefPtr<SharedBuffer> data, const String& iconURL); + + bool shouldStopThreadActivity() const; + +private: + static void* iconDatabaseSyncThreadStart(void *); + void* iconDatabaseSyncThread(); + + // The following block of methods are called exclusively by the sync thread to manage i/o to and from the database + // Each method should periodically monitor m_threadTerminationRequested when it makes sense to return early on shutdown + void performOpenInitialization(); + bool checkIntegrity(); + void performURLImport(); + void* syncThreadMainLoop(); + bool readFromDatabase(); + bool writeToDatabase(); + void pruneUnretainedIcons(); + void checkForDanglingPageURLs(bool pruneIfFound); + void removeAllIconsOnThread(); + void deleteAllPreparedStatements(); + void* cleanupSyncThread(); + + // Record (on disk) whether or not Safari 2-style icons were imported (once per dataabse) + bool imported(); + void setImported(bool); + + bool m_initialPruningComplete; + + void setIconURLForPageURLInSQLDatabase(const String&, const String&); + void setIconIDForPageURLInSQLDatabase(int64_t, const String&); + void removePageURLFromSQLDatabase(const String& pageURL); + int64_t getIconIDForIconURLFromSQLDatabase(const String& iconURL); + int64_t addIconURLToSQLDatabase(const String&); + PassRefPtr<SharedBuffer> getImageDataForIconURLFromSQLDatabase(const String& iconURL); + void removeIconFromSQLDatabase(const String& iconURL); + void writeIconSnapshotToSQLDatabase(const IconSnapshot&); + + // The client is set by the main thread before the thread starts, and from then on is only used by the sync thread + IconDatabaseClient* m_client; + + SQLiteDatabase m_syncDB; + + // Track whether the "Safari 2" import is complete and/or set in the database + bool m_imported; + bool m_isImportedSet; + + OwnPtr<SQLiteStatement> m_setIconIDForPageURLStatement; + OwnPtr<SQLiteStatement> m_removePageURLStatement; + OwnPtr<SQLiteStatement> m_getIconIDForIconURLStatement; + OwnPtr<SQLiteStatement> m_getImageDataForIconURLStatement; + OwnPtr<SQLiteStatement> m_addIconToIconInfoStatement; + OwnPtr<SQLiteStatement> m_addIconToIconDataStatement; + OwnPtr<SQLiteStatement> m_getImageDataStatement; + OwnPtr<SQLiteStatement> m_deletePageURLsForIconURLStatement; + OwnPtr<SQLiteStatement> m_deleteIconFromIconInfoStatement; + OwnPtr<SQLiteStatement> m_deleteIconFromIconDataStatement; + OwnPtr<SQLiteStatement> m_updateIconInfoStatement; + OwnPtr<SQLiteStatement> m_updateIconDataStatement; + OwnPtr<SQLiteStatement> m_setIconInfoStatement; + OwnPtr<SQLiteStatement> m_setIconDataStatement; +#endif // ENABLE(ICONDATABASE) +}; + +// Function to obtain the global icon database. +IconDatabase* iconDatabase(); + +} // namespace WebCore + +#endif diff --git a/WebCore/loader/icon/IconDatabaseClient.h b/WebCore/loader/icon/IconDatabaseClient.h new file mode 100644 index 0000000..e642895 --- /dev/null +++ b/WebCore/loader/icon/IconDatabaseClient.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 IconDatabaseClient_h +#define IconDatabaseClient_h + +// All of these client methods will be called from a non-main thread +// Take appropriate measures + +namespace WebCore { + +class String; + +class IconDatabaseClient { +public: + virtual ~IconDatabaseClient() { } + virtual bool performImport() { return true; } + virtual void dispatchDidRemoveAllIcons() { } + virtual void dispatchDidAddIconForPageURL(const String& pageURL) { } +}; + +} // namespace WebCore +#endif diff --git a/WebCore/loader/icon/IconDatabaseNone.cpp b/WebCore/loader/icon/IconDatabaseNone.cpp new file mode 100644 index 0000000..c76a2c4 --- /dev/null +++ b/WebCore/loader/icon/IconDatabaseNone.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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. + */ + +#include "config.h" +#include "IconDatabase.h" +#include "SharedBuffer.h" + +namespace WebCore { + +static IconDatabase* sharedIconDatabase = 0; + +// This version number is in the DB and marks the current generation of the schema +// Theoretically once the switch is flipped this should never change +// Currently, an out-of-date schema causes the DB to be wiped and reset. This isn't +// so bad during development but in the future, we would need to write a conversion +// function to advance older released schemas to "current" +const int currentDatabaseVersion = 5; + +// Icons expire once a day +const int iconExpirationTime = 60*60*24; +// Absent icons are rechecked once a week +const int missingIconExpirationTime = 60*60*24*7; + +const int updateTimerDelay = 5; + +String IconDatabase::defaultDatabaseFilename() +{ + static String defaultDatabaseFilename = "Icons.db"; + return defaultDatabaseFilename; +} + +IconDatabase* iconDatabase() +{ + if (!sharedIconDatabase) + sharedIconDatabase = new IconDatabase; + return sharedIconDatabase; +} + +IconDatabase::IconDatabase() +{ +} + +bool IconDatabase::open(const String& databasePath) +{ + return false; +} + +bool IconDatabase::isOpen() const +{ + return false; +} + +void IconDatabase::close() +{ +} + +String IconDatabase::databasePath() const +{ + return String(); +} + +void IconDatabase::removeAllIcons() +{ +} + +void IconDatabase::setPrivateBrowsingEnabled(bool flag) +{ +} + +bool IconDatabase::isPrivateBrowsingEnabled() const +{ + return false; +} + +void IconDatabase::readIconForPageURLFromDisk(const String&) +{ + +} + +Image* IconDatabase::iconForPageURL(const String& pageURL, const IntSize& size) +{ + return defaultIcon(size); +} + + +IconLoadDecision IconDatabase::loadDecisionForIconURL(const String&, DocumentLoader*) +{ + return IconLoadNo; +} + +bool IconDatabase::iconDataKnownForIconURL(const String&) +{ + return false; +} + +String IconDatabase::iconURLForPageURL(const String& pageURL) +{ + return String(); +} + +Image* IconDatabase::defaultIcon(const IntSize& size) +{ + return 0; +} + +void IconDatabase::retainIconForPageURL(const String& pageURL) +{ +} + +void IconDatabase::releaseIconForPageURL(const String& pageURL) +{ +} + +void IconDatabase::setIconDataForIconURL(PassRefPtr<SharedBuffer> data, const String& iconURL) +{ +} + +void IconDatabase::setIconURLForPageURL(const String& iconURL, const String& pageURL) +{ +} + +void IconDatabase::setEnabled(bool enabled) +{ +} + +bool IconDatabase::isEnabled() const +{ + return false; +} + +IconDatabase::~IconDatabase() +{ + ASSERT_NOT_REACHED(); +} + +void IconDatabase::checkIntegrityBeforeOpening() +{ +} + +void IconDatabase::delayDatabaseCleanup() +{ +} + +void IconDatabase::allowDatabaseCleanup() +{ +} + +void IconDatabase::setClient(IconDatabaseClient*) +{ +} + +} // namespace WebCore diff --git a/WebCore/loader/icon/IconFetcher.cpp b/WebCore/loader/icon/IconFetcher.cpp new file mode 100644 index 0000000..efa7e14 --- /dev/null +++ b/WebCore/loader/icon/IconFetcher.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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. + */ + +#include "config.h" +#include "IconFetcher.h" + +#include "Document.h" +#include "Frame.h" +#include "HTMLHeadElement.h" +#include "HTMLLinkElement.h" +#include "HTMLNames.h" +#include "MIMETypeRegistry.h" +#include "ResourceHandle.h" +#include "ResourceRequest.h" +#include "SharedBuffer.h" +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +using namespace HTMLNames; + +struct IconLinkEntry { +public: + enum IconType { + Unknown, + ICNS, + ICO, + }; + + IconLinkEntry(IconType type, const KURL& url) + : m_type(type) + , m_url(url) + { + } + + IconType type() const { return m_type; } + const KURL& url() const { return m_url; } + + SharedBuffer* buffer() + { + if (!m_buffer) + m_buffer = SharedBuffer::create(); + + return m_buffer.get(); + } + +private: + RefPtr<SharedBuffer> m_buffer; + IconType m_type; + KURL m_url; +}; + +#if PLATFORM(MAC) +static const IconLinkEntry::IconType NativeIconType = IconLinkEntry::ICNS; +#elif PLATFORM(WIN) +static const IconLinkEntry::IconType NativeIconType = IconLinkEntry::ICO; +#else +static const IconLinkEntry::IconType NativeIconType = IconLinkEntry::Unknown; +#endif + +static void parseIconLink(HTMLLinkElement* link, Vector<IconLinkEntry>& entries) +{ + // FIXME: Parse the size attribute too. + + IconLinkEntry::IconType type = IconLinkEntry::Unknown; + const KURL& url = link->href(); + + // Try to determine the file type. + String path = url.path(); + + int pos = path.reverseFind('.'); + if (pos >= 0) { + String extension = path.substring(pos + 1); + if (equalIgnoringCase(extension, "icns")) + type = IconLinkEntry::ICNS; + else if (equalIgnoringCase(extension, "ico")) + type = IconLinkEntry::ICO; + } + + entries.append(IconLinkEntry(type, url)); +} + +PassRefPtr<IconFetcher> IconFetcher::create(Frame* frame, IconFetcherClient* client) +{ + Document* document = frame->document(); + if (!document) + return 0; + + HTMLHeadElement* head = document->head(); + if (!head) + return 0; + + Vector<IconLinkEntry> entries; + + for (Node* n = head; n; n = n->traverseNextNode()) { + if (!n->hasTagName(linkTag)) + continue; + + HTMLLinkElement* link = static_cast<HTMLLinkElement*>(n); + if (!link->isIcon()) + continue; + + parseIconLink(link, entries); + } + + if (entries.isEmpty()) + return 0; + + // Check if any of the entries have the same type as the native icon type. + + // FIXME: This should be way more sophisticated, and handle conversion + // of multisize formats for example. + for (unsigned i = 0; i < entries.size(); i++) { + const IconLinkEntry& entry = entries[i]; + if (entry.type() == NativeIconType) { + RefPtr<IconFetcher> iconFetcher = adoptRef(new IconFetcher(frame, client)); + + iconFetcher->m_entries.append(entry); + iconFetcher->loadEntry(); + + return iconFetcher.release(); + } + } + + return 0; +} + +IconFetcher::IconFetcher(Frame* frame, IconFetcherClient* client) + : m_frame(frame) + , m_client(client) + , m_currentEntry(0) +{ +} + +IconFetcher::~IconFetcher() +{ + cancel(); +} + +void IconFetcher::cancel() +{ + if (m_handle) + m_handle->cancel(); +} + +PassRefPtr<SharedBuffer> IconFetcher::createIcon() +{ + ASSERT(!m_entries.isEmpty()); + + // For now, just return the data of the first entry. + return m_entries.first().buffer(); +} + + +void IconFetcher::loadEntry() +{ + ASSERT(m_currentEntry < m_entries.size()); + ASSERT(!m_handle); + + m_handle = ResourceHandle::create(m_entries[m_currentEntry].url(), this, m_frame, false, false); +} + +void IconFetcher::loadFailed() +{ + m_handle = 0; + + m_client->finishedFetchingIcon(0); +} + +void IconFetcher::didReceiveResponse(ResourceHandle* handle, const ResourceResponse& response) +{ + ASSERT(m_handle == handle); + + int statusCode = response.httpStatusCode() / 100; + if (statusCode == 4 || statusCode == 5) { + loadFailed(); + return; + } +} + +void IconFetcher::didReceiveData(ResourceHandle* handle, const char* data, int length, int lengthReceived) +{ + ASSERT(m_handle == handle); + + m_entries[m_currentEntry].buffer()->append(data, length); +} + +void IconFetcher::didFinishLoading(ResourceHandle* handle) +{ + ASSERT(m_handle == handle); + + if (m_currentEntry == m_entries.size() - 1) { + // We finished loading, create the icon + RefPtr<SharedBuffer> iconData = createIcon(); + + m_client->finishedFetchingIcon(iconData.release()); + return; + } + + // Load the next entry + m_currentEntry++; + + loadEntry(); +} + +void IconFetcher::didFail(ResourceHandle* handle, const ResourceError&) +{ + ASSERT(m_handle == handle); + + loadFailed(); +} + + +} // namespace WebCore diff --git a/WebCore/loader/icon/IconFetcher.h b/WebCore/loader/icon/IconFetcher.h new file mode 100644 index 0000000..5327693 --- /dev/null +++ b/WebCore/loader/icon/IconFetcher.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE INC. ``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 APPLE INC. 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 IconFetcher_h +#define IconFetcher_h + +#include <wtf/RefCounted.h> +#include <wtf/Forward.h> +#include <wtf/Vector.h> + +#include "ResourceHandleClient.h" + +namespace WebCore { + +class Frame; +struct IconLinkEntry; +class ResourceHandle; +class SharedBuffer; + +class IconFetcherClient { +public: + virtual void finishedFetchingIcon(PassRefPtr<SharedBuffer> iconData) = 0; + + virtual ~IconFetcherClient() { } +}; + +class IconFetcher : public RefCounted<IconFetcher>, ResourceHandleClient { +public: + static PassRefPtr<IconFetcher> create(Frame*, IconFetcherClient*); + ~IconFetcher(); + + void cancel(); + +private: + IconFetcher(Frame*, IconFetcherClient*); + void loadEntry(); + void loadFailed(); + + PassRefPtr<SharedBuffer> createIcon(); + + virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&); + virtual void didReceiveData(ResourceHandle*, const char*, int, int lengthReceived); + virtual void didFinishLoading(ResourceHandle*); + virtual void didFail(ResourceHandle*, const ResourceError&); + + Frame* m_frame; + IconFetcherClient* m_client; + + unsigned m_currentEntry; + RefPtr<ResourceHandle> m_handle; + Vector<IconLinkEntry> m_entries; +}; + +} // namespace WebCore + +#endif // IconFetcher_h diff --git a/WebCore/loader/icon/IconLoader.cpp b/WebCore/loader/icon/IconLoader.cpp new file mode 100644 index 0000000..4337f51 --- /dev/null +++ b/WebCore/loader/icon/IconLoader.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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. + */ + +#include "config.h" +#include "IconLoader.h" + +#include "Document.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "IconDatabase.h" +#include "Logging.h" +#include "ResourceHandle.h" +#include "ResourceResponse.h" +#include "ResourceRequest.h" +#include "SubresourceLoader.h" + +using namespace std; + +namespace WebCore { + +IconLoader::IconLoader(Frame* frame) + : m_frame(frame) + , m_loadIsInProgress(false) +{ +} + +auto_ptr<IconLoader> IconLoader::create(Frame* frame) +{ + return auto_ptr<IconLoader>(new IconLoader(frame)); +} + +IconLoader::~IconLoader() +{ +} + +void IconLoader::startLoading() +{ + if (m_resourceLoader) + return; + + // FIXME: http://bugs.webkit.org/show_bug.cgi?id=10902 + // Once ResourceHandle will load without a DocLoader, we can remove this check. + // A frame may be documentless - one example is a frame containing only a PDF. + if (!m_frame->document()) { + LOG(IconDatabase, "Documentless-frame - icon won't be loaded"); + return; + } + + // Set flag so we can detect the case where the load completes before + // SubresourceLoader::create returns. + m_loadIsInProgress = true; + + RefPtr<SubresourceLoader> loader = SubresourceLoader::create(m_frame, this, m_frame->loader()->iconURL()); + if (!loader) + LOG_ERROR("Failed to start load for icon at url %s", m_frame->loader()->iconURL().string().ascii().data()); + + // Store the handle so we can cancel the load if stopLoading is called later. + // But only do it if the load hasn't already completed. + if (m_loadIsInProgress) + m_resourceLoader = loader.release(); +} + +void IconLoader::stopLoading() +{ + clearLoadingState(); +} + +void IconLoader::didReceiveResponse(SubresourceLoader* resourceLoader, const ResourceResponse& response) +{ + // If we got a status code indicating an invalid response, then lets + // ignore the data and not try to decode the error page as an icon. + int status = response.httpStatusCode(); + LOG(IconDatabase, "IconLoader::didReceiveResponse() - Loader %p, response %i", resourceLoader, status); + + if (status && (status < 200 || status > 299)) { + ResourceHandle* handle = resourceLoader->handle(); + finishLoading(handle ? handle->request().url() : KURL(), 0); + } +} + +void IconLoader::didReceiveData(SubresourceLoader* loader, const char*, int size) +{ + LOG(IconDatabase, "IconLoader::didReceiveData() - Loader %p, number of bytes %i", loader, size); +} + +void IconLoader::didFail(SubresourceLoader* resourceLoader, const ResourceError&) +{ + LOG(IconDatabase, "IconLoader::didFail() - Loader %p", resourceLoader); + + // Until <rdar://problem/5463392> is resolved and we can properly cancel SubresourceLoaders when they get an error response, + // we need to be prepared to receive this call even after we've "finished loading" once. + // After it is resolved, we can restore an assertion that the load is in progress if ::didFail() is called + + if (m_loadIsInProgress) { + ASSERT(resourceLoader == m_resourceLoader); + ResourceHandle* handle = resourceLoader->handle(); + finishLoading(handle ? handle->request().url() : KURL(), 0); + } +} + +void IconLoader::didReceiveAuthenticationChallenge(SubresourceLoader* resourceLoader, const AuthenticationChallenge& challenge) +{ + // We don't ever want to prompt for authentication just for a site icon, so + // implement this method to cancel the resource load + m_resourceLoader->cancel(); +} + +void IconLoader::didFinishLoading(SubresourceLoader* resourceLoader) +{ + LOG(IconDatabase, "IconLoader::didFinishLoading() - Loader %p", resourceLoader); + + // Until <rdar://problem/5463392> is resolved and we can properly cancel SubresourceLoaders when they get an error response, + // we need to be prepared to receive this call even after we've "finished loading" once. + // After it is resolved, we can restore an assertion that the load is in progress if ::didFail() is called + + if (m_loadIsInProgress) { + ASSERT(resourceLoader == m_resourceLoader); + ResourceHandle* handle = resourceLoader->handle(); + finishLoading(handle ? handle->request().url() : KURL(), m_resourceLoader->resourceData()); + } +} + +void IconLoader::finishLoading(const KURL& iconURL, PassRefPtr<SharedBuffer> data) +{ + // When an icon load results in a 404 we commit it to the database here and clear the loading state. + // But the SubresourceLoader continues pulling in data in the background for the 404 page if the server sends one. + // Once that data finishes loading or if the load is cancelled while that data is being read, finishLoading ends up being called a second time. + // We need to change SubresourceLoader to have a mode where it will stop itself after receiving a 404 so this won't happen - + // in the meantime, we'll only commit this data to the IconDatabase if it's the first time ::finishLoading() is called + // <rdar://problem/5463392> tracks that enhancement + + if (!iconURL.isEmpty() && m_loadIsInProgress) { + iconDatabase()->setIconDataForIconURL(data, iconURL.string()); + LOG(IconDatabase, "IconLoader::finishLoading() - Committing iconURL %s to database", iconURL.string().ascii().data()); + m_frame->loader()->commitIconURLToIconDatabase(iconURL); + m_frame->loader()->client()->dispatchDidReceiveIcon(); + } + + clearLoadingState(); +} + +void IconLoader::clearLoadingState() +{ + m_resourceLoader = 0; + m_loadIsInProgress = false; +} + +} diff --git a/WebCore/loader/icon/IconLoader.h b/WebCore/loader/icon/IconLoader.h new file mode 100644 index 0000000..a7194d8 --- /dev/null +++ b/WebCore/loader/icon/IconLoader.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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 IconLoader_h +#define IconLoader_h + +#include "SubresourceLoaderClient.h" +#include <memory> +#include <wtf/Forward.h> +#include <wtf/Noncopyable.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +class Frame; +class KURL; +class SharedBuffer; + +class IconLoader : private SubresourceLoaderClient, Noncopyable { +public: + static std::auto_ptr<IconLoader> create(Frame*); + ~IconLoader(); + + void startLoading(); + void stopLoading(); + +private: + IconLoader(Frame*); + + virtual void didReceiveResponse(SubresourceLoader*, const ResourceResponse&); + virtual void didReceiveData(SubresourceLoader*, const char*, int); + virtual void didFinishLoading(SubresourceLoader*); + virtual void didFail(SubresourceLoader*, const ResourceError&); + + virtual void didReceiveAuthenticationChallenge(SubresourceLoader*, const AuthenticationChallenge&); + + void finishLoading(const KURL&, PassRefPtr<SharedBuffer> data); + void clearLoadingState(); + + Frame* m_frame; + + RefPtr<SubresourceLoader> m_resourceLoader; + bool m_loadIsInProgress; +}; // class IconLoader + +} // namespace WebCore + +#endif diff --git a/WebCore/loader/icon/IconRecord.cpp b/WebCore/loader/icon/IconRecord.cpp new file mode 100644 index 0000000..f070cc9 --- /dev/null +++ b/WebCore/loader/icon/IconRecord.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "IconRecord.h" + +#include "BitmapImage.h" +#include "IconDatabase.h" +#include "Logging.h" +#include "SQLiteStatement.h" +#include "SQLiteTransaction.h" +#include "SystemTime.h" + +#include <limits.h> + +namespace WebCore { + +IconRecord::IconRecord(const String& url) + : m_iconURL(url) + , m_stamp(0) + , m_dataSet(false) +{ + +} + +IconRecord::~IconRecord() +{ + LOG(IconDatabase, "Destroying IconRecord for icon url %s", m_iconURL.ascii().data()); +} + +Image* IconRecord::image(const IntSize& size) +{ + // FIXME rdar://4680377 - For size right now, we are returning our one and only image and the Bridge + // is resizing it in place. We need to actually store all the original representations here and return a native + // one, or resize the best one to the requested size and cache that result. + + return m_image.get(); +} + +void IconRecord::setImageData(PassRefPtr<SharedBuffer> data) +{ + // It's okay to delete the raw image here. Any existing clients using this icon will be + // managing an image that was created with a copy of this raw image data. + m_image = BitmapImage::create(); + + // Copy the provided data into the buffer of the new Image object. + if (!m_image->setData(data, true)) { + LOG(IconDatabase, "Manual image data for iconURL '%s' FAILED - it was probably invalid image data", m_iconURL.ascii().data()); + m_image.clear(); + } + + m_dataSet = true; +} + +void IconRecord::loadImageFromResource(const char* resource) +{ + if (!resource) + return; + + m_image = Image::loadPlatformResource(resource); + m_dataSet = true; +} + +ImageDataStatus IconRecord::imageDataStatus() +{ + if (!m_dataSet) + return ImageDataStatusUnknown; + if (!m_image) + return ImageDataStatusMissing; + return ImageDataStatusPresent; +} + +IconSnapshot IconRecord::snapshot(bool forDeletion) const +{ + if (forDeletion) + return IconSnapshot(m_iconURL, 0, 0); + + return IconSnapshot(m_iconURL, m_stamp, m_image ? m_image->data() : 0); +} + +} // namespace WebCore diff --git a/WebCore/loader/icon/IconRecord.h b/WebCore/loader/icon/IconRecord.h new file mode 100644 index 0000000..aaea787 --- /dev/null +++ b/WebCore/loader/icon/IconRecord.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 IconRecord_h +#define IconRecord_h + +#include "PageURLRecord.h" +#include <wtf/RefCounted.h> +#include "SharedBuffer.h" + +#include <wtf/HashSet.h> +#include <wtf/OwnPtr.h> +#include "PlatformString.h" +#include "StringHash.h" + +namespace WebCore { + +class IconDataSnapshot; +class Image; +class IntSize; +class SQLDatabase; + +enum ImageDataStatus { + ImageDataStatusPresent, ImageDataStatusMissing, ImageDataStatusUnknown +}; + +class IconSnapshot { +public: + IconSnapshot() : timestamp(0) { } + + IconSnapshot(const String& url, int stamp, SharedBuffer* theData) + : iconURL(url) + , timestamp(stamp) + , data(theData) + { } + + String iconURL; + int timestamp; + RefPtr<SharedBuffer> data; +}; + +class IconRecord : public RefCounted<IconRecord> { + friend class PageURLRecord; +public: + static PassRefPtr<IconRecord> create(const String& url) + { + return adoptRef(new IconRecord(url)); + } + ~IconRecord(); + + time_t getTimestamp() { return m_stamp; } + void setTimestamp(time_t stamp) { m_stamp = stamp; } + + void setImageData(PassRefPtr<SharedBuffer> data); + Image* image(const IntSize&); + + String iconURL() { return m_iconURL; } + + void loadImageFromResource(const char*); + + ImageDataStatus imageDataStatus(); + + const HashSet<String>& retainingPageURLs() { return m_retainingPageURLs; } + + IconSnapshot snapshot(bool forDeletion = false) const; + +private: + IconRecord(const String& url); + + String m_iconURL; + time_t m_stamp; + RefPtr<Image> m_image; + + HashSet<String> m_retainingPageURLs; + + // This allows us to cache whether or not a SiteIcon has had its data set yet + // This helps the IconDatabase know if it has to set the data on a new object or not, + // and also to determine if the icon is missing data or if it just hasn't been brought + // in from the DB yet + bool m_dataSet; + + // FIXME - Right now WebCore::Image doesn't have a very good API for accessing multiple representations + // Even the NSImage way of doing things that we do in WebKit isn't very clean... once we come up with a + // better way of handling that, we'll likely have a map of size-to-images similar to below + // typedef HashMap<IntSize, Image*> SizeImageMap; + // SizeImageMap m_images; +}; + + +} //namespace WebCore + +#endif diff --git a/WebCore/loader/icon/PageURLRecord.cpp b/WebCore/loader/icon/PageURLRecord.cpp new file mode 100644 index 0000000..09d649f --- /dev/null +++ b/WebCore/loader/icon/PageURLRecord.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "PageURLRecord.h" + +#include "IconRecord.h" + +namespace WebCore { + +PageURLRecord::PageURLRecord(const String& pageURL) + : m_pageURL(pageURL) + , m_retainCount(0) +{ +} + +PageURLRecord::~PageURLRecord() +{ + setIconRecord(0); +} + +void PageURLRecord::setIconRecord(PassRefPtr<IconRecord> icon) +{ + if (m_iconRecord) + m_iconRecord->m_retainingPageURLs.remove(m_pageURL); + + m_iconRecord = icon; + + if (m_iconRecord) + m_iconRecord->m_retainingPageURLs.add(m_pageURL); +} + +PageURLSnapshot PageURLRecord::snapshot(bool forDeletion) const +{ + return PageURLSnapshot(m_pageURL, (m_iconRecord && !forDeletion) ? m_iconRecord->iconURL() : String()); +} + +} // namespace WebCore diff --git a/WebCore/loader/icon/PageURLRecord.h b/WebCore/loader/icon/PageURLRecord.h new file mode 100644 index 0000000..bc52f5b --- /dev/null +++ b/WebCore/loader/icon/PageURLRecord.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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 PageURLRecord_h +#define PageURLRecord_h + +#include "PlatformString.h" + +#include <wtf/Noncopyable.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +class IconRecord; + +class PageURLSnapshot { +public: + PageURLSnapshot() { } + + PageURLSnapshot(const String& page, const String& icon) + : pageURL(page) + , iconURL(icon) + { } + + String pageURL; + String iconURL; +}; + +class PageURLRecord : Noncopyable { +public: + PageURLRecord(const String& pageURL); + ~PageURLRecord(); + + inline String url() const { return m_pageURL; } + + void setIconRecord(PassRefPtr<IconRecord>); + IconRecord* iconRecord() { return m_iconRecord.get(); } + + PageURLSnapshot snapshot(bool forDeletion = false) const; + + // Returns false if the page wasn't retained beforehand, true if the retain count was already 1 or higher + inline bool retain() { return m_retainCount++; } + + // Returns true if the page is still retained after the call. False if the retain count just dropped to 0 + inline bool release() + { + ASSERT(m_retainCount > 0); + return --m_retainCount; + } + + inline int retainCount() const { return m_retainCount; } +private: + String m_pageURL; + RefPtr<IconRecord> m_iconRecord; + int m_retainCount; +}; + +} + +#endif // PageURLRecord_h diff --git a/WebCore/loader/loader.cpp b/WebCore/loader/loader.cpp new file mode 100644 index 0000000..6221a6a --- /dev/null +++ b/WebCore/loader/loader.cpp @@ -0,0 +1,495 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#include "loader.h" + +#include "Cache.h" +#include "CachedImage.h" +#include "CachedResource.h" +#include "CString.h" +#include "DocLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "HTMLDocument.h" +#include "Request.h" +#include "ResourceHandle.h" +#include "ResourceRequest.h" +#include "ResourceResponse.h" +#include "SecurityOrigin.h" +#include "SubresourceLoader.h" +#include <wtf/Assertions.h> +#include <wtf/Vector.h> + +#define REQUEST_MANAGEMENT_ENABLED 0 +#define REQUEST_DEBUG 0 + +namespace WebCore { + +#if REQUEST_MANAGEMENT_ENABLED +// Match the parallel connection count used by the networking layer +// FIXME should not hardcode something like this +static const unsigned maxRequestsInFlightPerHost = 4; +// Having a limit might still help getting more important resources first +static const unsigned maxRequestsInFlightForNonHTTPProtocols = 20; +#else +static const unsigned maxRequestsInFlightPerHost = 10000; +static const unsigned maxRequestsInFlightForNonHTTPProtocols = 10000; +#endif + + +Loader::Loader() + : m_nonHTTPProtocolHost(AtomicString(), maxRequestsInFlightForNonHTTPProtocols) + , m_requestTimer(this, &Loader::requestTimerFired) +{ +} + +Loader::~Loader() +{ + ASSERT_NOT_REACHED(); +} + +Loader::Priority Loader::determinePriority(const CachedResource* resource) const +{ +#if REQUEST_MANAGEMENT_ENABLED + switch (resource->type()) { + case CachedResource::CSSStyleSheet: +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: +#endif +#if ENABLE(XBL) + case CachedResource::XBL: +#endif + return High; + case CachedResource::Script: + case CachedResource::FontResource: + return Medium; + case CachedResource::ImageResource: + return Low; + } + ASSERT_NOT_REACHED(); + return Low; +#else + return High; +#endif +} + +void Loader::load(DocLoader* docLoader, CachedResource* resource, bool incremental, bool skipCanLoadCheck, bool sendResourceLoadCallbacks) +{ + ASSERT(docLoader); + Request* request = new Request(docLoader, resource, incremental, skipCanLoadCheck, sendResourceLoadCallbacks); + + Host* host; + KURL url(resource->url()); + bool isHTTP = url.protocolIs("http") || url.protocolIs("https"); + if (isHTTP) { + AtomicString hostName = url.host(); + host = m_hosts.get(hostName.impl()); + if (!host) { + host = new Host(hostName, maxRequestsInFlightPerHost); + m_hosts.add(hostName.impl(), host); + } + } else + host = &m_nonHTTPProtocolHost; + + bool hadRequests = host->hasRequests(); + Priority priority = determinePriority(resource); + host->addRequest(request, priority); + docLoader->incrementRequestCount(); + + if (priority > Low || !isHTTP || !hadRequests) { + // Try to request important resources immediately + host->servePendingRequests(priority); + } else { + // Handle asynchronously so early low priority requests don't get scheduled before later high priority ones + scheduleServePendingRequests(); + } +} + +void Loader::scheduleServePendingRequests() +{ + if (!m_requestTimer.isActive()) + m_requestTimer.startOneShot(0); +} + +void Loader::requestTimerFired(Timer<Loader>*) +{ + servePendingRequests(); +} + +void Loader::servePendingRequests(Priority minimumPriority) +{ + m_requestTimer.stop(); + + m_nonHTTPProtocolHost.servePendingRequests(minimumPriority); + + Vector<Host*> hostsToServe; + copyValuesToVector(m_hosts, hostsToServe); + for (unsigned n = 0; n < hostsToServe.size(); ++n) { + Host* host = hostsToServe[n]; + if (host->hasRequests()) + host->servePendingRequests(minimumPriority); + else if (!host->processingResource()){ + AtomicString name = host->name(); + delete host; + m_hosts.remove(name.impl()); + } + } +} + +void Loader::cancelRequests(DocLoader* docLoader) +{ + if (m_nonHTTPProtocolHost.hasRequests()) + m_nonHTTPProtocolHost.cancelRequests(docLoader); + + Vector<Host*> hostsToCancel; + copyValuesToVector(m_hosts, hostsToCancel); + for (unsigned n = 0; n < hostsToCancel.size(); ++n) { + Host* host = hostsToCancel[n]; + if (host->hasRequests()) + host->cancelRequests(docLoader); + } + + scheduleServePendingRequests(); + + if (docLoader->loadInProgress()) + ASSERT(docLoader->requestCount() == 1); + else + ASSERT(docLoader->requestCount() == 0); +} + +Loader::Host::Host(const AtomicString& name, unsigned maxRequestsInFlight) + : m_name(name) + , m_maxRequestsInFlight(maxRequestsInFlight) + , m_processingResource(false) +{ +} + +Loader::Host::~Host() +{ + ASSERT(m_requestsLoading.isEmpty()); + for (unsigned p = 0; p <= High; p++) + ASSERT(m_requestsPending[p].isEmpty()); +} + +void Loader::Host::addRequest(Request* request, Priority priority) +{ + m_requestsPending[priority].append(request); +} + +bool Loader::Host::hasRequests() const +{ + if (!m_requestsLoading.isEmpty()) + return true; + for (unsigned p = 0; p <= High; p++) { + if (!m_requestsPending[p].isEmpty()) + return true; + } + return false; +} + +void Loader::Host::servePendingRequests(Loader::Priority minimumPriority) +{ + bool serveMore = true; + for (int priority = High; priority >= minimumPriority && serveMore; --priority) + servePendingRequests(m_requestsPending[priority], serveMore); +} + +void Loader::Host::servePendingRequests(RequestQueue& requestsPending, bool& serveLowerPriority) +{ + while (!requestsPending.isEmpty()) { + Request* request = requestsPending.first(); + DocLoader* docLoader = request->docLoader(); + bool resourceIsCacheValidator = request->cachedResource()->isCacheValidator(); + // If the document is fully parsed and there are no pending stylesheets there won't be any more + // resources that we would want to push to the front of the queue. Just hand off the remaining resources + // to the networking layer. + bool parsedAndStylesheetsKnown = !docLoader->doc()->parsing() && docLoader->doc()->haveStylesheetsLoaded(); + if (!parsedAndStylesheetsKnown && !resourceIsCacheValidator && m_requestsLoading.size() >= m_maxRequestsInFlight) { + serveLowerPriority = false; + return; + } + requestsPending.removeFirst(); + + ResourceRequest resourceRequest(request->cachedResource()->url()); +#ifdef ANDROID + resourceRequest.setCachedResource(request->cachedResource()); +#endif + + if (!request->cachedResource()->accept().isEmpty()) + resourceRequest.setHTTPAccept(request->cachedResource()->accept()); + + KURL referrer = docLoader->doc()->url(); + if ((referrer.protocolIs("http") || referrer.protocolIs("https")) && referrer.path().isEmpty()) + referrer.setPath("/"); + resourceRequest.setHTTPReferrer(referrer.string()); + FrameLoader::addHTTPOriginIfNeeded(resourceRequest, docLoader->doc()->securityOrigin()->toString()); + + if (resourceIsCacheValidator) { + CachedResource* resourceToRevalidate = request->cachedResource()->resourceToRevalidate(); + ASSERT(resourceToRevalidate->canUseCacheValidator()); + ASSERT(resourceToRevalidate->isLoaded()); + const String& lastModified = resourceToRevalidate->response().httpHeaderField("Last-Modified"); + const String& eTag = resourceToRevalidate->response().httpHeaderField("ETag"); + if (!lastModified.isEmpty() || !eTag.isEmpty()) { + if (docLoader->cachePolicy() == CachePolicyReload || docLoader->cachePolicy() == CachePolicyRefresh) + resourceRequest.setHTTPHeaderField("Cache-Control", "max-age=0"); + if (!lastModified.isEmpty()) + resourceRequest.setHTTPHeaderField("If-Modified-Since", lastModified); + if (!eTag.isEmpty()) + resourceRequest.setHTTPHeaderField("If-None-Match", eTag); + } + } + + RefPtr<SubresourceLoader> loader = SubresourceLoader::create(docLoader->doc()->frame(), + this, resourceRequest, request->shouldSkipCanLoadCheck(), request->sendResourceLoadCallbacks()); + if (loader) { + m_requestsLoading.add(loader.release(), request); + request->cachedResource()->setRequestedFromNetworkingLayer(); +#if REQUEST_DEBUG + printf("HOST %s COUNT %d LOADING %s\n", resourceRequest.url().host().latin1().data(), m_requestsLoading.size(), request->cachedResource()->url().latin1().data()); +#endif + } else { + docLoader->decrementRequestCount(); + docLoader->setLoadInProgress(true); + request->cachedResource()->error(); + docLoader->setLoadInProgress(false); + delete request; + } + } +} + +void Loader::Host::didFinishLoading(SubresourceLoader* loader) +{ + RequestMap::iterator i = m_requestsLoading.find(loader); + if (i == m_requestsLoading.end()) + return; + + m_processingResource = true; + + Request* request = i->second; + m_requestsLoading.remove(i); + DocLoader* docLoader = request->docLoader(); + if (!request->isMultipart()) + docLoader->decrementRequestCount(); + + CachedResource* resource = request->cachedResource(); + ASSERT(!resource->resourceToRevalidate()); + + // If we got a 4xx response, we're pretending to have received a network + // error, so we can't send the successful data() and finish() callbacks. + if (!resource->errorOccurred()) { + docLoader->setLoadInProgress(true); + resource->data(loader->resourceData(), true); + resource->finish(); + } + + delete request; + + docLoader->setLoadInProgress(false); + + docLoader->checkForPendingPreloads(); + +#if REQUEST_DEBUG + KURL u(resource->url()); + printf("HOST %s COUNT %d RECEIVED %s\n", u.host().latin1().data(), m_requestsLoading.size(), resource->url().latin1().data()); +#endif + servePendingRequests(); + + m_processingResource = false; +} + +void Loader::Host::didFail(SubresourceLoader* loader, const ResourceError&) +{ + didFail(loader); +} + +void Loader::Host::didFail(SubresourceLoader* loader, bool cancelled) +{ + loader->clearClient(); + + RequestMap::iterator i = m_requestsLoading.find(loader); + if (i == m_requestsLoading.end()) + return; + + m_processingResource = true; + + Request* request = i->second; + m_requestsLoading.remove(i); + DocLoader* docLoader = request->docLoader(); + if (!request->isMultipart()) + docLoader->decrementRequestCount(); + + CachedResource* resource = request->cachedResource(); + + if (resource->resourceToRevalidate()) + cache()->revalidationFailed(resource); + + if (!cancelled) { + docLoader->setLoadInProgress(true); + resource->error(); + } + + docLoader->setLoadInProgress(false); + if (cancelled || !resource->isPreloaded()) + cache()->remove(resource); + + delete request; + + docLoader->checkForPendingPreloads(); + + servePendingRequests(); + + m_processingResource = false; +} + +void Loader::Host::didReceiveResponse(SubresourceLoader* loader, const ResourceResponse& response) +{ + Request* request = m_requestsLoading.get(loader); + + // FIXME: This is a workaround for <rdar://problem/5236843> + // If a load starts while the frame is still in the provisional state + // (this can be the case when loading the user style sheet), committing the load then causes all + // requests to be removed from the m_requestsLoading map. This means that request might be null here. + // In that case we just return early. + // ASSERT(request); + if (!request) + return; + + CachedResource* resource = request->cachedResource(); + + if (resource->isCacheValidator()) { + if (response.httpStatusCode() == 304) { + // 304 Not modified / Use local copy + m_requestsLoading.remove(loader); + request->docLoader()->decrementRequestCount(); + + // Existing resource is ok, just use it updating the expiration time. + cache()->revalidationSucceeded(resource, response); + + if (request->docLoader()->frame()) + request->docLoader()->frame()->loader()->checkCompleted(); + + delete request; + + servePendingRequests(); + return; + } + // Did not get 304 response, continue as a regular resource load. + cache()->revalidationFailed(resource); + } + + resource->setResponse(response); + + String encoding = response.textEncodingName(); + if (!encoding.isNull()) + request->cachedResource()->setEncoding(encoding); + + if (request->isMultipart()) { + ASSERT(request->cachedResource()->isImage()); + static_cast<CachedImage*>(request->cachedResource())->clear(); + if (request->docLoader()->frame()) + request->docLoader()->frame()->loader()->checkCompleted(); + } else if (response.isMultipart()) { + request->setIsMultipart(true); + + // We don't count multiParts in a DocLoader's request count + request->docLoader()->decrementRequestCount(); + + // If we get a multipart response, we must have a handle + ASSERT(loader->handle()); + if (!request->cachedResource()->isImage()) + loader->handle()->cancel(); + } +} + +void Loader::Host::didReceiveData(SubresourceLoader* loader, const char* data, int size) +{ + Request* request = m_requestsLoading.get(loader); + if (!request) + return; + + CachedResource* resource = request->cachedResource(); + ASSERT(!resource->isCacheValidator()); + + if (resource->errorOccurred()) + return; + + m_processingResource = true; + + if (resource->response().httpStatusCode() / 100 == 4) { + // Treat a 4xx response like a network error. + resource->error(); + m_processingResource = false; + return; + } + + // Set the data. + if (request->isMultipart()) { + // The loader delivers the data in a multipart section all at once, send eof. + // The resource data will change as the next part is loaded, so we need to make a copy. + RefPtr<SharedBuffer> copiedData = SharedBuffer::create(data, size); + resource->data(copiedData.release(), true); + } else if (request->isIncremental()) + resource->data(loader->resourceData(), false); + + m_processingResource = false; +} + +void Loader::Host::cancelPendingRequests(RequestQueue& requestsPending, DocLoader* docLoader) +{ + RequestQueue remaining; + RequestQueue::iterator end = requestsPending.end(); + for (RequestQueue::iterator it = requestsPending.begin(); it != end; ++it) { + Request* request = *it; + if (request->docLoader() == docLoader) { + cache()->remove(request->cachedResource()); + delete request; + docLoader->decrementRequestCount(); + } else + remaining.append(request); + } + requestsPending.swap(remaining); +} + +void Loader::Host::cancelRequests(DocLoader* docLoader) +{ + for (unsigned p = 0; p <= High; p++) + cancelPendingRequests(m_requestsPending[p], docLoader); + + Vector<SubresourceLoader*, 256> loadersToCancel; + + RequestMap::iterator end = m_requestsLoading.end(); + for (RequestMap::iterator i = m_requestsLoading.begin(); i != end; ++i) { + Request* r = i->second; + if (r->docLoader() == docLoader) + loadersToCancel.append(i->first.get()); + } + + for (unsigned i = 0; i < loadersToCancel.size(); ++i) { + SubresourceLoader* loader = loadersToCancel[i]; + didFail(loader, true); + } +} + +} //namespace WebCore diff --git a/WebCore/loader/loader.h b/WebCore/loader/loader.h new file mode 100644 index 0000000..c51374c --- /dev/null +++ b/WebCore/loader/loader.h @@ -0,0 +1,97 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@kde.org> + Copyright (C) 2004, 2006, 2007, 2008 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef loader_h +#define loader_h + +#include "AtomicString.h" +#include "AtomicStringImpl.h" +#include "PlatformString.h" +#include "SubresourceLoaderClient.h" +#include "Timer.h" +#include <wtf/Deque.h> +#include <wtf/HashMap.h> +#include <wtf/Noncopyable.h> + +namespace WebCore { + + class CachedResource; + class DocLoader; + class Request; + + class Loader : Noncopyable { + public: + Loader(); + ~Loader(); + + void load(DocLoader*, CachedResource*, bool incremental = true, bool skipCanLoadCheck = false, bool sendResourceLoadCallbacks = true); + + void cancelRequests(DocLoader*); + + enum Priority { Low, Medium, High }; + void servePendingRequests(Priority minimumPriority = Low); + + private: + Priority determinePriority(const CachedResource*) const; + void scheduleServePendingRequests(); + + void requestTimerFired(Timer<Loader>*); + + class Host : private SubresourceLoaderClient { + public: + Host(const AtomicString& name, unsigned maxRequestsInFlight); + ~Host(); + + const AtomicString& name() const { return m_name; } + void addRequest(Request*, Priority); + void servePendingRequests(Priority minimumPriority = Low); + void cancelRequests(DocLoader*); + bool hasRequests() const; + bool processingResource() const { return m_processingResource; } + + private: + virtual void didReceiveResponse(SubresourceLoader*, const ResourceResponse&); + virtual void didReceiveData(SubresourceLoader*, const char*, int); + virtual void didFinishLoading(SubresourceLoader*); + virtual void didFail(SubresourceLoader*, const ResourceError&); + + typedef Deque<Request*> RequestQueue; + void servePendingRequests(RequestQueue& requestsPending, bool& serveLowerPriority); + void didFail(SubresourceLoader*, bool cancelled = false); + void cancelPendingRequests(RequestQueue& requestsPending, DocLoader*); + + RequestQueue m_requestsPending[High + 1]; + typedef HashMap<RefPtr<SubresourceLoader>, Request*> RequestMap; + RequestMap m_requestsLoading; + const AtomicString m_name; + const int m_maxRequestsInFlight; + bool m_processingResource; + }; + typedef HashMap<AtomicStringImpl*, Host*> HostMap; + HostMap m_hosts; + Host m_nonHTTPProtocolHost; + + Timer<Loader> m_requestTimer; + }; + +} + +#endif diff --git a/WebCore/loader/mac/DocumentLoaderMac.cpp b/WebCore/loader/mac/DocumentLoaderMac.cpp new file mode 100644 index 0000000..05c6e26 --- /dev/null +++ b/WebCore/loader/mac/DocumentLoaderMac.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "DocumentLoader.h" +#include "MainResourceLoader.h" +#include "ResourceHandle.h" +#include "ResourceLoader.h" + +namespace WebCore { + +#ifndef BUILDING_ON_TIGER +static void scheduleAll(const ResourceLoaderSet& loaders, SchedulePair* pair) +{ + const ResourceLoaderSet copy = loaders; + ResourceLoaderSet::const_iterator end = copy.end(); + for (ResourceLoaderSet::const_iterator it = copy.begin(); it != end; ++it) + if (ResourceHandle* handle = (*it)->handle()) + handle->schedule(pair); +} + +static void unscheduleAll(const ResourceLoaderSet& loaders, SchedulePair* pair) +{ + const ResourceLoaderSet copy = loaders; + ResourceLoaderSet::const_iterator end = copy.end(); + for (ResourceLoaderSet::const_iterator it = copy.begin(); it != end; ++it) + if (ResourceHandle* handle = (*it)->handle()) + handle->unschedule(pair); +} +#endif + +void DocumentLoader::schedule(SchedulePair* pair) +{ +#ifndef BUILDING_ON_TIGER + if (m_mainResourceLoader && m_mainResourceLoader->handle()) + m_mainResourceLoader->handle()->schedule(pair); + scheduleAll(m_subresourceLoaders, pair); + scheduleAll(m_plugInStreamLoaders, pair); + scheduleAll(m_multipartSubresourceLoaders, pair); +#endif +} + +void DocumentLoader::unschedule(SchedulePair* pair) +{ +#ifndef BUILDING_ON_TIGER + if (m_mainResourceLoader && m_mainResourceLoader->handle()) + m_mainResourceLoader->handle()->unschedule(pair); + unscheduleAll(m_subresourceLoaders, pair); + unscheduleAll(m_plugInStreamLoaders, pair); + unscheduleAll(m_multipartSubresourceLoaders, pair); +#endif +} + +} // namespace diff --git a/WebCore/loader/mac/LoaderNSURLExtras.h b/WebCore/loader/mac/LoaderNSURLExtras.h new file mode 100644 index 0000000..ce5a490 --- /dev/null +++ b/WebCore/loader/mac/LoaderNSURLExtras.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#import <Foundation/Foundation.h> + +#ifdef __cplusplus +extern "C" { +#endif + +NSString *suggestedFilenameWithMIMEType(NSURL *url, NSString *MIMEType); + +#ifdef __cplusplus +} +#endif diff --git a/WebCore/loader/mac/LoaderNSURLExtras.mm b/WebCore/loader/mac/LoaderNSURLExtras.mm new file mode 100644 index 0000000..9a507f5 --- /dev/null +++ b/WebCore/loader/mac/LoaderNSURLExtras.mm @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2005, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#import "config.h" +#import "LoaderNSURLExtras.h" + +#import <wtf/Assertions.h> +#import <wtf/Vector.h> +#import "KURL.h" +#import "LocalizedStrings.h" +#import "MIMETypeRegistry.h" +#import "PlatformString.h" +#import "WebCoreNSStringExtras.h" + +using namespace WebCore; + +static bool vectorContainsString(const Vector<String>& vector, const String& string) +{ + int size = vector.size(); + for (int i = 0; i < size; i++) + if (vector[i] == string) + return true; + return false; +} + +NSString *suggestedFilenameWithMIMEType(NSURL *url, NSString *MIMEType) +{ + // Get the filename from the URL. Try the lastPathComponent first. + NSString *lastPathComponent = [[url path] lastPathComponent]; + NSString *filename = filenameByFixingIllegalCharacters(lastPathComponent); + NSString *extension = nil; + + if ([filename length] == 0 || [lastPathComponent isEqualToString:@"/"]) { + // lastPathComponent is no good, try the host. + NSString *host = KURL(url).host(); + filename = filenameByFixingIllegalCharacters(host); + if ([filename length] == 0) { + // Can't make a filename using this URL, use "unknown". + filename = copyImageUnknownFileLabel(); + } + } else { + // Save the extension for later correction. Only correct the extension of the lastPathComponent. + // For example, if the filename ends up being the host, we wouldn't want to correct ".com" in "www.apple.com". + extension = [filename pathExtension]; + } + + // No mime type reported. Just return the filename we have now. + if (!MIMEType) { + return filename; + } + + // Do not correct filenames that are reported with a mime type of tar, and + // have a filename which has .tar in it or ends in .tgz + if (([MIMEType isEqualToString:@"application/tar"] || [MIMEType isEqualToString:@"application/x-tar"]) + && (hasCaseInsensitiveSubstring(filename, @".tar") + || hasCaseInsensitiveSuffix(filename, @".tgz"))) { + return filename; + } + + // I don't think we need to worry about this for the image case + // If the type is known, check the extension and correct it if necessary. + if (![MIMEType isEqualToString:@"application/octet-stream"] && ![MIMEType isEqualToString:@"text/plain"]) { + Vector<String> extensions = MIMETypeRegistry::getExtensionsForMIMEType(MIMEType); + + if (extensions.isEmpty() || !vectorContainsString(extensions, extension)) { + // The extension doesn't match the MIME type. Correct this. + NSString *correctExtension = MIMETypeRegistry::getPreferredExtensionForMIMEType(MIMEType); + if ([correctExtension length] != 0) { + // Append the correct extension. + filename = [filename stringByAppendingPathExtension:correctExtension]; + } + } + } + + return filename; +} diff --git a/WebCore/loader/mac/ResourceLoaderMac.mm b/WebCore/loader/mac/ResourceLoaderMac.mm new file mode 100644 index 0000000..9769ac9 --- /dev/null +++ b/WebCore/loader/mac/ResourceLoaderMac.mm @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS 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. + */ + +#include "config.h" +#include "ResourceLoader.h" + +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "ResourceHandle.h" + +namespace WebCore { + +NSCachedURLResponse* ResourceLoader::willCacheResponse(ResourceHandle* handle, NSCachedURLResponse* response) +{ + return frameLoader()->client()->willCacheResponse(documentLoader(), identifier(), response); +} + +} diff --git a/WebCore/loader/win/DocumentLoaderWin.cpp b/WebCore/loader/win/DocumentLoaderWin.cpp new file mode 100644 index 0000000..bab7de6 --- /dev/null +++ b/WebCore/loader/win/DocumentLoaderWin.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2006 Don Gibson <dgibson77@gmail.com> + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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. + */ + +#include "config.h" +#include "DocumentLoader.h" + +#include "FrameWin.h" +#include "PlatformString.h" + +namespace WebCore { + +void DocumentLoader::setTitle(const String& title) +{ + String text = title; + text.replace('//', m_frame->backslashAsCurrencySymbol()); + + FrameWin* frameWin = static_cast<FrameWin*>(m_frame); + if (frameWin->client()) + frameWin->client()->setTitle(text); +} + +} diff --git a/WebCore/loader/win/FrameLoaderWin.cpp b/WebCore/loader/win/FrameLoaderWin.cpp new file mode 100644 index 0000000..66aa6ff --- /dev/null +++ b/WebCore/loader/win/FrameLoaderWin.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2006 Don Gibson <dgibson77@gmail.com> + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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. + */ + +#include "config.h" +#include "FrameLoader.h" + +#include "DocumentLoader.h" +#include "FrameLoadRequest.h" +#include "FrameWin.h" +#include "ResourceRequest.h" + +namespace WebCore { + +void FrameLoader::urlSelected(const FrameLoadRequest& request, Event* /*triggering Event*/) +{ + FrameWin* frameWin = static_cast<FrameWin*>(m_frame); + if (frameWin->client()) + frameWin->client()->openURL(request.resourceRequest().url().string(), request.lockHistory()); +} + +void FrameLoader::submitForm(const FrameLoadRequest& request, Event*) +{ + const ResourceRequest& resourceRequest = request.resourceRequest(); + +#ifdef MULTIPLE_FORM_SUBMISSION_PROTECTION + // FIXME: this is a hack inherited from FrameMac, and should be pushed into Frame + if (m_submittedFormURL == resourceRequest.url()) + return; + m_submittedFormURL = resourceRequest.url(); +#endif + + FrameWin* frameWin = static_cast<FrameWin*>(m_frame); + if (frameWin->client()) + frameWin->client()->submitForm(resourceRequest.httpMethod(), resourceRequest.url(), resourceRequest.httpBody()); + + clearRecordedFormValues(); +} + +} |