summaryrefslogtreecommitdiffstats
path: root/WebCore/loader
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:30:52 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:30:52 -0800
commit8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2 (patch)
tree11425ea0b299d6fb89c6d3618a22d97d5bf68d0f /WebCore/loader
parent648161bb0edfc3d43db63caed5cc5213bc6cb78f (diff)
downloadexternal_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.zip
external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.tar.gz
external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.tar.bz2
auto import from //depot/cupcake/@135843
Diffstat (limited to 'WebCore/loader')
-rw-r--r--WebCore/loader/Cache.cpp724
-rw-r--r--WebCore/loader/Cache.h213
-rw-r--r--WebCore/loader/CachePolicy.h40
-rw-r--r--WebCore/loader/CachedCSSStyleSheet.cpp127
-rw-r--r--WebCore/loader/CachedCSSStyleSheet.h68
-rw-r--r--WebCore/loader/CachedFont.cpp203
-rw-r--r--WebCore/loader/CachedFont.h89
-rw-r--r--WebCore/loader/CachedImage.cpp364
-rw-r--r--WebCore/loader/CachedImage.h101
-rw-r--r--WebCore/loader/CachedResource.cpp316
-rw-r--r--WebCore/loader/CachedResource.h238
-rw-r--r--WebCore/loader/CachedResourceClient.h82
-rw-r--r--WebCore/loader/CachedResourceClientWalker.cpp55
-rw-r--r--WebCore/loader/CachedResourceClientWalker.h51
-rw-r--r--WebCore/loader/CachedResourceHandle.cpp42
-rw-r--r--WebCore/loader/CachedResourceHandle.h92
-rw-r--r--WebCore/loader/CachedScript.cpp107
-rw-r--r--WebCore/loader/CachedScript.h62
-rw-r--r--WebCore/loader/CachedXBLDocument.cpp112
-rw-r--r--WebCore/loader/CachedXBLDocument.h69
-rw-r--r--WebCore/loader/CachedXSLStyleSheet.cpp103
-rw-r--r--WebCore/loader/CachedXSLStyleSheet.h66
-rw-r--r--WebCore/loader/DocLoader.cpp468
-rw-r--r--WebCore/loader/DocLoader.h146
-rw-r--r--WebCore/loader/DocumentLoader.cpp924
-rw-r--r--WebCore/loader/DocumentLoader.h311
-rw-r--r--WebCore/loader/EmptyClients.h418
-rw-r--r--WebCore/loader/FTPDirectoryDocument.cpp493
-rw-r--r--WebCore/loader/FTPDirectoryDocument.h48
-rw-r--r--WebCore/loader/FTPDirectoryParser.cpp1624
-rw-r--r--WebCore/loader/FTPDirectoryParser.h157
-rw-r--r--WebCore/loader/FormState.cpp49
-rw-r--r--WebCore/loader/FormState.h59
-rw-r--r--WebCore/loader/FrameLoader.cpp5283
-rw-r--r--WebCore/loader/FrameLoader.h701
-rw-r--r--WebCore/loader/FrameLoaderClient.h221
-rw-r--r--WebCore/loader/FrameLoaderTypes.h78
-rw-r--r--WebCore/loader/ImageDocument.cpp366
-rw-r--r--WebCore/loader/ImageDocument.h76
-rw-r--r--WebCore/loader/ImageLoader.cpp137
-rw-r--r--WebCore/loader/ImageLoader.h73
-rw-r--r--WebCore/loader/MainResourceLoader.cpp496
-rw-r--r--WebCore/loader/MainResourceLoader.h104
-rw-r--r--WebCore/loader/MediaDocument.cpp168
-rw-r--r--WebCore/loader/MediaDocument.h54
-rw-r--r--WebCore/loader/NavigationAction.cpp83
-rw-r--r--WebCore/loader/NavigationAction.h61
-rw-r--r--WebCore/loader/NetscapePlugInStreamLoader.cpp130
-rw-r--r--WebCore/loader/NetscapePlugInStreamLoader.h70
-rw-r--r--WebCore/loader/PluginDocument.cpp150
-rw-r--r--WebCore/loader/PluginDocument.h48
-rw-r--r--WebCore/loader/ProgressTracker.cpp253
-rw-r--r--WebCore/loader/ProgressTracker.h80
-rw-r--r--WebCore/loader/Request.cpp47
-rw-r--r--WebCore/loader/Request.h63
-rw-r--r--WebCore/loader/ResourceLoader.cpp446
-rw-r--r--WebCore/loader/ResourceLoader.h150
-rw-r--r--WebCore/loader/SubresourceLoader.cpp268
-rw-r--r--WebCore/loader/SubresourceLoader.h73
-rw-r--r--WebCore/loader/SubresourceLoaderClient.h60
-rw-r--r--WebCore/loader/SubstituteData.h69
-rw-r--r--WebCore/loader/SubstituteResource.h64
-rw-r--r--WebCore/loader/TextDocument.cpp187
-rw-r--r--WebCore/loader/TextDocument.h51
-rw-r--r--WebCore/loader/TextResourceDecoder.cpp799
-rw-r--r--WebCore/loader/TextResourceDecoder.h80
-rw-r--r--WebCore/loader/UserStyleSheetLoader.cpp62
-rw-r--r--WebCore/loader/UserStyleSheetLoader.h57
-rw-r--r--WebCore/loader/appcache/ApplicationCache.cpp192
-rw-r--r--WebCore/loader/appcache/ApplicationCache.h103
-rw-r--r--WebCore/loader/appcache/ApplicationCacheGroup.cpp715
-rw-r--r--WebCore/loader/appcache/ApplicationCacheGroup.h152
-rw-r--r--WebCore/loader/appcache/ApplicationCacheResource.cpp71
-rw-r--r--WebCore/loader/appcache/ApplicationCacheResource.h74
-rw-r--r--WebCore/loader/appcache/ApplicationCacheStorage.cpp663
-rw-r--r--WebCore/loader/appcache/ApplicationCacheStorage.h96
-rw-r--r--WebCore/loader/appcache/DOMApplicationCache.cpp286
-rw-r--r--WebCore/loader/appcache/DOMApplicationCache.h140
-rw-r--r--WebCore/loader/appcache/DOMApplicationCache.idl74
-rw-r--r--WebCore/loader/appcache/ManifestParser.cpp166
-rw-r--r--WebCore/loader/appcache/ManifestParser.h52
-rw-r--r--WebCore/loader/archive/Archive.h62
-rw-r--r--WebCore/loader/archive/ArchiveFactory.cpp89
-rw-r--r--WebCore/loader/archive/ArchiveFactory.h50
-rw-r--r--WebCore/loader/archive/ArchiveResource.cpp77
-rw-r--r--WebCore/loader/archive/ArchiveResource.h65
-rw-r--r--WebCore/loader/archive/ArchiveResourceCollection.cpp89
-rw-r--r--WebCore/loader/archive/ArchiveResourceCollection.h59
-rw-r--r--WebCore/loader/archive/cf/LegacyWebArchive.cpp562
-rw-r--r--WebCore/loader/archive/cf/LegacyWebArchive.h67
-rw-r--r--WebCore/loader/archive/cf/LegacyWebArchiveMac.mm74
-rw-r--r--WebCore/loader/icon/IconDatabase.cpp2053
-rw-r--r--WebCore/loader/icon/IconDatabase.h243
-rw-r--r--WebCore/loader/icon/IconDatabaseClient.h48
-rw-r--r--WebCore/loader/icon/IconDatabaseNone.cpp174
-rw-r--r--WebCore/loader/icon/IconFetcher.cpp236
-rw-r--r--WebCore/loader/icon/IconFetcher.h78
-rw-r--r--WebCore/loader/icon/IconLoader.cpp171
-rw-r--r--WebCore/loader/icon/IconLoader.h70
-rw-r--r--WebCore/loader/icon/IconRecord.cpp106
-rw-r--r--WebCore/loader/icon/IconRecord.h117
-rw-r--r--WebCore/loader/icon/PageURLRecord.cpp63
-rw-r--r--WebCore/loader/icon/PageURLRecord.h85
-rw-r--r--WebCore/loader/loader.cpp495
-rw-r--r--WebCore/loader/loader.h97
-rw-r--r--WebCore/loader/mac/DocumentLoaderMac.cpp79
-rw-r--r--WebCore/loader/mac/LoaderNSURLExtras.h39
-rw-r--r--WebCore/loader/mac/LoaderNSURLExtras.mm102
-rw-r--r--WebCore/loader/mac/ResourceLoaderMac.mm43
-rw-r--r--WebCore/loader/win/DocumentLoaderWin.cpp46
-rw-r--r--WebCore/loader/win/FrameLoaderWin.cpp63
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();
+}
+
+}